merged from alex/sharing

This commit is contained in:
Alex Lyulkov 2016-02-17 22:58:28 +03:00
commit bcb6411a7b
34 changed files with 1718 additions and 200 deletions

View file

@ -67,3 +67,5 @@ proxy:
- period: 1s - period: 1s
average: 500 average: 500
burst: 300 burst: 300
hangouts_enabled: true
hangouts_listen_addr: tcp://localhost:33009

View file

@ -145,7 +145,7 @@ func (s *AuthServer) GenerateHostCert(
return s.Authority.GenerateHostCert(hk.PrivateKey, key, id, hostname, role, ttl) return s.Authority.GenerateHostCert(hk.PrivateKey, key, id, hostname, role, ttl)
} }
// GenerateHostCert generates user certificate, it takes pkey as a signing // GenerateUserCert generates user certificate, it takes pkey as a signing
// private key (user certificate authority) // private key (user certificate authority)
func (s *AuthServer) GenerateUserCert( func (s *AuthServer) GenerateUserCert(
key []byte, id, username string, ttl time.Duration) ([]byte, error) { key []byte, id, username string, ttl time.Duration) ([]byte, error) {

View file

@ -188,6 +188,13 @@ func (a *AuthWithRoles) GetServers() ([]services.Server, error) {
return a.authServer.GetServers() return a.authServer.GetServers()
} }
} }
func (a *AuthWithRoles) GetAuthServers() ([]services.Server, error) {
if err := a.permChecker.HasPermission(a.role, ActionGetAuthServers); err != nil {
return nil, err
} else {
return a.authServer.GetAuthServers()
}
}
func (a *AuthWithRoles) UpsertWebTun(wt services.WebTun, ttl time.Duration) error { func (a *AuthWithRoles) UpsertWebTun(wt services.WebTun, ttl time.Duration) error {
if err := a.permChecker.HasPermission(a.role, ActionUpsertWebTun); err != nil { if err := a.permChecker.HasPermission(a.role, ActionUpsertWebTun); err != nil {
return err return err

View file

@ -352,6 +352,19 @@ func (c *Client) GetServers() ([]services.Server, error) {
return re.Servers, nil return re.Servers, nil
} }
// GetServers returns the list of auth servers registered in the cluster.
func (c *Client) GetAuthServers() ([]services.Server, error) {
out, err := c.Get(c.Endpoint("authservers"), url.Values{})
if err != nil {
return nil, err
}
var re *serversResponse
if err := json.Unmarshal(out.Bytes(), &re); err != nil {
return nil, err
}
return re.Servers, nil
}
// UpsertWebTun creates a persistent SSH tunnel to the specified web target // UpsertWebTun creates a persistent SSH tunnel to the specified web target
// server that is valid for ttl period. // server that is valid for ttl period.
// See services.WebTun documentation for details // See services.WebTun documentation for details
@ -910,6 +923,7 @@ type ClientI interface {
GetChunkReader(id string) (recorder.ChunkReadCloser, error) GetChunkReader(id string) (recorder.ChunkReadCloser, error)
UpsertServer(s services.Server, ttl time.Duration) error UpsertServer(s services.Server, ttl time.Duration) error
GetServers() ([]services.Server, error) GetServers() ([]services.Server, error)
GetAuthServers() ([]services.Server, error)
UpsertWebTun(wt services.WebTun, ttl time.Duration) error UpsertWebTun(wt services.WebTun, ttl time.Duration) error
GetWebTuns() ([]services.WebTun, error) GetWebTuns() ([]services.WebTun, error)
GetWebTun(prefix string) (*services.WebTun, error) GetWebTun(prefix string) (*services.WebTun, error)

View file

@ -33,8 +33,10 @@ func NewStandardPermissions() PermissionChecker {
sp.permissions = make(map[string](map[string]bool)) sp.permissions = make(map[string](map[string]bool))
sp.permissions[RoleUser] = map[string]bool{ sp.permissions[RoleUser] = map[string]bool{
ActionSignIn: true, ActionSignIn: true,
ActionGenerateUserCert: true, ActionGenerateUserCert: true,
ActionGetTrustedCertificates: true,
ActionGetRemoteCertificates: true,
} }
sp.permissions[RoleProvisionToken] = map[string]bool{ sp.permissions[RoleProvisionToken] = map[string]bool{
@ -52,6 +54,7 @@ func NewStandardPermissions() PermissionChecker {
ActionUserMappingExists: true, ActionUserMappingExists: true,
ActionGetUserKeys: true, ActionGetUserKeys: true,
ActionGetServers: true, ActionGetServers: true,
ActionGetAuthServers: true,
ActionGetHostCertificateAuthority: true, ActionGetHostCertificateAuthority: true,
ActionUpsertParty: true, ActionUpsertParty: true,
ActionLogEntry: true, ActionLogEntry: true,
@ -71,6 +74,59 @@ func NewStandardPermissions() PermissionChecker {
return &sp 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[RoleHangoutRemoteUser] = map[string]bool{
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,
ActionUpsertSession: true,
ActionUpsertRemoteCertificate: 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 { func (sp *standardPermissions) HasPermission(role, action string) error {
if role == RoleAdmin { if role == RoleAdmin {
return nil return nil
@ -109,16 +165,23 @@ var StandardRoles = []string{
RoleSignup, RoleSignup,
} }
var HangoutRoles = []string{
RoleAdmin,
RoleProvisionToken,
RoleHangoutRemoteUser,
}
const ( const (
PermissionRole = "role" PermissionRole = "role"
RoleAuth = "Auth" RoleAuth = "Auth"
RoleUser = "User" RoleUser = "User"
RoleWeb = "Web" RoleWeb = "Web"
RoleNode = "Node" RoleNode = "Node"
RoleAdmin = "Admin" RoleAdmin = "Admin"
RoleProvisionToken = "ProvisionToken" RoleProvisionToken = "ProvisionToken"
RoleSignup = "Signup" RoleSignup = "Signup"
RoleHangoutRemoteUser = "HangoutRemoteUser"
ActionGetSessions = "GetSession" ActionGetSessions = "GetSession"
ActionGetSession = "GetSession" ActionGetSession = "GetSession"
@ -139,6 +202,7 @@ const (
ActionGetChunkReader = "GetChunkReader" ActionGetChunkReader = "GetChunkReader"
ActionUpsertServer = "UpsertServer" ActionUpsertServer = "UpsertServer"
ActionGetServers = "GetServers" ActionGetServers = "GetServers"
ActionGetAuthServers = "GetAuthServers"
ActionUpsertWebTun = "UpsertWebTun" ActionUpsertWebTun = "UpsertWebTun"
ActionGetWebTuns = "GetWebTuns" ActionGetWebTuns = "GetWebTuns"
ActionGetWebTun = "GetWebTun" ActionGetWebTun = "GetWebTun"

View file

@ -108,6 +108,7 @@ func NewAPIServer(a *AuthWithRoles) *APIServer {
// Servers and presence heartbeat // Servers and presence heartbeat
srv.POST("/v1/servers", srv.upsertServer) srv.POST("/v1/servers", srv.upsertServer)
srv.GET("/v1/servers", srv.getServers) srv.GET("/v1/servers", srv.getServers)
srv.GET("/v1/authservers", srv.getAuthServers)
// Tokens // Tokens
srv.POST("/v1/tokens", srv.generateToken) srv.POST("/v1/tokens", srv.generateToken)
@ -239,6 +240,15 @@ func (s *APIServer) getServers(w http.ResponseWriter, r *http.Request, p httprou
reply(w, http.StatusOK, serversResponse{Servers: servers}) reply(w, http.StatusOK, serversResponse{Servers: servers})
} }
func (s *APIServer) getAuthServers(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
servers, err := s.a.GetAuthServers()
if err != nil {
replyErr(w, err)
return
}
reply(w, http.StatusOK, serversResponse{Servers: servers})
}
func (s *APIServer) upsertWebTun(w http.ResponseWriter, r *http.Request, p httprouter.Params) { func (s *APIServer) upsertWebTun(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var prefix, targetAddr, proxyAddr string var prefix, targetAddr, proxyAddr string
var ttl time.Duration var ttl time.Duration

View file

@ -24,6 +24,7 @@ import (
"sync" "sync"
"github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils"
@ -36,16 +37,26 @@ import (
) )
type TunServer struct { type TunServer struct {
certChecker ssh.CertChecker certChecker ssh.CertChecker
a *AuthServer userCertChecker ssh.CertChecker
l net.Listener userCertAllowed bool
srv *sshutils.Server a *AuthServer
hostSigner ssh.Signer l net.Listener
apiServer *APIWithRoles srv *sshutils.Server
hostSigner ssh.Signer
apiServer *APIWithRoles
} }
type ServerOption func(s *TunServer) error type ServerOption func(s *TunServer) error
func EnableUserCertificates() ServerOption {
return func(s *TunServer) error {
s.userCertAllowed = true
s.userCertChecker = ssh.CertChecker{IsAuthority: s.isUserCertAuthority}
return nil
}
}
// New returns an unstarted server // New returns an unstarted server
func NewTunServer(addr utils.NetAddr, hostSigners []ssh.Signer, func NewTunServer(addr utils.NetAddr, hostSigners []ssh.Signer,
apiServer *APIWithRoles, a *AuthServer, apiServer *APIWithRoles, a *AuthServer,
@ -53,9 +64,11 @@ func NewTunServer(addr utils.NetAddr, hostSigners []ssh.Signer,
opts ...ServerOption) (*TunServer, error) { opts ...ServerOption) (*TunServer, error) {
srv := &TunServer{ srv := &TunServer{
a: a, a: a,
apiServer: apiServer, apiServer: apiServer,
userCertAllowed: false,
} }
for _, o := range opts { for _, o := range opts {
if err := o(srv); err != nil { if err := o(srv); err != nil {
return nil, err return nil, err
@ -157,6 +170,20 @@ func (s *TunServer) isAuthority(auth ssh.PublicKey) bool {
return true return true
} }
// isAuthority is called during checking the client key, to see if the signing
// key is the real CA authority key.
func (s *TunServer) isUserCertAuthority(auth ssh.PublicKey) bool {
_, found, err := s.a.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 *TunServer) haveExt(sconn *ssh.ServerConn, ext ...string) bool { func (s *TunServer) haveExt(sconn *ssh.ServerConn, ext ...string) bool {
if sconn.Permissions == nil { if sconn.Permissions == nil {
return false return false
@ -244,6 +271,30 @@ func (s *TunServer) keyAuth(
log.Infof("%v auth attempt with key %v", cid, key.Type()) log.Infof("%v auth attempt with key %v", cid, key.Type())
cert, ok := key.(*ssh.Certificate)
if !ok {
return nil, trace.Errorf("ERROR: Server doesn't support provided key type")
}
if cert.CertType == ssh.UserCert {
if !s.userCertAllowed {
return nil, trace.Errorf("User certificates are not allowed")
}
_, err := s.userCertChecker.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
}
perms := &ssh.Permissions{
Extensions: map[string]string{
ExtHost: conn.User(),
"role": RoleHangoutRemoteUser,
},
}
return perms, nil
}
err := s.certChecker.CheckHostKey(conn.User(), conn.RemoteAddr(), key) err := s.certChecker.CheckHostKey(conn.User(), conn.RemoteAddr(), key)
if err != nil { if err != nil {
log.Warningf("conn(%v->%v, user=%v) ERROR: failed auth user %v, err: %v", log.Warningf("conn(%v->%v, user=%v) ERROR: failed auth user %v, err: %v",
@ -251,11 +302,6 @@ func (s *TunServer) keyAuth(
return nil, err return nil, err
} }
cert, ok := key.(*ssh.Certificate)
if !ok {
return nil, trace.Wrap(err)
}
if err := s.certChecker.CheckCert(conn.User(), cert); err != nil { if err := s.certChecker.CheckCert(conn.User(), cert); err != nil {
log.Warningf("conn(%v->%v, user=%v) ERROR: Failed to authorize user %v, err: %v", log.Warningf("conn(%v->%v, user=%v) ERROR: Failed to authorize user %v, err: %v",
conn.RemoteAddr(), conn.LocalAddr(), conn.User(), conn.User(), err) conn.RemoteAddr(), conn.LocalAddr(), conn.User(), conn.User(), err)
@ -521,6 +567,22 @@ func (t *TunDialer) Dial(network, address string) (net.Conn, error) {
} }
} }
func NewClientFromSSHClient(sshClient *ssh.Client) (*Client, error) {
tr := &http.Transport{
Dial: sshClient.Dial,
}
clt, err := NewClient(
"http://stub:0",
roundtrip.HTTPClient(&http.Client{
Transport: tr,
}))
if err != nil {
return nil, trace.Wrap(err)
}
return clt, nil
}
const ( const (
ReqWebSessionAgent = "web-session-agent@teleport" ReqWebSessionAgent = "web-session-agent@teleport"
ReqProvision = "provision@teleport" ReqProvision = "provision@teleport"

View file

@ -36,6 +36,8 @@ import (
"time" "time"
"github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/sshutils/scp" "github.com/gravitational/teleport/lib/sshutils/scp"
"github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils"
@ -60,18 +62,20 @@ type NodeClient struct {
// ConnectToProxy returns connected and authenticated ProxyClient // ConnectToProxy returns connected and authenticated ProxyClient
func ConnectToProxy(proxyAddress string, authMethods []ssh.AuthMethod, func ConnectToProxy(proxyAddress string, authMethods []ssh.AuthMethod,
user string) (*ProxyClient, error) { hostKeyCallback utils.HostKeyCallback, user string) (*ProxyClient, error) {
e := trace.Errorf("no authMethods were provided") e := trace.Errorf("no authMethods were provided")
for _, authMethod := range authMethods { for _, authMethod := range authMethods {
sshConfig := &ssh.ClientConfig{ sshConfig := &ssh.ClientConfig{
User: user, User: user,
Auth: []ssh.AuthMethod{authMethod}, Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: hostKeyCallback,
} }
proxyClient, err := ssh.Dial("tcp", proxyAddress, sshConfig) proxyClient, err := ssh.Dial("tcp", proxyAddress, sshConfig)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "handshake failed") { if strings.Contains(err.Error(), "handshake failed") ||
strings.Contains(err.Error(), "CheckHostSigners") {
e = trace.Wrap(err) e = trace.Wrap(err)
continue continue
} }
@ -166,7 +170,8 @@ func (proxy *ProxyClient) FindServers(labelName string,
// ConnectToNode connects to the ssh server via Proxy. // ConnectToNode connects to the ssh server via Proxy.
// It returns connected and authenticated NodeClient // It returns connected and authenticated NodeClient
func (proxy *ProxyClient) ConnectToNode(nodeAddress string, authMethods []ssh.AuthMethod, user string) (*NodeClient, error) { func (proxy *ProxyClient) ConnectToNode(nodeAddress string, authMethods []ssh.AuthMethod,
hostKeyCallback utils.HostKeyCallback, user string) (*NodeClient, error) {
if len(authMethods) == 0 { if len(authMethods) == 0 {
return nil, trace.Errorf("no authMethods were provided") return nil, trace.Errorf("no authMethods were provided")
} }
@ -217,14 +222,127 @@ func (proxy *ProxyClient) ConnectToNode(nodeAddress string, authMethods []ssh.Au
) )
sshConfig := &ssh.ClientConfig{ sshConfig := &ssh.ClientConfig{
User: user, User: user,
Auth: []ssh.AuthMethod{authMethod}, Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: hostKeyCallback,
} }
conn, chans, reqs, err := ssh.NewClientConn(pipeNetConn, conn, chans, reqs, err := ssh.NewClientConn(pipeNetConn,
nodeAddress, sshConfig) nodeAddress, sshConfig)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "handshake failed") { 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
}
// ConnectToNode connects to the ssh server via Proxy.
// It returns connected and authenticated NodeClient
func (proxy *ProxyClient) ConnectToHangout(nodeAddress string,
authMethods []ssh.AuthMethod) (*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,
)
// reading target server host certificate
buf := make([]byte, 1000000)
n, err := pipeNetConn.Read(buf)
if err != nil {
return nil, trace.Wrap(err)
}
var endpointInfo srv.HangoutEndpointInfo
err = json.Unmarshal(buf[:n], &endpointInfo)
if err != nil {
return nil, trace.Wrap(err)
}
hostKeyCallback := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
cert, ok := key.(*ssh.Certificate)
if !ok {
return trace.Errorf("expected certificate")
}
pk, _, _, _, err := ssh.ParseAuthorizedKey(endpointInfo.HostKey.PublicKey)
if err != nil {
return trace.Errorf("error parsing key: %v", err)
}
if sshutils.KeysEqual(pk, cert.SignatureKey) {
return nil
}
return trace.Errorf("remote host key is not valid")
}
sshConfig := &ssh.ClientConfig{
User: endpointInfo.OSUser,
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) e = trace.Wrap(err)
proxySession.Close() proxySession.Close()
continue continue
@ -247,22 +365,25 @@ func (proxy *ProxyClient) Close() error {
} }
// ConnectToNode returns connected and authenticated NodeClient // ConnectToNode returns connected and authenticated NodeClient
func ConnectToNode(optionalProxy *ProxyClient, nodeAddress string, authMethods []ssh.AuthMethod, user string) (*NodeClient, error) { func ConnectToNode(optionalProxy *ProxyClient, nodeAddress string, authMethods []ssh.AuthMethod,
hostKeyCallback utils.HostKeyCallback, user string) (*NodeClient, error) {
if optionalProxy != nil { if optionalProxy != nil {
return optionalProxy.ConnectToNode(nodeAddress, authMethods, user) return optionalProxy.ConnectToNode(nodeAddress, authMethods, hostKeyCallback, user)
} }
e := trace.Errorf("no authMethods were provided") e := trace.Errorf("no authMethods were provided")
for _, authMethod := range authMethods { for _, authMethod := range authMethods {
sshConfig := &ssh.ClientConfig{ sshConfig := &ssh.ClientConfig{
User: user, User: user,
Auth: []ssh.AuthMethod{authMethod}, Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: hostKeyCallback,
} }
client, err := ssh.Dial("tcp", nodeAddress, sshConfig) client, err := ssh.Dial("tcp", nodeAddress, sshConfig)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "handshake failed") { if strings.Contains(err.Error(), "handshake failed") ||
strings.Contains(err.Error(), "CheckHostSigners") {
e = trace.Wrap(err) e = trace.Wrap(err)
continue continue
} }
@ -276,12 +397,19 @@ func ConnectToNode(optionalProxy *ProxyClient, nodeAddress string, authMethods [
} }
// Shell returns remote shell as io.ReadWriterCloser object // Shell returns remote shell as io.ReadWriterCloser object
func (client *NodeClient) Shell(width, height int) (io.ReadWriteCloser, error) { func (client *NodeClient) Shell(width, height int, sessionID string) (io.ReadWriteCloser, error) {
session, err := client.Client.NewSession() session, err := client.Client.NewSession()
if err != nil { if err != nil {
return nil, trace.Wrap(err) return nil, trace.Wrap(err)
} }
if len(sessionID) > 0 {
err = session.Setenv(sshutils.SessionEnvVar, sessionID)
if err != nil {
return nil, trace.Wrap(err)
}
}
terminalModes := ssh.TerminalModes{} terminalModes := ssh.TerminalModes{}
err = session.RequestPty("xterm", height, width, terminalModes) err = session.RequestPty("xterm", height, width, terminalModes)

View file

@ -77,6 +77,7 @@ var _ = Suite(&ClientSuite{})
func (s *ClientSuite) SetUpSuite(c *C) { func (s *ClientSuite) SetUpSuite(c *C) {
utils.InitLoggerCLI() utils.InitLoggerCLI()
KeysDir = c.MkDir()
key, err := secret.NewKey() key, err := secret.NewKey()
c.Assert(err, IsNil) c.Assert(err, IsNil)
scrt, err := secret.New(&secret.Config{KeyBytes: key}) scrt, err := secret.New(&secret.Config{KeyBytes: key})
@ -253,13 +254,37 @@ func (s *ClientSuite) SetUpSuite(c *C) {
err = s.teleagent.Login("http://"+s.webAddress, s.user, string(s.pass), s.otp.OTP(), time.Minute) err = s.teleagent.Login("http://"+s.webAddress, s.user, string(s.pass), s.otp.OTP(), time.Minute)
c.Assert(err, IsNil) c.Assert(err, IsNil)
_, err = ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, NotNil)
passwordCallback := func() (string, string, error) {
return string(s.pass), s.otp.OTP(), nil
}
_, hostChecker := NewWebAuth(
agent.NewKeyring(),
s.user,
passwordCallback,
"http://"+s.webAddress,
time.Hour,
)
_, err = ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, hostChecker, s.user)
c.Assert(err, IsNil)
_, err = ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil)
// "Command labels will be calculated only on the second heartbeat" // "Command labels will be calculated only on the second heartbeat"
time.Sleep(time.Millisecond * 3100) time.Sleep(time.Millisecond * 3100)
} }
func (s *ClientSuite) TestRunCommand(c *C) { func (s *ClientSuite) TestRunCommand(c *C) {
nodeClient, err := ConnectToNode(nil, s.srvAddress, nodeClient, err := ConnectToNode(nil, s.srvAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
buf := bytes.Buffer{} buf := bytes.Buffer{}
@ -270,11 +295,11 @@ func (s *ClientSuite) TestRunCommand(c *C) {
func (s *ClientSuite) TestConnectViaProxy(c *C) { func (s *ClientSuite) TestConnectViaProxy(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress, proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
nodeClient, err := proxyClient.ConnectToNode(s.srvAddress, nodeClient, err := proxyClient.ConnectToNode(s.srvAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
buf := bytes.Buffer{} buf := bytes.Buffer{}
@ -296,23 +321,24 @@ func (s *ClientSuite) TestConnectUsingSeveralAgents(c *C) {
[]ssh.AuthMethod{ []ssh.AuthMethod{
AuthMethodFromAgent(agent1), AuthMethodFromAgent(agent1),
AuthMethodFromAgent(agent2), AuthMethodFromAgent(agent2),
}, s.user) }, CheckHostSignerFromCache, s.user)
c.Assert(err, NotNil) c.Assert(err, NotNil)
webAuth, hostChecker := NewWebAuth(
agent2,
s.user,
passwordCallback,
"http://"+s.webAddress,
time.Hour,
)
proxyClient, err := ConnectToProxy( proxyClient, err := ConnectToProxy(
s.proxyAddress, s.proxyAddress,
[]ssh.AuthMethod{ []ssh.AuthMethod{
AuthMethodFromAgent(agent1), AuthMethodFromAgent(agent1),
AuthMethodFromAgent(agent2), AuthMethodFromAgent(agent2),
NewWebAuth( webAuth,
agent2,
s.user,
passwordCallback,
"http://"+s.webAddress,
time.Hour,
),
}, },
s.user) hostChecker, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
nodeClient, err := proxyClient.ConnectToNode( nodeClient, err := proxyClient.ConnectToNode(
@ -321,7 +347,7 @@ func (s *ClientSuite) TestConnectUsingSeveralAgents(c *C) {
AuthMethodFromAgent(agent1), AuthMethodFromAgent(agent1),
AuthMethodFromAgent(agent2), AuthMethodFromAgent(agent2),
}, },
s.user) CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
buf := bytes.Buffer{} buf := bytes.Buffer{}
@ -336,7 +362,7 @@ func (s *ClientSuite) TestConnectUsingSeveralAgents(c *C) {
AuthMethodFromAgent(agent1), AuthMethodFromAgent(agent1),
AuthMethodFromAgent(agent2), AuthMethodFromAgent(agent2),
}, },
s.user) CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
buf = bytes.Buffer{} buf = bytes.Buffer{}
@ -347,14 +373,14 @@ func (s *ClientSuite) TestConnectUsingSeveralAgents(c *C) {
func (s *ClientSuite) TestShell(c *C) { func (s *ClientSuite) TestShell(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress, proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
nodeClient, err := proxyClient.ConnectToNode(s.srvAddress, nodeClient, err := proxyClient.ConnectToNode(s.srvAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
shell, err := nodeClient.Shell(100, 100) shell, err := nodeClient.Shell(100, 100, "")
c.Assert(err, IsNil) c.Assert(err, IsNil)
out := make([]byte, 100) out := make([]byte, 100)
@ -385,7 +411,7 @@ func (s *ClientSuite) TestShell(c *C) {
func (s *ClientSuite) TestGetServer(c *C) { func (s *ClientSuite) TestGetServer(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress, proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
server1Info := services.Server{ server1Info := services.Server{
@ -475,11 +501,11 @@ func (s *ClientSuite) TestGetServer(c *C) {
func (s *ClientSuite) TestUploadFile(c *C) { func (s *ClientSuite) TestUploadFile(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress, proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
nodeClient, err := proxyClient.ConnectToNode(s.srvAddress, nodeClient, err := proxyClient.ConnectToNode(s.srvAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
dir := c.MkDir() dir := c.MkDir()
@ -499,11 +525,11 @@ func (s *ClientSuite) TestUploadFile(c *C) {
func (s *ClientSuite) TestDownloadFile(c *C) { func (s *ClientSuite) TestDownloadFile(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress, proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
nodeClient, err := proxyClient.ConnectToNode(s.srvAddress, nodeClient, err := proxyClient.ConnectToNode(s.srvAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
dir := c.MkDir() dir := c.MkDir()
@ -523,7 +549,7 @@ func (s *ClientSuite) TestDownloadFile(c *C) {
func (s *ClientSuite) TestUploadDir(c *C) { func (s *ClientSuite) TestUploadDir(c *C) {
nodeClient, err := ConnectToNode(nil, s.srvAddress, nodeClient, err := ConnectToNode(nil, s.srvAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
dir1 := c.MkDir() dir1 := c.MkDir()
@ -553,7 +579,7 @@ func (s *ClientSuite) TestUploadDir(c *C) {
func (s *ClientSuite) TestDownloadDir(c *C) { func (s *ClientSuite) TestDownloadDir(c *C) {
nodeClient, err := ConnectToNode(nil, s.srvAddress, nodeClient, err := ConnectToNode(nil, s.srvAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user) []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil) c.Assert(err, IsNil)
dir1 := c.MkDir() dir1 := c.MkDir()
@ -618,11 +644,11 @@ func (s *ClientSuite) TestHOTPMock(c *C) {
} }
func (s *ClientSuite) TestParseTargetObject(c *C) { func (s *ClientSuite) TestParseTargetObject(c *C) {
addresses, err := ParseTargetServers(s.srv2Address, s.user, s.proxyAddress, []ssh.AuthMethod{s.teleagent.AuthMethod()}) addresses, err := ParseTargetServers(s.srv2Address, s.user, s.proxyAddress, []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache)
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(addresses, DeepEquals, []string{s.srv2Address}) c.Assert(addresses, DeepEquals, []string{s.srv2Address})
addresses, err = ParseTargetServers("_label1:val.*", s.user, s.proxyAddress, []ssh.AuthMethod{s.teleagent.AuthMethod()}) addresses, err = ParseTargetServers("_label1:val.*", s.user, s.proxyAddress, []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache)
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(len(addresses), Equals, 2) c.Assert(len(addresses), Equals, 2)
if addresses[0] == s.srvAddress { if addresses[0] == s.srvAddress {
@ -631,7 +657,7 @@ func (s *ClientSuite) TestParseTargetObject(c *C) {
c.Assert(addresses, DeepEquals, []string{s.srv2Address, s.srvAddress}) c.Assert(addresses, DeepEquals, []string{s.srv2Address, s.srvAddress})
} }
addresses, err = ParseTargetServers("_label2:value2*", s.user, s.proxyAddress, []ssh.AuthMethod{s.teleagent.AuthMethod()}) addresses, err = ParseTargetServers("_label2:value2*", s.user, s.proxyAddress, []ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache)
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(addresses, DeepEquals, []string{s.srvAddress}) c.Assert(addresses, DeepEquals, []string{s.srvAddress})

View file

@ -23,17 +23,63 @@ package client
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/gravitational/teleport/lib/backend/boltbk"
"github.com/gravitational/teleport/lib/services"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace" "github.com/gravitational/trace"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent" "golang.org/x/crypto/ssh/agent"
) )
func AddHostSignersToCache(hostSigners []services.CertificateAuthority) error {
bk, err := boltbk.New(filepath.Join(KeysDir, HostSignersFilename))
if err != nil {
return trace.Wrap(nil)
}
defer bk.Close()
ca := services.NewCAService(bk)
ca.IsCache = true
for _, hostSigner := range hostSigners {
err := ca.UpsertRemoteCertificate(hostSigner, 0)
if err != nil {
return trace.Wrap(nil)
}
}
return nil
}
func CheckHostSignerFromCache(hostname string, remote net.Addr, key ssh.PublicKey) error {
cert, ok := key.(*ssh.Certificate)
if !ok {
return trace.Errorf("expected certificate")
}
bk, err := boltbk.New(filepath.Join(KeysDir, HostSignersFilename))
if err != nil {
return trace.Wrap(nil)
}
defer bk.Close()
ca := services.NewCAService(bk)
ca.IsCache = true
_, found, err := ca.GetCertificateID(services.HostCert, cert.SignatureKey)
if err != nil {
return trace.Wrap(nil)
}
if !found {
return trace.Errorf("CheckHostSigners error: Not found")
}
return nil
}
// GetLoadAgent loads all the saved teleport certificates and // GetLoadAgent loads all the saved teleport certificates and
// creates ssh agent with them // creates ssh agent with them
func GetLocalAgent() (agent.Agent, error) { func GetLocalAgent() (agent.Agent, error) {
@ -152,8 +198,9 @@ func loadAllKeys() ([]Key, error) {
return keys, nil return keys, nil
} }
const ( var (
KeysDir = "/tmp/teleport" KeysDir = "/tmp/teleport"
KeyFilePrefix = "teleport_" KeyFilePrefix = "teleport_"
KeyFileSuffix = ".tkey" KeyFileSuffix = ".tkey"
HostSignersFilename = "HostSigners.db"
) )

View file

@ -32,6 +32,7 @@ import (
"sync" "sync"
"github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace" "github.com/gravitational/trace"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -40,16 +41,16 @@ import (
// RunCmd runs provided command on the target servers and // RunCmd runs provided command on the target servers and
// prints result to stdout, // prints result to stdout,
// target can be like "127.0.0.1:1234" or "_label:value". // target can be like "127.0.0.1:1234" or "_label:value".
func RunCmd(user, target, proxyAddress, command string, authMethods []ssh.AuthMethod) error { func RunCmd(user, target, proxyAddress, command string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
addresses, err := ParseTargetServers(target, user, proxyAddress, addresses, err := ParseTargetServers(target, user, proxyAddress,
authMethods) authMethods, hostKeyCallback)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
var proxyClient *ProxyClient var proxyClient *ProxyClient
if len(proxyAddress) > 0 { if len(proxyAddress) > 0 {
proxyClient, err = ConnectToProxy(proxyAddress, authMethods, user) proxyClient, err = ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -64,7 +65,7 @@ func RunCmd(user, target, proxyAddress, command string, authMethods []ssh.AuthMe
wg.Add(1) wg.Add(1)
go func(address string) { go func(address string) {
defer wg.Done() defer wg.Done()
output, err := runCmd(user, address, proxyClient, command, authMethods) output, err := runCmd(user, address, proxyClient, command, authMethods, hostKeyCallback)
stdoutMutex.Lock() stdoutMutex.Lock()
defer stdoutMutex.Unlock() defer stdoutMutex.Unlock()
fmt.Printf("Running command on %v\n", address) fmt.Printf("Running command on %v\n", address)
@ -91,9 +92,9 @@ func RunCmd(user, target, proxyAddress, command string, authMethods []ssh.AuthMe
// runCmd runs command on provided server and returns the output as string // runCmd runs command on provided server and returns the output as string
func runCmd(user, address string, func runCmd(user, address string,
proxyClient *ProxyClient, command string, proxyClient *ProxyClient, command string,
authMethods []ssh.AuthMethod) (output string, e error) { authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) (output string, e error) {
c, err := ConnectToNode(proxyClient, address, authMethods, user) c, err := ConnectToNode(proxyClient, address, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return "", trace.Wrap(err) return "", trace.Wrap(err)
} }
@ -111,8 +112,10 @@ func runCmd(user, address string,
// Upload uploads file or dir to the target servers, // Upload uploads file or dir to the target servers,
// target can be like "127.0.0.1:1234" or "_label:value". // target can be like "127.0.0.1:1234" or "_label:value".
// Processes for each server work in parallel // Processes for each server work in parallel
func Upload(user, target, proxyAddress, localSourcePath, remoteDestPath string, authMethods []ssh.AuthMethod) error { func Upload(user, target, proxyAddress, localSourcePath, remoteDestPath string,
addresses, err := ParseTargetServers(target, user, proxyAddress, authMethods) authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
addresses, err := ParseTargetServers(target, user, proxyAddress, authMethods, hostKeyCallback)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -122,7 +125,7 @@ func Upload(user, target, proxyAddress, localSourcePath, remoteDestPath string,
var proxyClient *ProxyClient var proxyClient *ProxyClient
if len(proxyAddress) > 0 { if len(proxyAddress) > 0 {
proxyClient, err = ConnectToProxy(proxyAddress, authMethods, user) proxyClient, err = ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -139,7 +142,7 @@ func Upload(user, target, proxyAddress, localSourcePath, remoteDestPath string,
defer wg.Done() defer wg.Done()
err := upload(user, address, proxyClient, err := upload(user, address, proxyClient,
localSourcePath, remoteDestPath, authMethods) localSourcePath, remoteDestPath, authMethods, hostKeyCallback)
stdoutMutex.Lock() stdoutMutex.Lock()
defer stdoutMutex.Unlock() defer stdoutMutex.Unlock()
@ -166,9 +169,9 @@ func Upload(user, target, proxyAddress, localSourcePath, remoteDestPath string,
// upload uploads file or dir to the provided server // upload uploads file or dir to the provided server
func upload(user, srvAddress string, proxyClient *ProxyClient, func upload(user, srvAddress string, proxyClient *ProxyClient,
localSourcePath, remoteDestPath string, localSourcePath, remoteDestPath string,
authMethods []ssh.AuthMethod) error { authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
c, err := ConnectToNode(proxyClient, srvAddress, authMethods, user) c, err := ConnectToNode(proxyClient, srvAddress, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -187,8 +190,10 @@ func upload(user, srvAddress string, proxyClient *ProxyClient,
// Processes for each server work in parallel. // Processes for each server work in parallel.
// If there are more than one target server, result files will be // If there are more than one target server, result files will be
// arranged in a folder. // arranged in a folder.
func Download(user, target, proxyAddress, remoteSourcePath, localDestPath string, isDir bool, authMethods []ssh.AuthMethod) error { func Download(user, target, proxyAddress, remoteSourcePath, localDestPath string,
addresses, err := ParseTargetServers(target, user, proxyAddress, authMethods) isDir bool, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
addresses, err := ParseTargetServers(target, user, proxyAddress, authMethods, hostKeyCallback)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -217,7 +222,7 @@ func Download(user, target, proxyAddress, remoteSourcePath, localDestPath string
var proxyClient *ProxyClient var proxyClient *ProxyClient
if len(proxyAddress) > 0 { if len(proxyAddress) > 0 {
proxyClient, err = ConnectToProxy(proxyAddress, authMethods, user) proxyClient, err = ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -238,7 +243,7 @@ func Download(user, target, proxyAddress, remoteSourcePath, localDestPath string
} }
err := download(user, address, proxyClient, err := download(user, address, proxyClient,
remoteSourcePath, dest, isDir, authMethods) remoteSourcePath, dest, isDir, authMethods, hostKeyCallback)
stdoutMutex.Lock() stdoutMutex.Lock()
defer stdoutMutex.Unlock() defer stdoutMutex.Unlock()
@ -266,9 +271,9 @@ func Download(user, target, proxyAddress, remoteSourcePath, localDestPath string
// download downloads file or dir from provided server // download downloads file or dir from provided server
func download(user, srvAddress string, proxyClient *ProxyClient, func download(user, srvAddress string, proxyClient *ProxyClient,
remoteSourcePath, localDestPath string, isDir bool, remoteSourcePath, localDestPath string, isDir bool,
authMethods []ssh.AuthMethod) error { authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
c, err := ConnectToNode(proxyClient, srvAddress, authMethods, user) c, err := ConnectToNode(proxyClient, srvAddress, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -286,7 +291,7 @@ func download(user, srvAddress string, proxyClient *ProxyClient,
// target can be like "127.0.0.1:1234" or "_label:value". // target can be like "127.0.0.1:1234" or "_label:value".
// If "_label:value" provided, it connects to the proxy server and // If "_label:value" provided, it connects to the proxy server and
// finds target servers // finds target servers
func ParseTargetServers(target string, user, proxyAddress string, authMethods []ssh.AuthMethod) ([]string, error) { func ParseTargetServers(target string, user, proxyAddress string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) ([]string, error) {
if target[0] == '_' { if target[0] == '_' {
// address is a label:value pair // address is a label:value pair
target = target[1:len(target)] target = target[1:len(target)]
@ -301,7 +306,7 @@ func ParseTargetServers(target string, user, proxyAddress string, authMethods []
return nil, trace.Errorf("proxy Address should be provided for server searching") return nil, trace.Errorf("proxy Address should be provided for server searching")
} }
proxyClient, err := ConnectToProxy(proxyAddress, authMethods, user) proxyClient, err := ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return nil, trace.Wrap(err) return nil, trace.Wrap(err)
} }

View file

@ -26,11 +26,13 @@ package client
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
"net"
"path/filepath" "path/filepath"
"strconv" "strconv"
"time" "time"
"github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/auth/native"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/web" "github.com/gravitational/teleport/lib/web"
"github.com/gravitational/trace" "github.com/gravitational/trace"
@ -43,27 +45,43 @@ func AuthMethodFromAgent(ag agent.Agent) ssh.AuthMethod {
return ssh.PublicKeysCallback(ag.Signers) return ssh.PublicKeysCallback(ag.Signers)
} }
// GenerateCertificateCallback returns ssh.AuthMethod as // NewWebAuth returns ssh.AuthMethod as
// a callback function. When callback is called, it tries to generate // a callback function and ssh HostKeyCallback. When any callback is
// teleport certificate using password and hotpToken, adds the // called, it tries to generate
// certificate to the provided agent, saves the certificate to the // load certificates using password and hotpToken, adds the
// login certificate to the provided agent, saves the certificates to the
// local folder and returns the agent as authenticator. // local folder and returns the agent as authenticator.
func NewWebAuth(ag agent.Agent, func NewWebAuth(ag agent.Agent,
user string, user string,
passwordCallback PasswordCallback, passwordCallback PasswordCallback,
webProxyAddress string, webProxyAddress string,
certificateTTL time.Duration) ssh.AuthMethod { certificateTTL time.Duration) (authMethod ssh.AuthMethod,
hostKeyCallback utils.HostKeyCallback) {
callbackFunc := func() (signers []ssh.Signer, err error) { callbackFunc := func() (signers []ssh.Signer, err error) {
err = Login(ag, webProxyAddress, user, certificateTTL, passwordCallback) err = Login(ag, webProxyAddress, user, certificateTTL, passwordCallback)
if err != nil { if err != nil {
fmt.Printf("Can't login to the server: %v\n", err)
return nil, trace.Wrap(err) return nil, trace.Wrap(err)
} }
return ag.Signers() return ag.Signers()
} }
return ssh.PublicKeysCallback(callbackFunc) hostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
err := CheckHostSignerFromCache(hostname, remote, key)
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)
}
return nil
}
return ssh.PublicKeysCallback(callbackFunc), hostKeyCallback
} }
type PasswordCallback func() (password, hotpToken string, e error) type PasswordCallback func() (password, hotpToken string, e error)
@ -88,13 +106,13 @@ func Login(ag agent.Agent, webProxyAddr string, user string,
return trace.Wrap(err) return trace.Wrap(err)
} }
cert, err := web.SSHAgentLogin(webProxyAddr, user, password, hotpToken, login, err := web.SSHAgentLogin(webProxyAddr, user, password, hotpToken,
pub, ttl) pub, ttl)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
pcert, _, _, _, err := ssh.ParseAuthorizedKey(cert) pcert, _, _, _, err := ssh.ParseAuthorizedKey(login.Cert)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -116,7 +134,7 @@ func Login(ag agent.Agent, webProxyAddr string, user string,
key := Key{ key := Key{
Priv: priv, Priv: priv,
Cert: cert, Cert: login.Cert,
Deadline: time.Now().Add(ttl), Deadline: time.Now().Add(ttl),
} }
@ -129,6 +147,11 @@ func Login(ag agent.Agent, webProxyAddr string, user string,
return trace.Wrap(err) return trace.Wrap(err)
} }
err = AddHostSignersToCache(login.HostSigners)
if err != nil {
return trace.Wrap(err)
}
fmt.Println("Logged in successfully") fmt.Println("Logged in successfully")
return nil return nil
} }

View file

@ -44,6 +44,10 @@ const (
// run behind an environment/firewall which only allows outgoing connections) // run behind an environment/firewall which only allows outgoing connections)
SSHProxyTunnelListenPort = 3024 SSHProxyTunnelListenPort = 3024
// When running in "SSH Proxy" role this port will be used for incoming
// connections from Hangouts nodes to register in the proxy
SSHProxyHangoutsListenPort = 3026
// When running as a "SSH Proxy" this port will be used to // When running as a "SSH Proxy" this port will be used to
// serve auth requests. // serve auth requests.
AuthListenPort = 3025 AuthListenPort = 3025
@ -126,6 +130,13 @@ func ReverseTunnellListenAddr() *utils.NetAddr {
return makeAddr(BindIP, SSHProxyTunnelListenPort) return makeAddr(BindIP, SSHProxyTunnelListenPort)
} }
// ReverseTunnellListenAddr returns the default listening address for the SSH Proxy service used
// by the Hangout nodes to establish hangouts_server<->proxy connection from the hangout
// owner side
func HangoutsListenAddr() *utils.NetAddr {
return makeAddr(BindIP, SSHProxyHangoutsListenPort)
}
func ReverseTunnellConnectAddr() *utils.NetAddr { func ReverseTunnellConnectAddr() *utils.NetAddr {
return makeAddr("127.0.0.1", SSHProxyTunnelListenPort) return makeAddr("127.0.0.1", SSHProxyTunnelListenPort)
} }

462
lib/hangout/hangout.go Normal file
View file

@ -0,0 +1,462 @@
/*
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 (
"net"
"os"
"os/user"
"path"
"time"
"github.com/gravitational/teleport/lib/auth"
authority "github.com/gravitational/teleport/lib/auth/native"
"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/limiter"
"github.com/gravitational/teleport/lib/recorder"
"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"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/utils"
log "github.com/Sirupsen/logrus"
"github.com/codahale/lunk"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
type Hangout struct {
auth *auth.AuthServer
signer ssh.Signer
tunClt reversetunnel.Agent
elog events.Log
rec recorder.Recorder
userPassword string
HangoutID string
nodePort string
authPort string
ClientAuthMethod ssh.AuthMethod
HostKeyCallback utils.HostKeyCallback
client *auth.TunClient
HangoutInfo utils.HangoutInfo
Token string
sessions session.SessionServer
}
func New(proxyTunnelAddress, nodeListeningAddress, authListeningAddress string,
readOnly bool, authMethods []ssh.AuthMethod,
hostKeyCallback utils.HostKeyCallback) (*Hangout, error) {
//log.SetOutput(os.Stderr)
//log.SetLevel(log.InfoLevel)
cfg := service.Config{}
service.ApplyDefaults(&cfg)
cfg.DataDir = HangoutDataDir + "/" + utils.RandomString()[:10]
cfg.Hostname = "localhost"
cfg.Auth.HostAuthorityDomain = "localhost"
cfg.Auth.KeysBackend.Type = "bolt"
cfg.Auth.KeysBackend.Params = `{"path": "` + cfg.DataDir + `/teleport.auth.db"}`
cfg.Auth.EventsBackend.Type = "bolt"
cfg.Auth.EventsBackend.Params = `{"path": "` + cfg.DataDir + `/teleport.event.db"}`
cfg.Auth.RecordsBackend.Type = "bolt"
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.AuthServers = []utils.NetAddr{cfg.Auth.SSHAddr}
nodeAddress, err := utils.ParseAddr("tcp://" + nodeListeningAddress)
if err != nil {
return nil, trace.Wrap(err)
}
cfg.SSH.Addr = *nodeAddress
tunnelAddress, err := utils.ParseAddr("tcp://" + proxyTunnelAddress)
if err != nil {
return nil, trace.Wrap(err)
}
cfg.ReverseTunnel.DialAddr = *tunnelAddress
_, err = os.Stat(cfg.DataDir)
if os.IsNotExist(err) {
err := os.MkdirAll(cfg.DataDir, os.ModeDir|0777)
if err != nil {
return nil, trace.Wrap(err)
}
}
h := &Hangout{}
h.HangoutID = utils.RandomString()[:20]
if err := h.initAuth(cfg, readOnly); 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)
}
if err := h.initSSHEndpoint(cfg); err != nil {
return nil, trace.Wrap(err)
}
if err := h.createUser(); err != nil {
return nil, trace.Wrap(err)
}
_, h.authPort, err = net.SplitHostPort(authListeningAddress)
if err != nil {
return nil, trace.Wrap(err)
}
_, h.nodePort, err = net.SplitHostPort(nodeListeningAddress)
if err != nil {
return nil, trace.Wrap(err)
}
h.ClientAuthMethod, err = Authorize(h.client)
h.HostKeyCallback = nil
h.HangoutInfo.AuthPort = h.authPort
h.HangoutInfo.NodePort = h.nodePort
h.HangoutInfo.HangoutID = h.HangoutID
h.Token, err = utils.MarshalHangoutInfo(&h.HangoutInfo)
if err != nil {
return nil, trace.Wrap(err)
}
// saving hangoutInfo using sessions just as storage
err = h.sessions.UpsertSession(h.Token, 0)
if err != nil {
return nil, trace.Wrap(err)
}
if err := h.initTunAgent(cfg, authMethods, hostKeyCallback); err != nil {
return nil, trace.Wrap(err)
}
return h, nil
}
func (h *Hangout) createUser() error {
var err error
h.userPassword = utils.RandomString()
if err != nil {
return trace.Wrap(err)
}
u, err := user.Current()
if err != nil {
return trace.Wrap(err)
}
osUser := u.Username
h.HangoutInfo.OSUser = osUser
_, _, 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.ClientI) (ssh.AuthMethod, error) {
priv, pub, err := authority.New().GenerateKeyPair("")
if err != nil {
return nil, trace.Wrap(err)
}
cert, err := auth.GenerateUserCert(pub, "id_"+HangoutUser, HangoutUser, 24*time.Hour)
if err != nil {
return nil, trace.Wrap(err)
}
pcert, _, _, _, err := ssh.ParseAuthorizedKey(cert)
if err != nil {
return nil, trace.Wrap(err)
}
pk, err := ssh.ParseRawPrivateKey(priv)
if err != nil {
return nil, trace.Wrap(err)
}
addedKey := agent.AddedKey{
PrivateKey: pk,
Certificate: pcert.(*ssh.Certificate),
Comment: "",
LifetimeSecs: 0,
ConfirmBeforeUse: false,
}
ag := agent.NewKeyring()
if err := ag.Add(addedKey); err != nil {
return nil, trace.Wrap(err)
}
return ssh.PublicKeysCallback(ag.Signers), nil
}
func (h *Hangout) initAuth(cfg service.Config, readOnlyHangout bool) error {
if cfg.Auth.HostAuthorityDomain == "" {
return trace.Errorf(
"please provide host certificate authority domain, e.g. example.com")
}
b, err := initBackend(cfg.DataDir, cfg.Hostname, cfg.AuthServers, cfg.Auth)
if err != nil {
return trace.Wrap(err)
}
h.elog, err = initEventBackend(
cfg.Auth.EventsBackend.Type, cfg.Auth.EventsBackend.Params)
if err != nil {
return trace.Wrap(err)
}
h.rec, err = initRecordBackend(
cfg.Auth.RecordsBackend.Type, cfg.Auth.RecordsBackend.Params)
if err != nil {
return trace.Wrap(err)
}
trustedAuthorities, err := cfg.Auth.TrustedAuthorities.Authorities()
if err != nil {
return trace.Wrap(err)
}
acfg := auth.InitConfig{
Backend: b,
Authority: authority.New(),
DomainName: cfg.Hostname,
AuthDomain: cfg.Auth.HostAuthorityDomain,
DataDir: cfg.DataDir,
SecretKey: cfg.Auth.SecretKey,
AllowedTokens: cfg.Auth.AllowedTokens,
TrustedAuthorities: trustedAuthorities,
}
asrv, signer, err := auth.Init(acfg)
if err != nil {
return trace.Wrap(err)
}
h.signer = signer
h.auth = asrv
h.sessions = session.New(b)
apisrv := auth.NewAPIWithRoles(asrv, h.elog, h.sessions, h.rec,
auth.NewHangoutPermissions(), auth.HangoutRoles,
)
go apisrv.Serve()
limiter, err := limiter.NewLimiter(cfg.Auth.Limiter)
if err != nil {
return trace.Wrap(err)
}
log.Infof("[AUTH] server SSH endpoint is starting")
tsrv, err := auth.NewTunServer(
cfg.Auth.SSHAddr, []ssh.Signer{h.signer},
apisrv,
asrv,
limiter,
auth.EnableUserCertificates(),
)
if err != nil {
return trace.Wrap(err)
}
go func() {
if err := tsrv.Start(); err != nil {
log.Errorf(err.Error())
}
}()
client, err := auth.NewTunClient(
cfg.AuthServers[0],
cfg.Hostname,
[]ssh.AuthMethod{ssh.PublicKeys(h.signer)})
if err != nil {
return trace.Wrap(err)
}
h.client = client
return nil
}
func (h *Hangout) initTunAgent(cfg service.Config, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
elog := &service.FanOutEventLogger{
Loggers: []lunk.EventLogger{
lunk.NewTextEventLogger(log.StandardLogger().Writer()),
h.client,
}}
a, err := reversetunnel.NewHangoutAgent(
cfg.ReverseTunnel.DialAddr,
h.HangoutID,
authMethods,
hostKeyCallback,
h.client,
reversetunnel.SetEventLogger(elog))
if err != nil {
return trace.Wrap(err)
}
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.Wait(); err != nil {
log.Fatalf("failed to start: %v", err)
}
}()
return nil
}
func (h *Hangout) initSSHEndpoint(cfg service.Config) error {
elog := &service.FanOutEventLogger{
Loggers: []lunk.EventLogger{
lunk.NewTextEventLogger(log.StandardLogger().Writer()),
h.client,
},
}
limiter, err := limiter.NewLimiter(cfg.SSH.Limiter)
if err != nil {
return trace.Wrap(err)
}
s, err := srv.New(cfg.SSH.Addr,
h.HangoutID,
[]ssh.Signer{h.signer},
h.client,
limiter,
cfg.DataDir,
srv.SetShell(cfg.SSH.Shell),
srv.SetEventLogger(elog),
srv.SetSessionServer(h.client),
srv.SetRecorder(h.client),
srv.SetLabels(cfg.SSH.Labels, cfg.SSH.CmdLabels),
)
if err != nil {
return trace.Wrap(err)
}
log.Infof("[SSH] server is starting on %v", cfg.SSH.Addr)
go func() {
if err := s.Start(); err != nil {
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)
}
const HangoutUser = "hangoutUser"
const HangoutDataDir = "/tmp/teleport_hangouts"
const DefaultNodeAddress = "localhost:3031"
const DefaultAuthAddress = "localhost:3032"

View file

@ -34,14 +34,15 @@ import (
) )
type Agent struct { type Agent struct {
addr utils.NetAddr addr utils.NetAddr
elog lunk.EventLogger elog lunk.EventLogger
clt *auth.TunClient clt *auth.TunClient
signers []ssh.Signer domainName string
domainName string waitC chan bool
waitC chan bool disconnectC chan bool
disconnectC chan bool conn ssh.Conn
conn ssh.Conn hostKeyCallback utils.HostKeyCallback
authMethods []ssh.AuthMethod
} }
type AgentOption func(a *Agent) error type AgentOption func(a *Agent) error
@ -60,9 +61,35 @@ func NewAgent(addr utils.NetAddr, domainName string, signers []ssh.Signer,
clt: clt, clt: clt,
addr: addr, addr: addr,
domainName: domainName, domainName: domainName,
signers: signers,
waitC: make(chan bool), waitC: make(chan bool),
disconnectC: make(chan bool, 10), disconnectC: make(chan bool, 10),
authMethods: []ssh.AuthMethod{ssh.PublicKeys(signers...)},
}
a.hostKeyCallback = a.checkHostSignature
for _, o := range options {
if err := o(a); err != nil {
return nil, err
}
}
if a.elog == nil {
a.elog = utils.NullEventLogger
}
return a, nil
}
func NewHangoutAgent(addr utils.NetAddr, hangoutID string,
authMethods []ssh.AuthMethod,
hostKeyCallback utils.HostKeyCallback,
clt *auth.TunClient, options ...AgentOption) (*Agent, error) {
a := &Agent{
clt: clt,
addr: addr,
domainName: hangoutID,
waitC: make(chan bool),
disconnectC: make(chan bool, 10),
authMethods: authMethods,
hostKeyCallback: hostKeyCallback,
} }
for _, o := range options { for _, o := range options {
if err := o(a); err != nil { if err := o(a); err != nil {
@ -148,15 +175,25 @@ func (a *Agent) connect() error {
return err return err
} }
log.Infof("agent connectting to %v", a.addr.FullAddress()) log.Infof("agent connectting to %v", a.addr.FullAddress())
c, err := ssh.Dial(a.addr.AddrNetwork, a.addr.Addr, &ssh.ClientConfig{
User: a.domainName, var c *ssh.Client
Auth: []ssh.AuthMethod{ssh.PublicKeys(a.signers...)}, var err error
HostKeyCallback: a.checkHostSignature, for _, authMethod := range a.authMethods {
}) c, err = ssh.Dial(a.addr.AddrNetwork, a.addr.Addr, &ssh.ClientConfig{
if err != nil { User: a.domainName,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: a.hostKeyCallback,
})
if c != nil {
break
}
}
if c == nil {
log.Errorf("failed connecting to '%v'. %v", a.addr.Addr, err) log.Errorf("failed connecting to '%v'. %v", a.addr.Addr, err)
return trace.Wrap(err) return trace.Wrap(err)
} }
a.conn = c a.conn = c
go a.startHeartbeat() go a.startHeartbeat()

View file

@ -46,6 +46,7 @@ type RemoteSite interface {
GetStatus() string GetStatus() string
GetClient() *auth.Client GetClient() *auth.Client
GetServers() ([]services.Server, error) GetServers() ([]services.Server, error)
GetHangoutInfo() (hostKey *services.CertificateAuthority, OSUser, AuthPort, NodePort string)
} }
type Server interface { type Server interface {
@ -63,6 +64,7 @@ type server struct {
certChecker ssh.CertChecker certChecker ssh.CertChecker
l net.Listener l net.Listener
srv *sshutils.Server srv *sshutils.Server
upsertSite func(c ssh.Conn) (*remoteSite, error)
sites []*remoteSite sites []*remoteSite
} }
@ -87,6 +89,32 @@ func NewServer(addr utils.NetAddr, hostSigners []ssh.Signer,
return nil, err return nil, err
} }
srv.certChecker = ssh.CertChecker{IsAuthority: srv.isAuthority} srv.certChecker = ssh.CertChecker{IsAuthority: srv.isAuthority}
srv.upsertSite = srv.upsertRegularSite
srv.srv = s
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.upsertSite = srv.upsertHangoutSite
srv.srv = s srv.srv = s
return srv, nil return srv, nil
} }
@ -144,6 +172,20 @@ func (s *server) isAuthority(auth ssh.PublicKey) bool {
return false 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) { func (s *server) getTrustedCAKeys() ([]ssh.PublicKey, error) {
out := []ssh.PublicKey{} out := []ssh.PublicKey{}
authKeys := [][]byte{} authKeys := [][]byte{}
@ -155,6 +197,7 @@ func (s *server) getTrustedCAKeys() ([]ssh.PublicKey, error) {
certs, err := s.ap.GetRemoteCertificates(services.HostCert, "") certs, err := s.ap.GetRemoteCertificates(services.HostCert, "")
if err != nil { if err != nil {
log.Errorf(err.Error())
return nil, err return nil, err
} }
for _, c := range certs { for _, c := range certs {
@ -201,7 +244,44 @@ func (s *server) keyAuth(
return perms, nil return perms, nil
} }
func (s *server) upsertSite(c ssh.Conn) (*remoteSite, error) { 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")
}
_, 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) upsertRegularSite(c ssh.Conn) (*remoteSite, error) {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
@ -227,6 +307,56 @@ func (s *server) upsertSite(c ssh.Conn) (*remoteSite, error) {
return site, nil return site, nil
} }
func (s *server) upsertHangoutSite(c ssh.Conn) (*remoteSite, error) {
s.Lock()
defer s.Unlock()
hangoutID := c.User()
for _, st := range s.sites {
if st.domainName == hangoutID {
return nil, trace.Errorf("Hangout ID is already used")
}
}
site := &remoteSite{srv: s, domainName: hangoutID}
err := site.init(c)
if err != nil {
return nil, err
}
site.hangoutHostKey, err = site.clt.GetHostCertificateAuthority()
if err != nil {
return nil, err
}
proxyUserCert, err := s.ap.GetUserCertificateAuthority()
if err != nil {
return nil, err
}
err = site.clt.UpsertRemoteCertificate(*proxyUserCert, 0)
if err != nil {
return nil, err
}
// receiving hangoutInfo using sessions just as storage
sess, err := site.clt.GetSessions()
if err != nil {
return nil, err
}
if len(sess) != 1 {
return nil, trace.Errorf("Can't get hangout info")
}
hangoutInfo, err := utils.UnmarshalHangoutInfo(sess[0].ID)
if err != nil {
return nil, err
}
site.domainName = hangoutInfo.HangoutID
site.hangoutOSUser = hangoutInfo.OSUser
site.hangoutAuthPort = hangoutInfo.AuthPort
site.hangoutNodePort = hangoutInfo.NodePort
s.sites = append(s.sites, site)
return site, nil
}
func (s *server) GetSites() []RemoteSite { func (s *server) GetSites() []RemoteSite {
s.RLock() s.RLock()
defer s.RUnlock() defer s.RUnlock()
@ -245,7 +375,7 @@ func (s *server) GetSite(domainName string) (RemoteSite, error) {
return s.sites[i], nil return s.sites[i], nil
} }
} }
return nil, fmt.Errorf("site not found") return nil, fmt.Errorf("site %v not found", domainName)
} }
// FindSimilarSite finds the site that is the most similar to domain. // FindSimilarSite finds the site that is the most similar to domain.
@ -289,6 +419,11 @@ type remoteSite struct {
lastActive time.Time lastActive time.Time
srv *server srv *server
clt *auth.Client clt *auth.Client
hangoutHostKey *services.CertificateAuthority
hangoutOSUser string
hangoutAuthPort string
hangoutNodePort string
} }
func (s *remoteSite) GetClient() *auth.Client { func (s *remoteSite) GetClient() *auth.Client {
@ -316,7 +451,7 @@ func (s *remoteSite) init(c ssh.Conn) error {
log.Errorf("remoteSite:authProxy %v", err) log.Errorf("remoteSite:authProxy %v", err)
return nil, err return nil, err
} }
return newChConn(s.conn, ch), nil return utils.NewChConn(s.conn, ch), nil
}, },
} }
clt, err := auth.NewClient( clt, err := auth.NewClient(
@ -374,7 +509,7 @@ func (s *remoteSite) ConnectToServer(server, user string, auth []ssh.AuthMethod)
if !dialed { if !dialed {
return nil, trace.Errorf("remote server %v is not available", server) 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( conn, chans, reqs, err := ssh.NewClientConn(
transportConn, server, transportConn, server,
&ssh.ClientConfig{ &ssh.ClientConfig{
@ -419,7 +554,7 @@ func (s *remoteSite) DialServer(server string) (net.Conn, error) {
if !dialed { if !dialed {
return nil, trace.Errorf("remote server %v is not available", server) 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) { func (s *remoteSite) GetServers() ([]services.Server, error) {
@ -434,7 +569,7 @@ func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
log.Errorf("remoteSite:authProxy %v", err) log.Errorf("remoteSite:authProxy %v", err)
return nil, err return nil, err
} }
return newChConn(s.conn, ch), nil return utils.NewChConn(s.conn, ch), nil
}, },
} }
@ -449,36 +584,8 @@ func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
fwd.ServeHTTP(w, r) fwd.ServeHTTP(w, r)
} }
func newChConn(conn ssh.Conn, ch ssh.Channel) *chConn { func (s *remoteSite) GetHangoutInfo() (hostKey *services.CertificateAuthority, OSUser, AuthPort, NodePort string) {
c := &chConn{} return s.hangoutHostKey, s.hangoutOSUser, s.hangoutAuthPort, s.hangoutNodePort
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" const ExtHost = "host@teleport"

View file

@ -90,6 +90,11 @@ type ProxyConfig struct {
// ReverseTunnelListenAddr is address where reverse tunnel dialers connect to // ReverseTunnelListenAddr is address where reverse tunnel dialers connect to
ReverseTunnelListenAddr utils.NetAddr ReverseTunnelListenAddr utils.NetAddr
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 is address for web portal of the proxy
WebAddr utils.NetAddr WebAddr utils.NetAddr
@ -302,14 +307,14 @@ func (c *LocalCertificateAuthority) CA() (*services.LocalCertificateAuthority, e
// MakeDefaultConfig() creates a new Config structure and populates it with defaults // MakeDefaultConfig() creates a new Config structure and populates it with defaults
func MakeDefaultConfig() (config *Config, err error) { func MakeDefaultConfig() (config *Config, err error) {
config = &Config{} config = &Config{}
if err = applyDefaults(config); err != nil { if err = ApplyDefaults(config); err != nil {
return nil, trace.Wrap(err) return nil, trace.Wrap(err)
} }
return config, nil return config, nil
} }
// applyDefaults applies default values to the existing config structure // ApplyDefaults applies default values to the existing config structure
func applyDefaults(cfg *Config) error { func ApplyDefaults(cfg *Config) error {
hostname, err := os.Hostname() hostname, err := os.Hostname()
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
@ -332,6 +337,8 @@ func applyDefaults(cfg *Config) error {
cfg.Proxy.AssetsDir = defaults.DataDir cfg.Proxy.AssetsDir = defaults.DataDir
cfg.Proxy.SSHAddr = *defaults.ProxyListenAddr() cfg.Proxy.SSHAddr = *defaults.ProxyListenAddr()
cfg.Proxy.WebAddr = *defaults.ProxyWebListenAddr() cfg.Proxy.WebAddr = *defaults.ProxyWebListenAddr()
cfg.Proxy.HangoutsEnabled = true
cfg.Proxy.HangoutsListenAddr = *defaults.HangoutsListenAddr()
cfg.ReverseTunnel.Enabled = true cfg.ReverseTunnel.Enabled = true
cfg.ReverseTunnel.DialAddr = *defaults.ReverseTunnellConnectAddr() cfg.ReverseTunnel.DialAddr = *defaults.ReverseTunnellConnectAddr()
cfg.Proxy.ReverseTunnelListenAddr = *defaults.ReverseTunnellListenAddr() cfg.Proxy.ReverseTunnelListenAddr = *defaults.ReverseTunnellListenAddr()

View file

@ -408,13 +408,32 @@ func initProxyEndpoint(supervisor Supervisor, cfg Config) error {
return trace.Wrap(err) return trace.Wrap(err)
} }
var hsrv reversetunnel.Server
if cfg.Proxy.HangoutsEnabled {
hsrv, err = reversetunnel.NewHangoutServer(
cfg.Proxy.HangoutsListenAddr,
[]ssh.Signer{i.KeySigner},
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, SSHProxy, err := srv.New(cfg.Proxy.SSHAddr,
cfg.Hostname, cfg.Hostname,
[]ssh.Signer{i.KeySigner}, []ssh.Signer{i.KeySigner},
client, client,
proxyLimiter, proxyLimiter,
cfg.DataDir, cfg.DataDir,
srv.SetProxyMode(tsrv), options...,
) )
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
@ -432,6 +451,20 @@ func initProxyEndpoint(supervisor Supervisor, cfg Config) error {
return nil return nil
}) })
if cfg.Proxy.HangoutsEnabled {
// register SSH reverse tunnel server that accepts connections
// from remote teleport nodes
supervisor.RegisterFunc(func() error {
utils.Consolef(cfg.Console, "[PROXY] Hangouts service is starting on %v", cfg.Proxy.HangoutsListenAddr.Addr)
if err := hsrv.Start(); err != nil {
utils.Consolef(cfg.Console, "[PROXY] Error: %v", err)
return trace.Wrap(err)
}
hsrv.Wait()
return nil
})
}
// Register web proxy server // Register web proxy server
supervisor.RegisterFunc(func() error { supervisor.RegisterFunc(func() error {
utils.Consolef(cfg.Console, "[PROXY] Web proxy service is starting on %v", cfg.Proxy.WebAddr.Addr) utils.Consolef(cfg.Console, "[PROXY] Web proxy service is starting on %v", cfg.Proxy.WebAddr.Addr)

54
lib/services/hangouts.go Normal file
View file

@ -0,0 +1,54 @@
/*
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 services
/*import (
"encoding/json"
"fmt"
"net/url"
"sync"
"time"
"github.com/gokyle/hotp"
log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace"
"golang.org/x/crypto/bcrypt"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/backend"
)
// UpsertPasswordHash upserts user password hash
func (s *WebService) SetHangoutID(id string) error {
err := s.backend.UpsertVal([]string{"hangout", "id", user},
"pwd", hash, 0)
if err != nil {
log.Errorf(err.Error())
return trace.Wrap(err)
}
return err
}
// GetPasswordHash returns the password hash for a given user
func (s *WebService) GetPasswordHash(user string) ([]byte, error) {
hash, err := s.backend.GetVal([]string{"web", "users", user}, "pwd")
if err != nil {
return nil, err
}
return hash, err
}
*/

View file

@ -65,6 +65,40 @@ func (s *PresenceService) UpsertServer(server Server, ttl time.Duration) error {
return err return err
} }
// GetServers returns a list of registered servers
func (s *PresenceService) GetAuthServers() ([]Server, error) {
IDs, err := s.backend.GetKeys([]string{"authservers"})
if err != nil {
return nil, trace.Wrap(err)
}
servers := make([]Server, len(IDs))
for i, id := range IDs {
data, err := s.backend.GetVal([]string{"authservers"}, id)
if err != nil {
return nil, trace.Wrap(err)
}
if err := json.Unmarshal(data, &servers[i]); err != nil {
return nil, trace.Wrap(err)
}
}
return servers, nil
}
// UpsertServer registers server presence, permanently if ttl is 0 or
// for the specified duration with second resolution if it's >= 1 second
func (s *PresenceService) UpsertAuthServer(server Server, ttl time.Duration) error {
data, err := json.Marshal(server)
if err != nil {
return trace.Wrap(err)
}
err = s.backend.UpsertVal([]string{"authservers"},
server.ID, data, ttl)
if err != nil {
return trace.Wrap(err)
}
return err
}
type Server struct { type Server struct {
ID string `json:"id"` ID string `json:"id"`
Addr string `json:"addr"` Addr string `json:"addr"`

View file

@ -69,9 +69,9 @@ func (s *ProvisioningService) DeleteToken(token string) error {
func JoinTokenRole(token, role string) (ouputToken string, e error) { func JoinTokenRole(token, role string) (ouputToken string, e error) {
switch role { switch role {
case "Auth": case TokenRoleAuth:
return "a" + token, nil return "a" + token, nil
case "Node": case TokenRoleNode:
return "n" + token, nil return "n" + token, nil
} }
return token, trace.Errorf("Unknown role %v", role) return token, trace.Errorf("Unknown role %v", role)
@ -82,10 +82,10 @@ func SplitTokenRole(outputToken string) (token, role string, e error) {
return outputToken, "", trace.Errorf("Unknown role") return outputToken, "", trace.Errorf("Unknown role")
} }
if outputToken[0] == 'n' { if outputToken[0] == 'n' {
return outputToken[1:], "Node", nil return outputToken[1:], TokenRoleNode, nil
} }
if outputToken[0] == 'a' { if outputToken[0] == 'a' {
return outputToken[1:], "Auth", nil return outputToken[1:], TokenRoleAuth, nil
} }
return outputToken, "", trace.Errorf("Unknown role") return outputToken, "", trace.Errorf("Unknown role")
} }
@ -94,3 +94,8 @@ type ProvisionToken struct {
DomainName string DomainName string
Role string Role string
} }
const (
TokenRoleAuth = "Auth"
TokenRoleNode = "Node"
)

135
lib/srv/hangouts.go Normal file
View file

@ -0,0 +1,135 @@
package srv
import (
"encoding/json"
"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
}
type HangoutEndpointInfo struct {
HostKey services.CertificateAuthority
OSUser 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)
}
hostKey, osUser, authPort, nodePort := remoteSrv.GetHangoutInfo()
if hostKey == nil {
return trace.Errorf("No hostkey for that hangout")
}
targetPort := ""
if t.port == "auth" {
targetPort = authPort
}
if t.port == "node" {
targetPort = nodePort
}
// find matching server in the list of servers for this site
servers, err := remoteSrv.GetServers()
if err != nil {
return trace.Wrap(err)
}
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) && targetPort == port {
server = &servers[i]
break
}
}
if server == nil {
return trace.Errorf("server %v:%v not found", t.host, t.port)
}
// send target server host key so user can check the server
endpointInfo := HangoutEndpointInfo{
HostKey: *hostKey,
OSUser: osUser,
}
data, err := json.Marshal(endpointInfo)
if err != nil {
return trace.Wrap(err)
}
_, err = ch.Write(data)
if err != nil {
return trace.Wrap(err)
}
// 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
}

View file

@ -39,8 +39,8 @@ import (
"github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils"
"code.google.com/p/go-uuid/uuid" "code.google.com/p/go-uuid/uuid"
"github.com/codahale/lunk"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/codahale/lunk"
"github.com/gravitational/trace" "github.com/gravitational/trace"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
// Server implements SSH server that uses configuration backend and certificate-based authentication: // Server implements SSH server that uses configuration backend and certificate-based authentication:
@ -68,8 +68,9 @@ type Server struct {
certificatesCache *services.CAService certificatesCache *services.CAService
proxyMode bool proxyMode bool
proxyTun reversetunnel.Server proxyTun reversetunnel.Server
hangoutsTun reversetunnel.Server
} }
type ServerOption func(s *Server) error 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, func SetLabels(labels map[string]string,
cmdLabels services.CommandLabels) ServerOption { cmdLabels services.CommandLabels) ServerOption {
return func(s *Server) error { return func(s *Server) error {
@ -147,6 +155,11 @@ func New(addr utils.NetAddr, hostname string, signers []ssh.Signer,
return nil, err return nil, err
} }
} }
if (s.hangoutsTun != nil) && (!s.proxyMode) {
return nil, trace.Errorf("Hangout available only in proxy mode")
}
if s.elog == nil { if s.elog == nil {
s.elog = utils.NullEventLogger 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) { func (s *Server) HandleNewChan(sconn *ssh.ServerConn, nch ssh.NewChannel) {
channelType := nch.ChannelType() channelType := nch.ChannelType()
if s.proxyMode { if s.proxyMode {
if channelType == "session" { // interactive sessions if channelType == "session" { // interactive sessions
ch, requests, err := nch.Accept() ch, requests, err := nch.Accept()

View file

@ -50,6 +50,9 @@ func parseSubsystemRequest(srv *Server, req *ssh.Request) (subsystem, error) {
if srv.proxyMode && strings.HasPrefix(s.Name, "proxy:") { if srv.proxyMode && strings.HasPrefix(s.Name, "proxy:") {
return parseProxySubsys(s.Name, srv) 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") { if srv.proxyMode && strings.HasPrefix(s.Name, "proxysites") {
return parseProxySitesSubsys(s.Name, srv) return parseProxySitesSubsys(s.Name, srv)
} }

View file

@ -57,20 +57,22 @@ func (a *TeleAgent) Start(agentAddr string) error {
return nil return nil
} }
func (a *TeleAgent) Login(proxyAddr string, user string, pass string, func (a *TeleAgent) Login(proxyAddr string,
hotpToken string, ttl time.Duration) error { user string, pass string, hotpToken string,
ttl time.Duration) error {
priv, pub, err := native.New().GenerateKeyPair("") priv, pub, err := native.New().GenerateKeyPair("")
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
cert, err := web.SSHAgentLogin(proxyAddr, user, pass, hotpToken, login, err := web.SSHAgentLogin(proxyAddr, user, pass, hotpToken,
pub, ttl) pub, ttl)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
pcert, _, _, _, err := ssh.ParseAuthorizedKey(cert) pcert, _, _, _, err := ssh.ParseAuthorizedKey(login.Cert)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }

View file

@ -20,6 +20,8 @@ import (
"io" "io"
"net" "net"
"time" "time"
"golang.org/x/crypto/ssh"
) )
// PipeNetConn implemetns net.Conn from io.Reader,io.Writer and io.Closer // 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) go io.Copy(writer, r2)
return reader 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
}

52
lib/utils/hangout.go Normal file
View file

@ -0,0 +1,52 @@
/*
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 utils
import (
"encoding/base64"
"encoding/json"
"github.com/gravitational/trace"
)
type HangoutInfo struct {
AuthPort string `json:"a"`
NodePort string `json:"n"`
HangoutID string `json:"id"`
OSUser string `json:"u"`
}
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
}

View file

@ -20,12 +20,18 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/rand"
"net"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"github.com/gravitational/trace" "github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
) )
type HostKeyCallback func(hostname string, remote net.Addr, key ssh.PublicKey) error
func ReadPath(path string) ([]byte, error) { func ReadPath(path string) ([]byte, error) {
s, err := filepath.Abs(path) s, err := filepath.Abs(path)
if err != nil { if err != nil {
@ -100,6 +106,15 @@ func MultiCloser(closers ...io.Closer) *multiCloser {
} }
} }
func RandomString() string {
result := ""
for i := 0; i < 10; i++ {
x := rand.Uint32()
result += strconv.FormatUint(uint64(x), 16)
}
return result
}
const ( const (
CertExtensionUser = "x-teleport-user" CertExtensionUser = "x-teleport-user"
CertExtensionRole = "x-teleport-role" CertExtensionRole = "x-teleport-role"

View file

@ -23,6 +23,7 @@ import (
"net/http" "net/http"
"github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils"
@ -123,7 +124,7 @@ type RequestHandler func(http.ResponseWriter, *http.Request, httprouter.Params,
type AuthHandler interface { type AuthHandler interface {
GetHost() string GetHost() string
Auth(user, pass string, hotpToken string) (string, error) Auth(user, pass string, hotpToken string) (string, error)
GetCertificate(c SSHLoginCredentials) ([]byte, error) GetCertificate(c SSHLoginCredentials) (SSHLoginResponse, error)
NewUserForm(token string) (user string, QRImg []byte, hotpFirstValues []string, e error) NewUserForm(token string) (user string, QRImg []byte, hotpFirstValues []string, e error)
NewUserFinish(token, password, hotpToken string) error NewUserFinish(token, password, hotpToken string) error
ValidateSession(user, sid string) (Context, error) ValidateSession(user, sid string) (Context, error)
@ -174,23 +175,30 @@ func (s *LocalAuth) Auth(user, pass string, hotpToken string) (string, error) {
return clt.SignIn(user, []byte(pass)) return clt.SignIn(user, []byte(pass))
} }
func (s *LocalAuth) GetCertificate(c SSHLoginCredentials) ([]byte, error) { func (s *LocalAuth) GetCertificate(c SSHLoginCredentials) (SSHLoginResponse, error) {
method, err := auth.NewWebPasswordAuth(c.User, []byte(c.Password), method, err := auth.NewWebPasswordAuth(c.User, []byte(c.Password),
c.HOTPToken) c.HOTPToken)
if err != nil { if err != nil {
return nil, trace.Wrap(err) return SSHLoginResponse{}, trace.Wrap(err)
} }
clt, err := auth.NewTunClient(s.authServers[0], c.User, method) clt, err := auth.NewTunClient(s.authServers[0], c.User, method)
if err != nil { if err != nil {
return nil, trace.Wrap(err) return SSHLoginResponse{}, trace.Wrap(err)
} }
cert, err := clt.GenerateUserCert(c.PubKey, "id_"+c.User, c.User, c.TTL) cert, err := clt.GenerateUserCert(c.PubKey, "id_"+c.User, c.User, c.TTL)
if err != nil { if err != nil {
return nil, trace.Wrap(err) return SSHLoginResponse{}, trace.Wrap(err)
}
hostSigners, err := clt.GetTrustedCertificates(services.HostCert)
if err != nil {
return SSHLoginResponse{}, trace.Wrap(err)
} }
return cert, nil return SSHLoginResponse{
Cert: cert,
HostSigners: hostSigners,
}, nil
} }
func (s *LocalAuth) NewUserForm(token string) (user string, func (s *LocalAuth) NewUserForm(token string) (user string,

View file

@ -266,7 +266,13 @@ func (h *MultiSiteHandler) loginSSHProxy(w http.ResponseWriter, r *http.Request,
w.Write([]byte(trace.Wrap(err).Error())) w.Write([]byte(trace.Wrap(err).Error()))
return return
} }
w.Write(cert) out, err := json.Marshal(cert)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(trace.Wrap(err).Error()))
return
}
w.Write(out)
} }
func (s *MultiSiteHandler) siteEvents(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) error { func (s *MultiSiteHandler) siteEvents(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) error {

View file

@ -8,11 +8,13 @@ import (
"strings" "strings"
"time" "time"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/trace" "github.com/gravitational/trace"
) )
func SSHAgentLogin(proxyAddr, user, password, hotpToken string, pubKey []byte, func SSHAgentLogin(proxyAddr, user, password, hotpToken string, pubKey []byte,
ttl time.Duration) (cert []byte, err error) { ttl time.Duration) (SSHLoginResponse, error) {
cred := SSHLoginCredentials{ cred := SSHLoginCredentials{
User: user, User: user,
@ -24,7 +26,7 @@ func SSHAgentLogin(proxyAddr, user, password, hotpToken string, pubKey []byte,
credJSON, err := json.Marshal(cred) credJSON, err := json.Marshal(cred)
if err != nil { if err != nil {
return nil, trace.Wrap(err) return SSHLoginResponse{}, trace.Wrap(err)
} }
if !strings.HasPrefix(proxyAddr, "http://") { if !strings.HasPrefix(proxyAddr, "http://") {
@ -37,20 +39,26 @@ func SSHAgentLogin(proxyAddr, user, password, hotpToken string, pubKey []byte,
"credentials": []string{string(credJSON)}, "credentials": []string{string(credJSON)},
}) })
if err != nil { if err != nil {
return nil, trace.Wrap(err) return SSHLoginResponse{}, trace.Wrap(err)
} }
defer out.Body.Close() defer out.Body.Close()
body, err := ioutil.ReadAll(out.Body) body, err := ioutil.ReadAll(out.Body)
if err != nil { if err != nil {
return nil, trace.Wrap(err) return SSHLoginResponse{}, trace.Wrap(err)
} }
if out.StatusCode != 200 { if out.StatusCode != 200 {
return nil, trace.Errorf(string(body)) return SSHLoginResponse{}, trace.Errorf(string(body))
} }
return body, nil var res SSHLoginResponse
err = json.Unmarshal(body, &res)
if err != nil {
return SSHLoginResponse{}, trace.Wrap(err)
}
return res, nil
} }
type SSHLoginCredentials struct { type SSHLoginCredentials struct {
@ -60,3 +68,8 @@ type SSHLoginCredentials struct {
PubKey []byte PubKey []byte
TTL time.Duration TTL time.Duration
} }
type SSHLoginResponse struct {
Cert []byte
HostSigners []services.CertificateAuthority
}

View file

@ -21,11 +21,12 @@ import (
"os" "os"
"github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/hangout"
"github.com/gravitational/kingpin"
"github.com/gravitational/trace" "github.com/gravitational/trace"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent" "golang.org/x/crypto/ssh/agent"
"github.com/gravitational/kingpin"
) )
func RunTSH(app *kingpin.Application) error { func RunTSH(app *kingpin.Application) error {
@ -40,6 +41,7 @@ func RunTSH(app *kingpin.Application) error {
connectAddress := connect.Arg("target", "Target server address. You can provide several servers using label searching target _label:value").Required().String() connectAddress := connect.Arg("target", "Target server address. You can provide several servers using label searching target _label:value").Required().String()
connectCommand := connect.Arg("command", "Run provided command instead of shell").String() connectCommand := connect.Arg("command", "Run provided command instead of shell").String()
connectPort := connect.Flag("port", "Remote server port").Short('p').String() connectPort := connect.Flag("port", "Remote server port").Short('p').String()
connectSessionID := connect.Flag("session", "Session ID. You can connect to one shared shell from different clients using one session ID").String()
getServers := app.Command("get-servers", "Returns list of servers") getServers := app.Command("get-servers", "Returns list of servers")
getServersLabelName := getServers.Flag("label", "Label name").String() getServersLabelName := getServers.Flag("label", "Label name").String()
@ -51,6 +53,15 @@ func RunTSH(app *kingpin.Application) error {
scpIsDir := scp.Flag("recursively", "Source path is a directory").Short('r').Bool() scpIsDir := scp.Flag("recursively", "Source path is a directory").Short('r').Bool()
scpPort := scp.Flag("port", "Remote server port").Short('P').String() scpPort := scp.Flag("port", "Remote server port").Short('P').String()
share := app.Command("share", "Creates new hangout")
shareReverseProxy := share.Flag("rproxy", "Remote reverse proxy address").Required().String()
shareNodeAddress := share.Flag("node-addr", "SSH server listening address").Default(hangout.DefaultNodeAddress).String()
shareAuthAddress := share.Flag("auth-addr", "auth server listening address").Default(hangout.DefaultAuthAddress).String()
shareReadOnly := share.Flag("readonly", "Remote users can't write to the shell").Bool()
join := app.Command("join", "Join a remote hangout")
joinURL := join.Arg("url", "The url from the hangout owner").Required().String()
selectedCommand := kingpin.MustParse(app.Parse(os.Args[1:])) selectedCommand := kingpin.MustParse(app.Parse(os.Args[1:]))
if (selectedCommand == getServers.FullCommand()) && (len(*proxy) == 0) { if (selectedCommand == getServers.FullCommand()) && (len(*proxy) == 0) {
@ -67,16 +78,18 @@ func RunTSH(app *kingpin.Application) error {
} }
passwordCallback := client.GetPasswordFromConsole(*proxyUser) passwordCallback := client.GetPasswordFromConsole(*proxyUser)
webAuth, hostKeyCallback := client.NewWebAuth(
teleportFileSSHAgent,
*proxyUser,
passwordCallback,
*webProxyAddress,
*loginTTL,
)
authMethods := []ssh.AuthMethod{ authMethods := []ssh.AuthMethod{
client.AuthMethodFromAgent(standartSSHAgent), client.AuthMethodFromAgent(standartSSHAgent),
client.AuthMethodFromAgent(teleportFileSSHAgent), client.AuthMethodFromAgent(teleportFileSSHAgent),
client.NewWebAuth( webAuth,
teleportFileSSHAgent,
*proxyUser,
passwordCallback,
*webProxyAddress,
*loginTTL,
),
} }
err = trace.Errorf("No command") err = trace.Errorf("No command")
@ -84,13 +97,17 @@ func RunTSH(app *kingpin.Application) error {
switch selectedCommand { switch selectedCommand {
case connect.FullCommand(): case connect.FullCommand():
err = SSH(*connectAddress, *proxy, *connectCommand, err = SSH(*connectAddress, *proxy, *connectCommand,
*connectPort, authMethods) *connectPort, *connectSessionID, authMethods, hostKeyCallback)
case getServers.FullCommand(): case getServers.FullCommand():
err = GetServers(*proxy, *getServersLabelName, err = GetServers(*proxy, *getServersLabelName,
*getServersLabelValue, authMethods) *getServersLabelValue, authMethods, hostKeyCallback)
case scp.FullCommand(): case scp.FullCommand():
err = SCP(*proxy, *scpSource, *scpDest, *scpIsDir, *scpPort, err = SCP(*proxy, *scpSource, *scpDest, *scpIsDir, *scpPort,
authMethods) authMethods, hostKeyCallback)
case share.FullCommand():
err = Share(*proxy, *shareReverseProxy, *shareNodeAddress, *shareAuthAddress, *shareReadOnly, authMethods, hostKeyCallback)
case join.FullCommand():
err = Join(*joinURL, authMethods, hostKeyCallback)
} }
return err return err

View file

@ -21,19 +21,23 @@ import (
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"os/user"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/hangout"
"github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace" "github.com/gravitational/trace"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMethod) error { func SSH(target, proxyAddress, command, port, sessionID string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
user, target := client.SplitUserAndAddress(target) user, target := client.SplitUserAndAddress(target)
if !strings.Contains(target, ":") { if !strings.Contains(target, ":") {
target += ":" + port target += ":" + port
@ -43,11 +47,11 @@ func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMetho
return fmt.Errorf("Error: please provide user name") return fmt.Errorf("Error: please provide user name")
} }
if len(command) > 0 { if len(command) > 0 {
return client.RunCmd(user, target, proxyAddress, command, authMethods) return client.RunCmd(user, target, proxyAddress, command, authMethods, hostKeyCallback)
} }
addresses, err := client.ParseTargetServers(target, user, proxyAddress, addresses, err := client.ParseTargetServers(target, user, proxyAddress,
authMethods) authMethods, hostKeyCallback)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -59,22 +63,26 @@ func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMetho
var c *client.NodeClient var c *client.NodeClient
if len(proxyAddress) > 0 { if len(proxyAddress) > 0 {
proxyClient, err := client.ConnectToProxy(proxyAddress, authMethods, user) proxyClient, err := client.ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
defer proxyClient.Close() defer proxyClient.Close()
c, err = proxyClient.ConnectToNode(address, authMethods, user) c, err = proxyClient.ConnectToNode(address, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
} else { } else {
var err error var err error
c, err = client.ConnectToNode(nil, address, authMethods, user) c, err = client.ConnectToNode(nil, address, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
} }
return shell(c, address, sessionID)
}
func shell(c *client.NodeClient, address string, sessionID string) error {
defer c.Close() defer c.Close()
// disable input buffering // disable input buffering
@ -100,7 +108,7 @@ func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMetho
return trace.Wrap(err) return trace.Wrap(err)
} }
shell, err := c.Shell(width, height) shell, err := c.Shell(width, height, sessionID)
if err != nil { if err != nil {
// restore the console echoing state when exiting // restore the console echoing state when exiting
exec.Command("stty", "-F", "/dev/tty", "echo").Run() exec.Command("stty", "-F", "/dev/tty", "echo").Run()
@ -198,12 +206,12 @@ func getTerminalSize() (width int, height int, e error) {
return width, height, nil return width, height, nil
} }
func GetServers(proxyAddress, labelName, labelValueRegexp string, authMethods []ssh.AuthMethod) error { func GetServers(proxyAddress, labelName, labelValueRegexp string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
user, proxyAddress := client.SplitUserAndAddress(proxyAddress) user, proxyAddress := client.SplitUserAndAddress(proxyAddress)
if len(user) == 0 { if len(user) == 0 {
return fmt.Errorf("Error: please provide user name") return fmt.Errorf("Error: please provide user name")
} }
proxyClient, err := client.ConnectToProxy(proxyAddress, authMethods, user) proxyClient, err := client.ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, user)
if err != nil { if err != nil {
return trace.Wrap(err) return trace.Wrap(err)
} }
@ -235,7 +243,7 @@ func GetServers(proxyAddress, labelName, labelValueRegexp string, authMethods []
return nil return nil
} }
func SCP(proxyAddress, source, dest string, isDir bool, port string, authMethods []ssh.AuthMethod) error { func SCP(proxyAddress, source, dest string, isDir bool, port string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
if strings.Contains(source, ":") { if strings.Contains(source, ":") {
user, source := client.SplitUserAndAddress(source) user, source := client.SplitUserAndAddress(source)
if len(user) == 0 { if len(user) == 0 {
@ -249,7 +257,7 @@ func SCP(proxyAddress, source, dest string, isDir bool, port string, authMethods
targetServers += ":" + port targetServers += ":" + port
} }
return client.Download(user, targetServers, proxyAddress, path, return client.Download(user, targetServers, proxyAddress, path,
dest, isDir, authMethods) dest, isDir, authMethods, hostKeyCallback)
} else { } else {
user, dest := client.SplitUserAndAddress(dest) user, dest := client.SplitUserAndAddress(dest)
if len(user) == 0 { if len(user) == 0 {
@ -263,7 +271,80 @@ func SCP(proxyAddress, source, dest string, isDir bool, port string, authMethods
} }
return client.Upload(user, targetServers, proxyAddress, source, return client.Upload(user, targetServers, proxyAddress, source,
path, authMethods) path, authMethods, hostKeyCallback)
} }
return nil return nil
} }
func Share(proxyAddress, hangoutProxyAddress, nodeListeningAddress,
authListeningAddress string, readOnly bool, authMethods []ssh.AuthMethod,
hostKeyCallback utils.HostKeyCallback) error {
hangoutServer, err := hangout.New(hangoutProxyAddress, nodeListeningAddress,
authListeningAddress, readOnly, authMethods, hostKeyCallback)
url := proxyAddress + "/hangout/" + hangoutServer.HangoutID
fmt.Printf("\nURL:\n\n%v\n\n", url)
if err != nil {
return trace.Wrap(err)
}
u, err := user.Current()
if err != nil {
return trace.Wrap(err)
}
return SSH(u.Username+"@"+nodeListeningAddress, "", "", "", "hangoutSession", []ssh.AuthMethod{hangoutServer.ClientAuthMethod}, hangoutServer.HostKeyCallback)
}
func Join(hangoutURL string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
/*
// Debug Mode
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)
hangoutURL = "localhost:33008" + "/hangout/" + hangoutServer.HangoutID
*/
urlParts := strings.Split(hangoutURL, "/")
if len(urlParts) < 3 {
return trace.Errorf("invalid URL")
}
proxyAddress := strings.Join(urlParts[:len(urlParts)-2], "/")
hangoutID := urlParts[len(urlParts)-1 : len(urlParts)][0]
proxy, err := client.ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, "123")
if err != nil {
return trace.Wrap(err)
}
authConn, err := proxy.ConnectToHangout(hangoutID+":auth", authMethods)
if err != nil {
return trace.Wrap(err)
}
authClient, err := auth.NewClientFromSSHClient(authConn.Client)
if err != nil {
return trace.Wrap(err)
}
nodeAuthMethod, err := hangout.Authorize(authClient)
if err != nil {
return trace.Wrap(err)
}
nodeConn, err := proxy.ConnectToHangout(hangoutID+":node", []ssh.AuthMethod{nodeAuthMethod})
if err != nil {
return trace.Wrap(err)
}
fmt.Println("Connected\n")
return shell(nodeConn, hangoutID, "hangoutSession")
}

View file

@ -32,6 +32,7 @@ import (
"github.com/gravitational/teleport/lib/backend/boltbk" "github.com/gravitational/teleport/lib/backend/boltbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk" "github.com/gravitational/teleport/lib/backend/encryptedbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor" "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/events/boltlog"
"github.com/gravitational/teleport/lib/limiter" "github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/recorder/boltrec" "github.com/gravitational/teleport/lib/recorder/boltrec"
@ -90,6 +91,7 @@ var _ = Suite(&TshSuite{})
func (s *TshSuite) SetUpSuite(c *C) { func (s *TshSuite) SetUpSuite(c *C) {
utils.InitLoggerCLI() utils.InitLoggerCLI()
client.KeysDir = c.MkDir()
key, err := secret.NewKey() key, err := secret.NewKey()
c.Assert(err, IsNil) c.Assert(err, IsNil)
scrt, err := secret.New(&secret.Config{KeyBytes: key}) scrt, err := secret.New(&secret.Config{KeyBytes: key})