diff --git a/lib/auth/api_with_roles.go b/lib/auth/api_with_roles.go index 2f7a5bf18dd..18af67fea20 100644 --- a/lib/auth/api_with_roles.go +++ b/lib/auth/api_with_roles.go @@ -21,6 +21,7 @@ import ( "net" "net/http" "sync" + "time" "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/events" @@ -30,10 +31,12 @@ import ( log "github.com/Sirupsen/logrus" "github.com/gravitational/trace" + + "golang.org/x/crypto/ssh" ) type APIWithRoles struct { - listeners map[teleport.Role]*utils.MemoryListener + listeners map[teleport.Role]*fakeSocket servers map[teleport.Role]*APIServer } @@ -45,7 +48,7 @@ func NewAPIWithRoles(authServer *AuthServer, roles []teleport.Role) *APIWithRoles { api := APIWithRoles{} - api.listeners = make(map[teleport.Role]*utils.MemoryListener) + api.listeners = make(map[teleport.Role]*fakeSocket) api.servers = make(map[teleport.Role]*APIServer) for _, role := range roles { @@ -58,7 +61,7 @@ func NewAPIWithRoles(authServer *AuthServer, role: role, } api.servers[role] = NewAPIServer(&a) - api.listeners[role] = utils.NewMemoryListener() + api.listeners[role] = makefakeSocket() } return &api } @@ -68,6 +71,7 @@ func (api *APIWithRoles) Serve() { for role, _ := range api.listeners { wg.Add(1) go func(listener net.Listener, handler http.Handler) { + log.Debugf("[api_with_roles] about to call http.Serve() on a listener") if err := http.Serve(listener, handler); (err != nil) && (err != io.EOF) { log.Errorf(err.Error()) } @@ -76,19 +80,121 @@ func (api *APIWithRoles) Serve() { wg.Wait() } -func (api *APIWithRoles) HandleConn(conn net.Conn, role teleport.Role) error { +// HandleNewChannel is called when a new SSH channel (SSH connection) wants to communicate via HTTP API +// to one of the API servers +func (api *APIWithRoles) HandleNewChannel(remoteAddr net.Addr, channel ssh.Channel, role teleport.Role) error { + // find a listener mapped to the requested role: listener, ok := api.listeners[role] if !ok { - conn.Close() + channel.Close() return trace.Errorf("no such role '%v'", role) } - log.Debugf("---------> APIWithRoles.HandleConn remote addr: %v", conn.RemoteAddr()) - return listener.Handle(conn) + // create a bridge between the incoming SSH channel to the HTTP-based API server + return listener.CreateBridge(remoteAddr, channel) } func (api *APIWithRoles) Close() { for _, listener := range api.listeners { listener.Close() } +} +// Implements a fake "socket" (net.Listener interface) on top of exisitng ssh.Channel +type fakeSocket struct { + closed chan int + connections chan net.Conn +} + +func makefakeSocket() *fakeSocket { + return &fakeSocket{ + closed: make(chan int), + connections: make(chan net.Conn), + } +} + +type FakeSSHConnection struct { + remoteAddr net.Addr + sshChan ssh.Channel + closed chan int +} + +func (conn *FakeSSHConnection) Read(b []byte) (n int, err error) { + return conn.sshChan.Read(b) +} + +func (conn *FakeSSHConnection) Write(b []byte) (n int, err error) { + return conn.sshChan.Write(b) +} + +func (conn *FakeSSHConnection) Close() error { + conn.closed <- 1 + return conn.sshChan.Close() +} + +func (conn *FakeSSHConnection) RemoteAddr() net.Addr { + return conn.remoteAddr +} + +func (conn *FakeSSHConnection) LocalAddr() net.Addr { + return &utils.NetAddr{AddrNetwork: "tcp", Addr: "socket.over.ssh"} +} + +// SetDeadline is needed to implement net.Conn interface +func (conn *FakeSSHConnection) SetDeadline(t time.Time) error { + return nil +} + +// SetReadDeadline is needed to implement net.Conn interface +func (conn *FakeSSHConnection) SetReadDeadline(t time.Time) error { + return nil +} + +// SetWriteDeadline is needed to implement net.Conn interface +func (conn *FakeSSHConnection) SetWriteDeadline(t time.Time) error { + return nil +} + +// CreateBridge takes an incoming SSH connection and creates an SSH-to-HTTP "bridge connection" +// and waits for that connection to be closed either by the client or by the server +func (socket *fakeSocket) CreateBridge(remoteAddr net.Addr, sshChan ssh.Channel) error { + log.Debugf("SocketOverSSH.Handle(from=%v) is called", remoteAddr) + // wrap sshChan into a 'fake connection' which allows us to + // a) preserve the original address of the connected client + // b) sit and wait until client closes the ssh channel, so we'll close this fake socket + connection := &FakeSSHConnection{ + remoteAddr: remoteAddr, + sshChan: sshChan, + closed: make(chan int), + } + socket.connections <- connection // Accept() unblocks here + + // wait for the connection to close: + select { + case <-connection.closed: + break + } + log.Debugf("SocketOverSSH.Handle(from=%v) is done", remoteAddr) + return nil +} + +// Accept waits for new connections to arrive (via CreateBridge) and returns them to +// the blocked http.Serve() +func (socket *fakeSocket) Accept() (c net.Conn, err error) { + select { + case newConnection := <-socket.connections: + return newConnection, nil + case <-socket.closed: + return nil, io.EOF + } +} + +// Close closes the listener. +// Any blocked Accept operations will be unblocked and return errors. +func (socket *fakeSocket) Close() error { + return nil +} + +// Addr returns the listener's network address. +func (socket *fakeSocket) Addr() net.Addr { + return &utils.NetAddr{AddrNetwork: "tcp", Addr: "socket.over.ssh"} } diff --git a/lib/auth/tun.go b/lib/auth/tun.go index eb21da0dff1..b3da168b6a8 100644 --- a/lib/auth/tun.go +++ b/lib/auth/tun.go @@ -277,23 +277,20 @@ func (s *AuthTunnel) handleWebAgentRequest(sconn *ssh.ServerConn, ch ssh.Channel // handleDirectTCPIPRequest accepts an incoming SSH connection via TCP/IP and forwards // it to the local auth server which listens on local UNIX pipe -func (s *AuthTunnel) handleDirectTCPIPRequest(sconn *ssh.ServerConn, ch ssh.Channel, req *sshutils.DirectTCPIPReq) { +func (s *AuthTunnel) handleDirectTCPIPRequest(sconn *ssh.ServerConn, sshChannel ssh.Channel, req *sshutils.DirectTCPIPReq) { defer sconn.Close() - log.Debugf("[TUNNEL] ------> Remote address: %v", sconn.RemoteAddr()) - // retreive the role from thsi connection's permissions (make sure it's a valid role) role := teleport.Role(sconn.Permissions.Extensions[ExtRole]) if err := role.Check(); err != nil { log.Errorf(err.Error()) return } - // forward this to API-with-roles server, it will try to proxy this connection - // to an API service mapped to this role: - addr := &utils.NetAddr{Addr: "localhost", AddrNetwork: "tcp://"} - conn := utils.NewPipeNetConn(ch, ch, ch, addr, sconn.RemoteAddr()) - if err := s.apiServer.HandleConn(conn, role); err != nil { - log.Errorf(err.Error()) + + // Forward this new SSH channel to API-with-roles server. It will try to proxy this + // connection to the API service mapped to this role: + if err := s.apiServer.HandleNewChannel(sconn.RemoteAddr(), sshChannel, role); err != nil { + log.Error(err) return } } diff --git a/lib/utils/memlistener.go b/lib/utils/memlistener.go deleted file mode 100644 index 2d3fb0cd802..00000000000 --- a/lib/utils/memlistener.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -Copyright 2015 Gravitational, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -// package auth implements certificate signing authority and access control server -// Authority server is composed of several parts: -// -// * Authority server itself that implements signing and acl logic -// * HTTP server wrapper for authority server -// * HTTP client wrapper -// - -package utils - -import ( - "github.com/gravitational/trace" - "io" - "net" - "sync" - "sync/atomic" -) - -// Memory listener implements net.Listener using net.Conn -type MemoryListener struct { - connections chan net.Conn - state chan int - closed uint32 -} - -func NewMemoryListener() *MemoryListener { - ml := &MemoryListener{} - ml.connections = make(chan net.Conn) - ml.state = make(chan int) - return ml -} - -func (ml *MemoryListener) Accept() (net.Conn, error) { - select { - case newConnection := <-ml.connections: - return newConnection, nil - case <-ml.state: - return nil, io.EOF - } -} - -func (ml *MemoryListener) Close() error { - if atomic.CompareAndSwapUint32(&ml.closed, 0, 1) { - close(ml.state) - } - return nil -} - -func (ml *MemoryListener) Handle(conn net.Conn) error { - select { - case <-ml.state: - return trace.Errorf("MemoryListener is closed") - default: - } - - local, remote := net.Pipe() - - wg := &sync.WaitGroup{} - wg.Add(2) - go func() { - defer wg.Done() - defer local.Close() - io.Copy(local, conn) - }() - go func() { - defer wg.Done() - defer conn.Close() - io.Copy(conn, local) - }() - ml.connections <- remote - wg.Wait() - - return nil -} - -func (ml *MemoryListener) Addr() net.Addr { - addr := NetAddr{ - AddrNetwork: "tcp", - Addr: "memoryListener", - } - return &addr -} diff --git a/web/dist/app/app b/web/dist/app/app index 13a90ff41eb..b3619c2b6d6 120000 --- a/web/dist/app/app +++ b/web/dist/app/app @@ -1 +1 @@ -/home/ekontsevoy/go/src/github.com/gravitational/teleport/web/dist/app \ No newline at end of file +/var/go/src/github.com/gravitational/teleport/web/dist/app \ No newline at end of file