diff --git a/examples/embedded.yaml b/examples/embedded.yaml index 8fd2069c163..2b58ce36855 100644 --- a/examples/embedded.yaml +++ b/examples/embedded.yaml @@ -67,3 +67,5 @@ proxy: - period: 1s average: 500 burst: 300 + hangouts_enabled: true + hangouts_listen_addr: tcp://localhost:33009 \ No newline at end of file diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 35158da1c3e..4adb3efe8c8 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -191,7 +191,7 @@ func (s *AuthServer) CreateToken() (string, error) { return "", err } - return string(p.SID) + return string(p.SID), nil } func (s *AuthServer) GenerateToken(domainName, role string, ttl time.Duration) (string, error) { diff --git a/lib/auth/permissions.go b/lib/auth/permissions.go index d700ad317b7..2de5fac1ce9 100644 --- a/lib/auth/permissions.go +++ b/lib/auth/permissions.go @@ -36,6 +36,7 @@ func NewStandardPermissions() PermissionChecker { ActionSignIn: true, ActionGenerateUserCert: true, ActionGetTrustedCertificates: true, + ActionGetRemoteCertificates: true, } sp.permissions[RoleProvisionToken] = map[string]bool{ @@ -73,6 +74,53 @@ func NewStandardPermissions() PermissionChecker { return &sp } +func NewHangoutPermissions() PermissionChecker { + sp := standardPermissions{} + sp.permissions = make(map[string](map[string]bool)) + + sp.permissions[RoleUser] = map[string]bool{ + ActionSignIn: true, + ActionGenerateUserCert: true, + ActionGetTrustedCertificates: true, + ActionGetRemoteCertificates: true, + } + + sp.permissions[RoleProvisionToken] = map[string]bool{ + ActionRegisterUsingToken: true, + ActionRegisterNewAuthServer: true, + ActionGenerateUserCert: true, + } + + sp.permissions[RoleNode] = map[string]bool{ + ActionUpsertServer: true, + ActionGetUserCertificateAuthority: true, + ActionGetRemoteCertificates: true, + ActionGetTrustedCertificates: true, + ActionGetCertificateID: true, + ActionGetAllUserMappings: true, + ActionUserMappingExists: true, + ActionGetUserKeys: true, + ActionGetServers: true, + ActionGetAuthServers: true, + ActionGetHostCertificateAuthority: true, + ActionUpsertParty: true, + ActionLogEntry: true, + ActionGetChunkWriter: true, + } + + sp.permissions[RoleWeb] = map[string]bool{ + ActionGetWebSession: true, + ActionDeleteWebSession: true, + } + + sp.permissions[RoleSignup] = map[string]bool{ + ActionGetSignupTokenData: true, + ActionCreateUserWithToken: true, + } + + return &sp +} + func (sp *standardPermissions) HasPermission(role, action string) error { if role == RoleAdmin { return nil @@ -114,6 +162,7 @@ var StandardRoles = []string{ var HangoutRoles = []string{ RoleAdmin, RoleHangoutUser, + RoleProvisionToken, } const ( diff --git a/lib/auth/tun.go b/lib/auth/tun.go index d5d814590ee..00712341340 100644 --- a/lib/auth/tun.go +++ b/lib/auth/tun.go @@ -530,6 +530,30 @@ func (t *TunDialer) Dial(network, address string) (net.Conn, error) { } } +func NewClientFromSSHClient(sshClient *ssh.Client) (*Client, error) { + tr := &http.Transport{ + Dial: sshClient.Dial, + /*Dial: func(network, addr string) (net.Conn, error) { + ch, _, err := conn.OpenChannel(ReqDirectTCPIP, nil) + if err != nil { + log.Errorf(err.Error()) + return nil, err + } + return utils.NewChConn(conn, ch), nil + },*/ + } + clt, err := NewClient( + "http://stub:0", + roundtrip.HTTPClient(&http.Client{ + Transport: tr, + })) + if err != nil { + return nil, trace.Wrap(err) + } + + return clt, nil +} + const ( ReqWebSessionAgent = "web-session-agent@teleport" ReqProvision = "provision@teleport" diff --git a/lib/client/client.go b/lib/client/client.go index e16fe6a3b21..3266348f5e4 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -247,6 +247,88 @@ func (proxy *ProxyClient) ConnectToNode(nodeAddress string, authMethods []ssh.Au return nil, e } +// ConnectToNode connects to the ssh server via Proxy. +// It returns connected and authenticated NodeClient +//DialHangout +func (proxy *ProxyClient) ConnectToHangout(nodeAddress string, authMethods []ssh.AuthMethod, + hostKeyCallback utils.HostKeyCallback, user string) (*NodeClient, error) { + if len(authMethods) == 0 { + return nil, trace.Errorf("no authMethods were provided") + } + + proxy.Lock() + defer proxy.Unlock() + + e := trace.Errorf("unknown Error") + + for _, authMethod := range authMethods { + + proxySession, err := proxy.Client.NewSession() + if err != nil { + return nil, trace.Wrap(err) + } + + proxyWriter, err := proxySession.StdinPipe() + if err != nil { + return nil, trace.Wrap(err) + } + + proxyReader, err := proxySession.StdoutPipe() + if err != nil { + return nil, trace.Wrap(err) + } + + err = proxySession.RequestSubsystem(fmt.Sprintf("hangout:%v", nodeAddress)) + if err != nil { + return nil, trace.Wrap(err) + } + + localAddr, err := utils.ParseAddr("tcp://" + proxy.proxyAddress) + if err != nil { + return nil, trace.Wrap(err) + } + + remoteAddr, err := utils.ParseAddr("tcp://" + nodeAddress) + if err != nil { + return nil, trace.Wrap(err) + } + + pipeNetConn := utils.NewPipeNetConn( + proxyReader, + proxyWriter, + proxySession, + localAddr, + remoteAddr, + ) + + sshConfig := &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{authMethod}, + HostKeyCallback: hostKeyCallback, + } + + conn, chans, reqs, err := ssh.NewClientConn(pipeNetConn, + nodeAddress, sshConfig) + if err != nil { + if strings.Contains(err.Error(), "handshake failed") || + strings.Contains(err.Error(), "CheckHostSigners") { + e = trace.Wrap(err) + proxySession.Close() + continue + } + return nil, trace.Wrap(err) + } + + client := ssh.NewClient(conn, chans, reqs) + if err != nil { + return nil, trace.Wrap(err) + } + + return &NodeClient{Client: client}, nil + } + return nil, e +} + func (proxy *ProxyClient) Close() error { return proxy.Client.Close() } diff --git a/lib/client/utils.go b/lib/client/utils.go index 1428a0d2068..3a0c004c2c1 100644 --- a/lib/client/utils.go +++ b/lib/client/utils.go @@ -32,6 +32,7 @@ import ( "time" "github.com/gravitational/teleport/lib/auth/native" + "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/web" "github.com/gravitational/trace" @@ -60,6 +61,7 @@ func NewWebAuth(ag agent.Agent, callbackFunc := func() (signers []ssh.Signer, err error) { err = Login(ag, webProxyAddress, user, certificateTTL, passwordCallback) if err != nil { + fmt.Printf("Can't login to the server: %v\n", err) return nil, trace.Wrap(err) } @@ -71,6 +73,7 @@ func NewWebAuth(ag agent.Agent, if err != nil { err = Login(ag, webProxyAddress, user, certificateTTL, passwordCallback) if err != nil { + fmt.Printf("Can't login to the server: %v\n", err) return trace.Wrap(err) } return CheckHostSignerFromCache(hostname, remote, key) diff --git a/lib/hangout/hangout.go b/lib/hangout/hangout.go index fdaa45edf1d..35315b8b07a 100644 --- a/lib/hangout/hangout.go +++ b/lib/hangout/hangout.go @@ -22,37 +22,44 @@ import ( "os/user" //"path/filepath" //"strings" - //"time" "net" "os" + "path" + "time" "github.com/gravitational/teleport/lib/auth" authority "github.com/gravitational/teleport/lib/auth/native" - //"github.com/gravitational/teleport/lib/backend/boltbk" - //"github.com/gravitational/teleport/lib/backend/encryptedbk" - //"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor" + "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/backend/boltbk" + "github.com/gravitational/teleport/lib/backend/encryptedbk" + "github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor" + "github.com/gravitational/teleport/lib/backend/etcdbk" "github.com/gravitational/teleport/lib/events" - //"github.com/gravitational/teleport/lib/events/boltlog" + "github.com/gravitational/teleport/lib/events/boltlog" "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/recorder" - //"github.com/gravitational/teleport/lib/recorder/boltrec" + "github.com/gravitational/teleport/lib/recorder/boltrec" "github.com/gravitational/teleport/lib/reversetunnel" "github.com/gravitational/teleport/lib/service" - //"github.com/gravitational/teleport/lib/services" - //sess "github.com/gravitational/teleport/lib/session" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/session" + "github.com/gravitational/teleport/lib/srv" //"github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/session" + //"github.com/gravitational/session" + log "github.com/Sirupsen/logrus" + //"github.com/gravitational/log" "github.com/gravitational/trace" //"github.com/gokyle/hotp" //"github.com/mailgun/lemma/secret" + "github.com/codahale/lunk" "golang.org/x/crypto/ssh" - //"golang.org/x/crypto/ssh/agent" + "golang.org/x/crypto/ssh/agent" ) type Hangout struct { - auth auth.AuthServer + auth *auth.AuthServer tunClt reversetunnel.Agent elog events.Log rec recorder.Recorder @@ -62,45 +69,53 @@ type Hangout struct { authPort string ClientAuthMethod ssh.AuthMethod HostKeyCallback utils.HostKeyCallback + client *auth.TunClient + passwordToken string + HangoutInfo HangoutInfo + Token string } func New(proxyTunnelAddress, nodeListeningAddress, authListeningAddress string, readOnly bool, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) (*Hangout, error) { + //log.SetOutput(os.Stderr) + //log.SetLevel(log.InfoLevel) + //log.Initialize("console", "INFO") + cfg := service.Config{} service.SetDefaults(&cfg) - cfg.DataDir = "/tmp/teleporthangout" + cfg.DataDir = HangoutDataDir cfg.Hostname = "localhost" cfg.Auth.HostAuthorityDomain = "localhost" cfg.Auth.KeysBackend.Type = "bolt" - cfg.Auth.KeysBackend.Params = `{"path": "` + DataDir + `/teleport.auth.db"}` + cfg.Auth.KeysBackend.Params = `{"path": "` + cfg.DataDir + `/teleport.auth.db"}` cfg.Auth.EventsBackend.Type = "bolt" - cfg.Auth.EventsBackend.Params = `{"path": "` + DataDir + `/teleport.event.db"}` + cfg.Auth.EventsBackend.Params = `{"path": "` + cfg.DataDir + `/teleport.event.db"}` cfg.Auth.RecordsBackend.Type = "bolt" - cfg.Auth.RecordsBackend.Params = `{"path": "` + DataDir + `/teleport.records.db"}` - authAddress, err := utils.ParseAddr(authListeningAddress) + cfg.Auth.RecordsBackend.Params = `{"path": "` + cfg.DataDir + `/teleport.records.db"}` + authAddress, err := utils.ParseAddr("tcp://" + authListeningAddress) if err != nil { return nil, trace.Wrap(err) } - cfg.Auth.SSHAddr = authAddress + cfg.Auth.SSHAddr = *authAddress cfg.AuthServers = []utils.NetAddr{cfg.Auth.SSHAddr} - nodeAddress, err := utils.ParseAddr(nodeListeningAddress) + nodeAddress, err := utils.ParseAddr("tcp://" + nodeListeningAddress) if err != nil { return nil, trace.Wrap(err) } - cfg.SSH.Addr = nodeAddress + cfg.SSH.Addr = *nodeAddress - tunnelAddress, err := utils.ParseAddr(proxyTunnelAddress) + tunnelAddress, err := utils.ParseAddr("tcp://" + proxyTunnelAddress) if err != nil { return nil, trace.Wrap(err) } - cfg.ReverseTunnel.DialAddr = tunnelAddress + cfg.ReverseTunnel.DialAddr = *tunnelAddress - _, err := os.Stat(cfg.DataDir) + _, err = os.Stat(cfg.DataDir) if os.IsNotExist(err) { err := os.MkdirAll(cfg.DataDir, os.ModeDir|0777) if err != nil { @@ -113,19 +128,31 @@ func New(proxyTunnelAddress, nodeListeningAddress, authListeningAddress string, return nil, trace.Wrap(err) } - var err error - h.hangoutID, err = h.auth.CreateToken() if err != nil { return nil, trace.Wrap(err) } + thisSrv := services.Server{ + ID: cfg.Auth.SSHAddr.Addr, + Addr: cfg.Auth.SSHAddr.Addr, + Hostname: h.hangoutID, + } + err = h.auth.UpsertServer(thisSrv, 0) + if err != nil { + return nil, trace.Wrap(err) + } + + log.Infof("***************** init SSH") + if err := h.initSSHEndpoint(cfg); err != nil { return nil, trace.Wrap(err) } + log.Infof("***************** init Tun Agent") if err := h.initTunAgent(cfg, authMethods, hostKeyCallback); err != nil { return nil, trace.Wrap(err) } + log.Infof("***************** create User") if err := h.createUser(); err != nil { return nil, trace.Wrap(err) } @@ -139,12 +166,25 @@ func New(proxyTunnelAddress, nodeListeningAddress, authListeningAddress string, return nil, trace.Wrap(err) } - h.ClientAuthMethod, h.HostKeyCallback, err = Authorize(auth, h.userPassword) + h.ClientAuthMethod, h.HostKeyCallback, err = Authorize(h.client, h.userPassword) + + h.HangoutInfo.AuthPort = h.authPort + h.HangoutInfo.NodePort = h.nodePort + h.HangoutInfo.HangoutID = h.hangoutID + h.HangoutInfo.HangoutPassword = h.passwordToken + + h.Token, err = MarshalHangoutInfo(&h.HangoutInfo) + if err != nil { + return nil, trace.Wrap(err) + } + + return h, nil } func (h *Hangout) createUser() error { var err error - h.userPassword, err = h.auth.GenerateToken(HangoutUser, services.TokenRoleHangout, 0) + h.passwordToken, err = h.auth.GenerateToken(HangoutUser, services.TokenRoleHangout, 0) + h.userPassword = h.passwordToken[0:100] if err != nil { return trace.Wrap(err) } @@ -154,18 +194,24 @@ func (h *Hangout) createUser() error { return trace.Wrap(err) } osUser := u.Username + h.HangoutInfo.OSUser = osUser - _, _, err := s.a.UpsertPassword(HangoutUser, h.userPassword) + _, _, err = h.auth.UpsertPassword(HangoutUser, []byte(h.userPassword)) if err != nil { return trace.Wrap(err) } err = h.auth.UpsertUserMapping("local", HangoutUser, osUser, 0) + if err != nil { + return trace.Wrap(err) + } + + return nil } -func Authorize(auth auth.Client, userPassword string) (ssh.AuthMethod, utils.HostKeyCallback, error) { +func Authorize(auth auth.ClientI, userPassword string) (ssh.AuthMethod, utils.HostKeyCallback, error) { - priv, pub, err := native.New().GenerateKeyPair("") + priv, pub, err := authority.New().GenerateKeyPair("") if err != nil { return nil, nil, trace.Wrap(err) } @@ -174,7 +220,7 @@ func Authorize(auth auth.Client, userPassword string) (ssh.AuthMethod, utils.Hos return nil, nil, trace.Wrap(err) } - pcert, _, _, _, err := ssh.ParseAuthorizedKey(login.Cert) + pcert, _, _, _, err := ssh.ParseAuthorizedKey(cert) if err != nil { return nil, nil, trace.Wrap(err) } @@ -244,8 +290,9 @@ func (h *Hangout) initAuth(cfg service.Config, readOnlyHangout bool) error { if err != nil { return trace.Wrap(err) } - apisrv := auth.NewAPIWithRoles(asrv, elog, session.New(b), rec, - auth.NewStandardPermissions(), auth.HangoutRoles, + h.auth = asrv + apisrv := auth.NewAPIWithRoles(asrv, h.elog, session.New(b), h.rec, + auth.NewHangoutPermissions(), auth.HangoutRoles, ) go apisrv.Serve() @@ -270,16 +317,10 @@ func (h *Hangout) initAuth(cfg service.Config, readOnlyHangout bool) error { } }() - thisSrv := services.Server{ - ID: cfg.Auth.SSHAddr.Addr(), - Addr: cfg.Auth.SSHAddr.Addr(), - Hostname: h.HangoutID, - } - err := asrv.UpsertServer(thisSrv, 0) return nil } -func (h *Hangout) initTunAgent(cfg Config, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error { +func (h *Hangout) initTunAgent(cfg service.Config, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error { signer, err := auth.ReadKeys(cfg.Hostname, cfg.DataDir) if err != nil { return trace.Wrap(err) @@ -293,6 +334,8 @@ func (h *Hangout) initTunAgent(cfg Config, authMethods []ssh.AuthMethod, hostKey return trace.Wrap(err) } + h.client = client + elog := &service.FanOutEventLogger{ Loggers: []lunk.EventLogger{ lunk.NewTextEventLogger(log.StandardLogger().Writer()), @@ -311,10 +354,11 @@ func (h *Hangout) initTunAgent(cfg Config, authMethods []ssh.AuthMethod, hostKey } log.Infof("[REVERSE TUNNEL] teleport tunnel agent starting") + if err := a.Start(); err != nil { + log.Fatalf("failed to start: %v", err) + } + go func() { - if err := a.Start(); err != nil { - log.Fatalf("failed to start: %v", err) - } if err := a.Wait(); err != nil { log.Fatalf("failed to start: %v", err) } @@ -322,7 +366,7 @@ func (h *Hangout) initTunAgent(cfg Config, authMethods []ssh.AuthMethod, hostKey return nil } -func (h *Hangout) initSSHEndpoint(cfg Config) error { +func (h *Hangout) initSSHEndpoint(cfg service.Config) error { signer, err := auth.ReadKeys(cfg.Hostname, cfg.DataDir) if err != nil { return trace.Wrap(err) @@ -349,7 +393,7 @@ func (h *Hangout) initSSHEndpoint(cfg Config) error { } s, err := srv.New(cfg.SSH.Addr, - cfg.Hostname, + h.hangoutID, []ssh.Signer{signer}, client, limiter, @@ -367,18 +411,86 @@ func (h *Hangout) initSSHEndpoint(cfg Config) error { log.Infof("[SSH] server is starting on %v", cfg.SSH.Addr) go func() { if err := s.Start(); err != nil { - log.Errorf(err) - } - if err := s.Wait(); err != nil { - log.Errorf(err) + log.Errorf(err.Error()) } + s.Wait() }() return nil } +func initBackend(dataDir, domainName string, peers service.NetAddrSlice, cfg service.AuthConfig) (*encryptedbk.ReplicatedBackend, error) { + var bk backend.Backend + var err error + + switch cfg.KeysBackend.Type { + case "etcd": + bk, err = etcdbk.FromJSON(cfg.KeysBackend.Params) + case "bolt": + bk, err = boltbk.FromJSON(cfg.KeysBackend.Params) + default: + return nil, trace.Errorf("unsupported backend type: %v", cfg.KeysBackend.Type) + } + if err != nil { + return nil, trace.Wrap(err) + } + + keyStorage := path.Join(dataDir, "backend_keys") + encryptionKeys := []encryptor.Key{} + for _, strKey := range cfg.KeysBackend.EncryptionKeys { + encKey, err := encryptedbk.KeyFromString(strKey) + if err != nil { + return nil, err + } + encryptionKeys = append(encryptionKeys, encKey) + } + + encryptedBk, err := encryptedbk.NewReplicatedBackend(bk, + keyStorage, encryptionKeys, encryptor.GenerateGPGKey) + + if err != nil { + log.Errorf(err.Error()) + log.Infof("Initializing backend as follower node") + myKey, err := encryptor.GenerateGPGKey(domainName + " key") + if err != nil { + return nil, err + } + masterKey, err := auth.RegisterNewAuth( + domainName, cfg.Token, myKey.Public(), peers) + if err != nil { + return nil, err + } + log.Infof(" ", myKey, masterKey) + encryptedBk, err = encryptedbk.NewReplicatedBackend(bk, + keyStorage, []encryptor.Key{myKey, masterKey}, + encryptor.GenerateGPGKey) + if err != nil { + return nil, err + } + } + return encryptedBk, nil +} + +func initEventBackend(btype string, params string) (events.Log, error) { + switch btype { + case "bolt": + return boltlog.FromJSON(params) + } + return nil, trace.Errorf("unsupported backend type: %v", btype) +} + +func initRecordBackend(btype string, params string) (recorder.Recorder, error) { + switch btype { + case "bolt": + return boltrec.FromJSON(params) + } + return nil, trace.Errorf("unsupported backend type: %v", btype) +} + func (h *Hangout) GetJoinCommand() string { - nodeAddress := h.hangoutID + ":" + h.nodePort - authAddress := h.hangoutID + ":" + h.authPort + //nodeAddress := h.hangoutID + ":" + h.nodePort + //authAddress := h.hangoutID + ":" + h.authPort + return "" } const HangoutUser = "hangoutUser" +const HangoutDataDir = "/tmp/teleport_hangouts" diff --git a/lib/hangout/token.go b/lib/hangout/token.go new file mode 100644 index 00000000000..204097a57df --- /dev/null +++ b/lib/hangout/token.go @@ -0,0 +1,53 @@ +/* +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 hangout + +import ( + "encoding/base64" + "encoding/json" + + "github.com/gravitational/trace" +) + +type HangoutInfo struct { + AuthPort string + NodePort string + HangoutID string + HangoutPassword string + OSUser string +} + +func MarshalHangoutInfo(h *HangoutInfo) (string, error) { + jsonString, err := json.Marshal(h) + if err != nil { + return "", trace.Wrap(err) + } + b64str := base64.StdEncoding.EncodeToString(jsonString) + return string(b64str), nil +} + +func UnmarshalHangoutInfo(id string) (*HangoutInfo, error) { + jsonString, err := base64.StdEncoding.DecodeString(id) + if err != nil { + return nil, trace.Wrap(err) + } + var h HangoutInfo + err = json.Unmarshal(jsonString, &h) + if err != nil { + return nil, trace.Wrap(err) + } + return &h, nil +} diff --git a/lib/reversetunnel/agent.go b/lib/reversetunnel/agent.go index a99e739f87e..a5fe40cc2b1 100644 --- a/lib/reversetunnel/agent.go +++ b/lib/reversetunnel/agent.go @@ -180,9 +180,6 @@ func (a *Agent) connect() error { Auth: []ssh.AuthMethod{authMethod}, HostKeyCallback: a.hostKeyCallback, }) - if err != nil { - log.Warningf(err.Error()) - } if c != nil { break } diff --git a/lib/reversetunnel/srv.go b/lib/reversetunnel/srv.go index aa7fe315b20..9b821e08ff8 100644 --- a/lib/reversetunnel/srv.go +++ b/lib/reversetunnel/srv.go @@ -91,6 +91,30 @@ func NewServer(addr utils.NetAddr, hostSigners []ssh.Signer, return srv, nil } +// New returns an unstarted server +func NewHangoutServer(addr utils.NetAddr, hostSigners []ssh.Signer, + ap auth.AccessPoint, limiter *limiter.Limiter) (Server, error) { + srv := &server{ + sites: []*remoteSite{}, + ap: ap, + } + s, err := sshutils.NewServer( + addr, + srv, + hostSigners, + sshutils.AuthMethods{ + PublicKey: srv.hangoutKeyAuth, + }, + limiter, + ) + if err != nil { + return nil, err + } + srv.certChecker = ssh.CertChecker{IsAuthority: srv.isHangoutAuthority} + srv.srv = s + return srv, nil +} + func (s *server) Wait() { s.srv.Wait() } @@ -144,6 +168,20 @@ func (s *server) isAuthority(auth ssh.PublicKey) bool { return false } +// isAuthority is called during checking the client key, to see if the signing +// key is the real CA authority key. +func (s *server) isHangoutAuthority(auth ssh.PublicKey) bool { + _, found, err := s.ap.GetCertificateID(services.UserCert, auth) + if err != nil { + log.Errorf("failed to retrieve trused keys, err: %v", err) + return false + } + if found { + return true + } + return false +} + func (s *server) getTrustedCAKeys() ([]ssh.PublicKey, error) { out := []ssh.PublicKey{} authKeys := [][]byte{} @@ -155,6 +193,7 @@ func (s *server) getTrustedCAKeys() ([]ssh.PublicKey, error) { certs, err := s.ap.GetRemoteCertificates(services.HostCert, "") if err != nil { + log.Errorf(err.Error()) return nil, err } for _, c := range certs { @@ -201,6 +240,44 @@ func (s *server) keyAuth( return perms, nil } +func (s *server) hangoutKeyAuth( + conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + cid := fmt.Sprintf( + "reversetunnelconn(%v->%v, user=%v)", conn.RemoteAddr(), + conn.LocalAddr(), conn.User()) + + log.Infof("%v auth attempt with key %v", cid, key.Type()) + + _, ok := key.(*ssh.Certificate) + if !ok { + log.Warningf("conn(%v->%v, user=%v) ERROR: Server doesn't support provided key type", + conn.RemoteAddr(), conn.LocalAddr(), conn.User()) + return nil, trace.Errorf("ERROR: Server doesn't support provided key type") + } + //teleportUser := cert.Permissions.Extensions[utils.CertExtensionUser] + + _, err := s.certChecker.Authenticate(conn, key) + if err != nil { + log.Warningf("conn(%v->%v, user=%v) ERROR: Failed to authorize user %v, err: %v", + conn.RemoteAddr(), conn.LocalAddr(), conn.User(), conn.User(), err) + return nil, err + } + + if err := s.certChecker.CheckCert(conn.User(), key.(*ssh.Certificate)); err != nil { + log.Warningf("conn(%v->%v, user=%v) ERROR: Failed to authorize user %v, err: %v", + conn.RemoteAddr(), conn.LocalAddr(), conn.User(), conn.User(), err) + return nil, trace.Wrap(err) + } + + perms := &ssh.Permissions{ + Extensions: map[string]string{ + ExtHost: conn.User(), + }, + } + + return perms, nil +} + func (s *server) upsertSite(c ssh.Conn) (*remoteSite, error) { s.Lock() defer s.Unlock() @@ -316,7 +393,7 @@ func (s *remoteSite) init(c ssh.Conn) error { log.Errorf("remoteSite:authProxy %v", err) return nil, err } - return newChConn(s.conn, ch), nil + return utils.NewChConn(s.conn, ch), nil }, } clt, err := auth.NewClient( @@ -374,7 +451,7 @@ func (s *remoteSite) ConnectToServer(server, user string, auth []ssh.AuthMethod) if !dialed { return nil, trace.Errorf("remote server %v is not available", server) } - transportConn := newChConn(s.conn, ch) + transportConn := utils.NewChConn(s.conn, ch) conn, chans, reqs, err := ssh.NewClientConn( transportConn, server, &ssh.ClientConfig{ @@ -419,7 +496,7 @@ func (s *remoteSite) DialServer(server string) (net.Conn, error) { if !dialed { return nil, trace.Errorf("remote server %v is not available", server) } - return newChConn(s.conn, ch), nil + return utils.NewChConn(s.conn, ch), nil } func (s *remoteSite) GetServers() ([]services.Server, error) { @@ -434,7 +511,7 @@ func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) { log.Errorf("remoteSite:authProxy %v", err) return nil, err } - return newChConn(s.conn, ch), nil + return utils.NewChConn(s.conn, ch), nil }, } @@ -449,36 +526,4 @@ func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) { fwd.ServeHTTP(w, r) } -func newChConn(conn ssh.Conn, ch ssh.Channel) *chConn { - c := &chConn{} - c.Channel = ch - c.conn = conn - return c -} - -type chConn struct { - ssh.Channel - conn ssh.Conn -} - -func (c *chConn) LocalAddr() net.Addr { - return c.conn.LocalAddr() -} - -func (c *chConn) RemoteAddr() net.Addr { - return c.conn.RemoteAddr() -} - -func (c *chConn) SetDeadline(t time.Time) error { - return nil -} - -func (c *chConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (c *chConn) SetWriteDeadline(t time.Time) error { - return nil -} - const ExtHost = "host@teleport" diff --git a/lib/service/cfg.go b/lib/service/cfg.go index cb185d868a2..ace0d63d5a2 100644 --- a/lib/service/cfg.go +++ b/lib/service/cfg.go @@ -90,6 +90,11 @@ type ProxyConfig struct { // ReverseTunnelListenAddr is address where reverse tunnel dialers connect to ReverseTunnelListenAddr utils.NetAddr `yaml:"reverse_tunnel_listen_addr" env:"TELEPORT_PROXY_REVERSE_TUNNEL_LISTEN_ADDR"` + HangoutsEnabled bool `yaml:"hangouts_enabled" env:"TELEPORT_PROXY_HANGOUTS_ENABLED` + + // HangoutListenAddr is address where hangout reverse tunnel dialers connect to + HangoutsListenAddr utils.NetAddr `yaml:"hangouts_listen_addr" env:"TELEPORT_PROXY_HANGOUTS_LISTEN_ADDR"` + // WebAddr is address for web portal of the proxy WebAddr utils.NetAddr `yaml:"web_addr" env:"TELEPORT_PROXY_WEB_ADDR"` diff --git a/lib/service/service.go b/lib/service/service.go index 17ba935223c..269620a3291 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -405,13 +405,32 @@ func initProxyEndpoint(supervisor Supervisor, cfg Config) error { return trace.Wrap(err) } + var hsrv reversetunnel.Server + if cfg.Proxy.HangoutsEnabled { + hsrv, err = reversetunnel.NewHangoutServer( + cfg.Proxy.HangoutsListenAddr, + []ssh.Signer{signer}, + client, + reverseTunnelLimiter, + ) + if err != nil { + return trace.Wrap(err) + } + + } + + options := []srv.ServerOption{srv.SetProxyMode(tsrv)} + if cfg.Proxy.HangoutsEnabled { + options = append(options, srv.EnableHangouts(hsrv)) + } + SSHProxy, err := srv.New(cfg.Proxy.SSHAddr, cfg.Hostname, []ssh.Signer{signer}, client, proxyLimiter, cfg.DataDir, - srv.SetProxyMode(tsrv), + options..., ) if err != nil { return trace.Wrap(err) @@ -429,6 +448,20 @@ func initProxyEndpoint(supervisor Supervisor, cfg Config) error { return nil }) + if cfg.Proxy.HangoutsEnabled { + // register SSH reverse tunnel server that accepts connections + // from remote teleport nodes + supervisor.RegisterFunc(func() error { + log.Infof("[PROXY] hangouts listening server starting on %v", + cfg.Proxy.HangoutsListenAddr) + if err := hsrv.Start(); err != nil { + return trace.Wrap(err) + } + hsrv.Wait() + return nil + }) + } + // Register web proxy server supervisor.RegisterFunc(func() error { log.Infof("[PROXY] teleport web proxy server starting on %v", diff --git a/lib/srv/hangouts.go b/lib/srv/hangouts.go new file mode 100644 index 00000000000..87c52ce9713 --- /dev/null +++ b/lib/srv/hangouts.go @@ -0,0 +1,102 @@ +package srv + +import ( + "fmt" + "io" + "net" + "strings" + "sync" + + "github.com/gravitational/teleport/lib/services" + + log "github.com/Sirupsen/logrus" + "github.com/gravitational/trace" + "golang.org/x/crypto/ssh" +) + +// hangoutsSubsys is an SSH subsystem for easy proxyneling through proxy server +// This subsystem creates a new TCP connection and connects ssh channel +// with this connection +type hangoutsSubsys struct { + srv *Server + host string + port string +} + +func parseHangoutsSubsys(name string, srv *Server) (*hangoutsSubsys, error) { + out := strings.Split(name, ":") + if len(out) != 3 { + return nil, trace.Errorf("invalid format for proxy request: '%v', expected 'proxy:host:port'", name) + } + return &hangoutsSubsys{ + srv: srv, + host: out[1], + port: out[2], + }, nil +} + +func (t *hangoutsSubsys) String() string { + return fmt.Sprintf("hangoutsSubsys(host=%v, port=%v)", t.host, t.port) +} + +func (t *hangoutsSubsys) execute(sconn *ssh.ServerConn, ch ssh.Channel, req *ssh.Request, ctx *ctx) error { + remoteSrv, err := t.srv.hangoutsTun.GetSite(t.host) + if err != nil { + return trace.Wrap(err) + } + + // find matching server in the list of servers for this site + servers, err := remoteSrv.GetServers() + if err != nil { + return trace.Wrap(err) + } + + serverAddr := fmt.Sprintf("%v:%v", t.host, t.port) + var server *services.Server + for i := range servers { + log.Infof("%v %v", servers[i].Addr, servers[i].Hostname) + ip, port, err := net.SplitHostPort(servers[i].Addr) + if err != nil { + return trace.Wrap(err) + } + // match either by hostname of ip, based on the match + if (t.host == ip || t.host == servers[i].Hostname) && port == t.port { + server = &servers[i] + break + } + } + if server == nil { + return trace.Errorf("server %v not found", serverAddr) + } + + // we must dial by server IP address because hostname + // may not be actually DNS resolvable + conn, err := remoteSrv.DialServer(server.Addr) + if err != nil { + return trace.Wrap(err) + } + + wg := &sync.WaitGroup{} + wg.Add(2) + + go func() { + defer wg.Done() + _, err := io.Copy(ch, conn) + if err != nil { + log.Errorf(err.Error()) + } + ch.Close() + }() + go func() { + defer wg.Done() + _, err := io.Copy(conn, ch) + if err != nil { + log.Errorf(err.Error()) + } + conn.Close() + }() + + wg.Wait() + + return nil +} diff --git a/lib/srv/srv.go b/lib/srv/srv.go index f0d821229c7..5a6d488a5c2 100644 --- a/lib/srv/srv.go +++ b/lib/srv/srv.go @@ -39,8 +39,8 @@ import ( "github.com/gravitational/teleport/lib/utils" "code.google.com/p/go-uuid/uuid" - "github.com/codahale/lunk" log "github.com/Sirupsen/logrus" + "github.com/codahale/lunk" "github.com/gravitational/trace" "golang.org/x/crypto/ssh" // Server implements SSH server that uses configuration backend and certificate-based authentication: @@ -68,8 +68,9 @@ type Server struct { certificatesCache *services.CAService - proxyMode bool - proxyTun reversetunnel.Server + proxyMode bool + proxyTun reversetunnel.Server + hangoutsTun reversetunnel.Server } type ServerOption func(s *Server) error @@ -110,6 +111,13 @@ func SetProxyMode(tsrv reversetunnel.Server) ServerOption { } } +func EnableHangouts(hangoutsTun reversetunnel.Server) ServerOption { + return func(s *Server) error { + s.hangoutsTun = hangoutsTun + return nil + } +} + func SetLabels(labels map[string]string, cmdLabels services.CommandLabels) ServerOption { return func(s *Server) error { @@ -147,6 +155,11 @@ func New(addr utils.NetAddr, hostname string, signers []ssh.Signer, return nil, err } } + + if (s.hangoutsTun != nil) && (!s.proxyMode) { + return nil, trace.Errorf("Hangout available only in proxy mode") + } + if s.elog == nil { s.elog = utils.NullEventLogger } @@ -417,7 +430,6 @@ func (s *Server) HandleRequest(r *ssh.Request) { func (s *Server) HandleNewChan(sconn *ssh.ServerConn, nch ssh.NewChannel) { channelType := nch.ChannelType() - if s.proxyMode { if channelType == "session" { // interactive sessions ch, requests, err := nch.Accept() diff --git a/lib/srv/subsystem.go b/lib/srv/subsystem.go index 5159fba0149..d17d6af2768 100644 --- a/lib/srv/subsystem.go +++ b/lib/srv/subsystem.go @@ -50,6 +50,9 @@ func parseSubsystemRequest(srv *Server, req *ssh.Request) (subsystem, error) { if srv.proxyMode && strings.HasPrefix(s.Name, "proxy:") { return parseProxySubsys(s.Name, srv) } + if srv.proxyMode && srv.hangoutsTun != nil && strings.HasPrefix(s.Name, "hangout:") { + return parseHangoutsSubsys(s.Name, srv) + } if srv.proxyMode && strings.HasPrefix(s.Name, "proxysites") { return parseProxySitesSubsys(s.Name, srv) } diff --git a/lib/utils/fakeconn.go b/lib/utils/fakeconn.go index 820abaabe0a..418b39294b1 100644 --- a/lib/utils/fakeconn.go +++ b/lib/utils/fakeconn.go @@ -20,6 +20,8 @@ import ( "io" "net" "time" + + "golang.org/x/crypto/ssh" ) // PipeNetConn implemetns net.Conn from io.Reader,io.Writer and io.Closer @@ -81,3 +83,35 @@ func SplitReaders(r1 io.Reader, r2 io.Reader) io.Reader { go io.Copy(writer, r2) return reader } + +func NewChConn(conn ssh.Conn, ch ssh.Channel) *chConn { + c := &chConn{} + c.Channel = ch + c.conn = conn + return c +} + +type chConn struct { + ssh.Channel + conn ssh.Conn +} + +func (c *chConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *chConn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +func (c *chConn) SetDeadline(t time.Time) error { + return nil +} + +func (c *chConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *chConn) SetWriteDeadline(t time.Time) error { + return nil +} diff --git a/lib/utils/utils.go b/lib/utils/utils.go index 95dd4a436a1..c341ba88ce1 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -20,10 +20,12 @@ import ( "fmt" "io" "io/ioutil" + "net" "os" "path/filepath" "github.com/gravitational/trace" + "golang.org/x/crypto/ssh" ) type HostKeyCallback func(hostname string, remote net.Addr, key ssh.PublicKey) error diff --git a/tool/tsh/tsh/cmd.go b/tool/tsh/tsh/cmd.go index 289bc5d666e..c306092a0b3 100644 --- a/tool/tsh/tsh/cmd.go +++ b/tool/tsh/tsh/cmd.go @@ -51,6 +51,9 @@ func RunTSH(app *kingpin.Application) error { scpIsDir := scp.Flag("recursively", "Source path is a directory").Short('r').Bool() scpPort := scp.Flag("port", "Remote server port").Short('P').String() + share := app.Command("share", "share") + join := app.Command("join", "join") + selectedCommand := kingpin.MustParse(app.Parse(os.Args[1:])) if (selectedCommand == getServers.FullCommand()) && (len(*proxy) == 0) { @@ -93,6 +96,10 @@ func RunTSH(app *kingpin.Application) error { case scp.FullCommand(): err = SCP(*proxy, *scpSource, *scpDest, *scpIsDir, *scpPort, authMethods, hostKeyCallback) + case share.FullCommand(): + err = Share("localhost:33009", "localhost:33010", "localhost:33011", authMethods, hostKeyCallback) + case join.FullCommand(): + err = Join(*proxy, "", authMethods, hostKeyCallback) } return err diff --git a/tool/tsh/tsh/tsh.go b/tool/tsh/tsh/tsh.go index 7eb9235e1fa..0372154218b 100644 --- a/tool/tsh/tsh/tsh.go +++ b/tool/tsh/tsh/tsh.go @@ -21,11 +21,13 @@ import ( "os" "os/exec" "os/signal" + "os/user" "strings" "sync" "syscall" "time" + "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/hangout" "github.com/gravitational/teleport/lib/services" @@ -34,7 +36,6 @@ import ( log "github.com/Sirupsen/logrus" "github.com/gravitational/trace" "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" ) func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error { @@ -79,6 +80,11 @@ func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMetho return trace.Wrap(err) } } + return shell(c, address) +} + +func shell(c *client.NodeClient, address string) error { + defer c.Close() // disable input buffering @@ -272,14 +278,65 @@ func SCP(proxyAddress, source, dest string, isDir bool, port string, authMethods return nil } -func Share(proxyAddress, nodeListeningAddress, authListeningAddress, - authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) { - hangoutServer := hangout.New(proxyAddress, nodeListeningAddress, - authListeningAddress, authMethods, hostKeyCallback) +func Share(hangoutProxyAddress, nodeListeningAddress, authListeningAddress string, + authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error { + hangoutServer, err := hangout.New(hangoutProxyAddress, nodeListeningAddress, + authListeningAddress, false, authMethods, hostKeyCallback) - return SSH(nodeListeningAddress, "", "", "", []ssh.AuthMethod{h.AuthMethod}, h.HostKeyCallback) + if err != nil { + return trace.Wrap(err) + } + + u, err := user.Current() + if err != nil { + return trace.Wrap(err) + } + + return SSH(u.Username+"@"+nodeListeningAddress, "", "", "", []ssh.AuthMethod{hangoutServer.ClientAuthMethod}, hangoutServer.HostKeyCallback) } -func Join(proxyAddress, nodeAddress, authAddress, userPassword string) { +func Join(proxyAddress, hangoutToken string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error { + hangoutServer, err := hangout.New("localhost:33009", "localhost:33010", + "localhost:33011", false, authMethods, hostKeyCallback) + if err != nil { + return trace.Wrap(err) + } + + time.Sleep(time.Second * 1) + + hangoutToken = hangoutServer.Token + hInfo, err := hangout.UnmarshalHangoutInfo(hangoutToken) + + proxy, err := client.ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, "123") + if err != nil { + return trace.Wrap(err) + } + + hangoutAuthMethod, err := auth.NewTokenAuth(hangout.HangoutUser, hInfo.HangoutPassword) + if err != nil { + return trace.Wrap(err) + } + + authConn, err := proxy.ConnectToHangout(hInfo.HangoutID+":"+hInfo.AuthPort, hangoutAuthMethod, nil, hangout.HangoutUser) + if err != nil { + return trace.Wrap(err) + } + + authClient, err := auth.NewClientFromSSHClient(authConn.Client) + if err != nil { + return trace.Wrap(err) + } + + nodeAuthMethod, nodeHostKeyCallback, err := hangout.Authorize(authClient, hInfo.HangoutPassword[0:100]) + if err != nil { + return trace.Wrap(err) + } + + nodeConn, err := proxy.ConnectToHangout(hInfo.HangoutID+":"+hInfo.NodePort, []ssh.AuthMethod{nodeAuthMethod}, nodeHostKeyCallback, hInfo.OSUser) + if err != nil { + return trace.Wrap(err) + } + + return shell(nodeConn, hInfo.HangoutID) } diff --git a/tool/tsh/tsh/tsh_test.go b/tool/tsh/tsh/tsh_test.go index 737bd51facb..036a91e1d59 100644 --- a/tool/tsh/tsh/tsh_test.go +++ b/tool/tsh/tsh/tsh_test.go @@ -32,6 +32,7 @@ import ( "github.com/gravitational/teleport/lib/backend/boltbk" "github.com/gravitational/teleport/lib/backend/encryptedbk" "github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor" + "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/events/boltlog" "github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/recorder/boltrec"