mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-04 02:21:02 +08:00
RemoteAddr is preserved in SSH->HTTP bridge for Auth API
Got rid of MemoryListener, instead replacing it with a closely tied pair of "fake socket" and "fake connection", where the connection wraps around the undrlying SSH channel. This allows me: - Know exactly when connection.Close() is called (no wait for in-memory pipes) - Keep the original remote addr around Most of the changes are in api_with_roles.go
This commit is contained in:
+113
-7
@@ -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"}
|
||||
}
|
||||
|
||||
+6
-9
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
/home/ekontsevoy/go/src/github.com/gravitational/teleport/web/dist/app
|
||||
/var/go/src/github.com/gravitational/teleport/web/dist/app
|
||||
Reference in New Issue
Block a user