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
average: 500
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)
}
// 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)
func (s *AuthServer) GenerateUserCert(
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()
}
}
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 {
if err := a.permChecker.HasPermission(a.role, ActionUpsertWebTun); err != nil {
return err

View file

@ -352,6 +352,19 @@ func (c *Client) GetServers() ([]services.Server, error) {
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
// server that is valid for ttl period.
// See services.WebTun documentation for details
@ -910,6 +923,7 @@ type ClientI interface {
GetChunkReader(id string) (recorder.ChunkReadCloser, error)
UpsertServer(s services.Server, ttl time.Duration) error
GetServers() ([]services.Server, error)
GetAuthServers() ([]services.Server, error)
UpsertWebTun(wt services.WebTun, ttl time.Duration) error
GetWebTuns() ([]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[RoleUser] = map[string]bool{
ActionSignIn: true,
ActionGenerateUserCert: true,
ActionSignIn: true,
ActionGenerateUserCert: true,
ActionGetTrustedCertificates: true,
ActionGetRemoteCertificates: true,
}
sp.permissions[RoleProvisionToken] = map[string]bool{
@ -52,6 +54,7 @@ func NewStandardPermissions() PermissionChecker {
ActionUserMappingExists: true,
ActionGetUserKeys: true,
ActionGetServers: true,
ActionGetAuthServers: true,
ActionGetHostCertificateAuthority: true,
ActionUpsertParty: true,
ActionLogEntry: true,
@ -71,6 +74,59 @@ func NewStandardPermissions() PermissionChecker {
return &sp
}
func NewHangoutPermissions() PermissionChecker {
sp := standardPermissions{}
sp.permissions = make(map[string](map[string]bool))
sp.permissions[RoleUser] = map[string]bool{
ActionSignIn: true,
ActionGenerateUserCert: true,
ActionGetTrustedCertificates: true,
ActionGetRemoteCertificates: true,
}
sp.permissions[RoleProvisionToken] = map[string]bool{
ActionRegisterUsingToken: true,
ActionRegisterNewAuthServer: true,
ActionGenerateUserCert: true,
}
sp.permissions[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 {
if role == RoleAdmin {
return nil
@ -109,16 +165,23 @@ var StandardRoles = []string{
RoleSignup,
}
var HangoutRoles = []string{
RoleAdmin,
RoleProvisionToken,
RoleHangoutRemoteUser,
}
const (
PermissionRole = "role"
RoleAuth = "Auth"
RoleUser = "User"
RoleWeb = "Web"
RoleNode = "Node"
RoleAdmin = "Admin"
RoleProvisionToken = "ProvisionToken"
RoleSignup = "Signup"
RoleAuth = "Auth"
RoleUser = "User"
RoleWeb = "Web"
RoleNode = "Node"
RoleAdmin = "Admin"
RoleProvisionToken = "ProvisionToken"
RoleSignup = "Signup"
RoleHangoutRemoteUser = "HangoutRemoteUser"
ActionGetSessions = "GetSession"
ActionGetSession = "GetSession"
@ -139,6 +202,7 @@ const (
ActionGetChunkReader = "GetChunkReader"
ActionUpsertServer = "UpsertServer"
ActionGetServers = "GetServers"
ActionGetAuthServers = "GetAuthServers"
ActionUpsertWebTun = "UpsertWebTun"
ActionGetWebTuns = "GetWebTuns"
ActionGetWebTun = "GetWebTun"

View file

@ -108,6 +108,7 @@ func NewAPIServer(a *AuthWithRoles) *APIServer {
// Servers and presence heartbeat
srv.POST("/v1/servers", srv.upsertServer)
srv.GET("/v1/servers", srv.getServers)
srv.GET("/v1/authservers", srv.getAuthServers)
// Tokens
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})
}
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) {
var prefix, targetAddr, proxyAddr string
var ttl time.Duration

View file

@ -24,6 +24,7 @@ import (
"sync"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils"
@ -36,16 +37,26 @@ import (
)
type TunServer struct {
certChecker ssh.CertChecker
a *AuthServer
l net.Listener
srv *sshutils.Server
hostSigner ssh.Signer
apiServer *APIWithRoles
certChecker ssh.CertChecker
userCertChecker ssh.CertChecker
userCertAllowed bool
a *AuthServer
l net.Listener
srv *sshutils.Server
hostSigner ssh.Signer
apiServer *APIWithRoles
}
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
func NewTunServer(addr utils.NetAddr, hostSigners []ssh.Signer,
apiServer *APIWithRoles, a *AuthServer,
@ -53,9 +64,11 @@ func NewTunServer(addr utils.NetAddr, hostSigners []ssh.Signer,
opts ...ServerOption) (*TunServer, error) {
srv := &TunServer{
a: a,
apiServer: apiServer,
a: a,
apiServer: apiServer,
userCertAllowed: false,
}
for _, o := range opts {
if err := o(srv); err != nil {
return nil, err
@ -157,6 +170,20 @@ func (s *TunServer) isAuthority(auth ssh.PublicKey) bool {
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 {
if sconn.Permissions == nil {
return false
@ -244,6 +271,30 @@ func (s *TunServer) keyAuth(
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)
if err != nil {
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
}
cert, ok := key.(*ssh.Certificate)
if !ok {
return nil, trace.Wrap(err)
}
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",
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 (
ReqWebSessionAgent = "web-session-agent@teleport"
ReqProvision = "provision@teleport"

View file

@ -36,6 +36,8 @@ import (
"time"
"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/utils"
@ -60,18 +62,20 @@ type NodeClient struct {
// ConnectToProxy returns connected and authenticated ProxyClient
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")
for _, authMethod := range authMethods {
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{authMethod},
User: user,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: hostKeyCallback,
}
proxyClient, err := ssh.Dial("tcp", proxyAddress, sshConfig)
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)
continue
}
@ -166,7 +170,8 @@ func (proxy *ProxyClient) FindServers(labelName string,
// ConnectToNode connects to the ssh server via Proxy.
// 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 {
return nil, trace.Errorf("no authMethods were provided")
}
@ -217,14 +222,127 @@ func (proxy *ProxyClient) ConnectToNode(nodeAddress string, authMethods []ssh.Au
)
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{authMethod},
User: user,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: hostKeyCallback,
}
conn, chans, reqs, err := ssh.NewClientConn(pipeNetConn,
nodeAddress, sshConfig)
if err != nil {
if strings.Contains(err.Error(), "handshake failed") {
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)
proxySession.Close()
continue
@ -247,22 +365,25 @@ func (proxy *ProxyClient) Close() error {
}
// 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 {
return optionalProxy.ConnectToNode(nodeAddress, authMethods, user)
return optionalProxy.ConnectToNode(nodeAddress, authMethods, hostKeyCallback, user)
}
e := trace.Errorf("no authMethods were provided")
for _, authMethod := range authMethods {
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{authMethod},
User: user,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: hostKeyCallback,
}
client, err := ssh.Dial("tcp", nodeAddress, sshConfig)
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)
continue
}
@ -276,12 +397,19 @@ func ConnectToNode(optionalProxy *ProxyClient, nodeAddress string, authMethods [
}
// 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()
if err != nil {
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{}
err = session.RequestPty("xterm", height, width, terminalModes)

View file

@ -77,6 +77,7 @@ var _ = Suite(&ClientSuite{})
func (s *ClientSuite) SetUpSuite(c *C) {
utils.InitLoggerCLI()
KeysDir = c.MkDir()
key, err := secret.NewKey()
c.Assert(err, IsNil)
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)
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"
time.Sleep(time.Millisecond * 3100)
}
func (s *ClientSuite) TestRunCommand(c *C) {
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)
buf := bytes.Buffer{}
@ -270,11 +295,11 @@ func (s *ClientSuite) TestRunCommand(c *C) {
func (s *ClientSuite) TestConnectViaProxy(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user)
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil)
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)
buf := bytes.Buffer{}
@ -296,23 +321,24 @@ func (s *ClientSuite) TestConnectUsingSeveralAgents(c *C) {
[]ssh.AuthMethod{
AuthMethodFromAgent(agent1),
AuthMethodFromAgent(agent2),
}, s.user)
}, CheckHostSignerFromCache, s.user)
c.Assert(err, NotNil)
webAuth, hostChecker := NewWebAuth(
agent2,
s.user,
passwordCallback,
"http://"+s.webAddress,
time.Hour,
)
proxyClient, err := ConnectToProxy(
s.proxyAddress,
[]ssh.AuthMethod{
AuthMethodFromAgent(agent1),
AuthMethodFromAgent(agent2),
NewWebAuth(
agent2,
s.user,
passwordCallback,
"http://"+s.webAddress,
time.Hour,
),
webAuth,
},
s.user)
hostChecker, s.user)
c.Assert(err, IsNil)
nodeClient, err := proxyClient.ConnectToNode(
@ -321,7 +347,7 @@ func (s *ClientSuite) TestConnectUsingSeveralAgents(c *C) {
AuthMethodFromAgent(agent1),
AuthMethodFromAgent(agent2),
},
s.user)
CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil)
buf := bytes.Buffer{}
@ -336,7 +362,7 @@ func (s *ClientSuite) TestConnectUsingSeveralAgents(c *C) {
AuthMethodFromAgent(agent1),
AuthMethodFromAgent(agent2),
},
s.user)
CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil)
buf = bytes.Buffer{}
@ -347,14 +373,14 @@ func (s *ClientSuite) TestConnectUsingSeveralAgents(c *C) {
func (s *ClientSuite) TestShell(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user)
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil)
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)
shell, err := nodeClient.Shell(100, 100)
shell, err := nodeClient.Shell(100, 100, "")
c.Assert(err, IsNil)
out := make([]byte, 100)
@ -385,7 +411,7 @@ func (s *ClientSuite) TestShell(c *C) {
func (s *ClientSuite) TestGetServer(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user)
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil)
server1Info := services.Server{
@ -475,11 +501,11 @@ func (s *ClientSuite) TestGetServer(c *C) {
func (s *ClientSuite) TestUploadFile(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user)
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil)
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)
dir := c.MkDir()
@ -499,11 +525,11 @@ func (s *ClientSuite) TestUploadFile(c *C) {
func (s *ClientSuite) TestDownloadFile(c *C) {
proxyClient, err := ConnectToProxy(s.proxyAddress,
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, s.user)
[]ssh.AuthMethod{s.teleagent.AuthMethod()}, CheckHostSignerFromCache, s.user)
c.Assert(err, IsNil)
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)
dir := c.MkDir()
@ -523,7 +549,7 @@ func (s *ClientSuite) TestDownloadFile(c *C) {
func (s *ClientSuite) TestUploadDir(c *C) {
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)
dir1 := c.MkDir()
@ -553,7 +579,7 @@ func (s *ClientSuite) TestUploadDir(c *C) {
func (s *ClientSuite) TestDownloadDir(c *C) {
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)
dir1 := c.MkDir()
@ -618,11 +644,11 @@ func (s *ClientSuite) TestHOTPMock(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(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(len(addresses), Equals, 2)
if addresses[0] == s.srvAddress {
@ -631,7 +657,7 @@ func (s *ClientSuite) TestParseTargetObject(c *C) {
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(addresses, DeepEquals, []string{s.srvAddress})

View file

@ -23,17 +23,63 @@ package client
import (
"encoding/json"
"io/ioutil"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/gravitational/teleport/lib/backend/boltbk"
"github.com/gravitational/teleport/lib/services"
log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
"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
// creates ssh agent with them
func GetLocalAgent() (agent.Agent, error) {
@ -152,8 +198,9 @@ func loadAllKeys() ([]Key, error) {
return keys, nil
}
const (
KeysDir = "/tmp/teleport"
KeyFilePrefix = "teleport_"
KeyFileSuffix = ".tkey"
var (
KeysDir = "/tmp/teleport"
KeyFilePrefix = "teleport_"
KeyFileSuffix = ".tkey"
HostSignersFilename = "HostSigners.db"
)

View file

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

View file

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

View file

@ -44,6 +44,10 @@ const (
// run behind an environment/firewall which only allows outgoing connections)
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
// serve auth requests.
AuthListenPort = 3025
@ -126,6 +130,13 @@ func ReverseTunnellListenAddr() *utils.NetAddr {
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 {
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 {
addr utils.NetAddr
elog lunk.EventLogger
clt *auth.TunClient
signers []ssh.Signer
domainName string
waitC chan bool
disconnectC chan bool
conn ssh.Conn
addr utils.NetAddr
elog lunk.EventLogger
clt *auth.TunClient
domainName string
waitC chan bool
disconnectC chan bool
conn ssh.Conn
hostKeyCallback utils.HostKeyCallback
authMethods []ssh.AuthMethod
}
type AgentOption func(a *Agent) error
@ -60,9 +61,35 @@ func NewAgent(addr utils.NetAddr, domainName string, signers []ssh.Signer,
clt: clt,
addr: addr,
domainName: domainName,
signers: signers,
waitC: make(chan bool),
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 {
if err := o(a); err != nil {
@ -148,15 +175,25 @@ func (a *Agent) connect() error {
return err
}
log.Infof("agent connectting to %v", a.addr.FullAddress())
c, err := ssh.Dial(a.addr.AddrNetwork, a.addr.Addr, &ssh.ClientConfig{
User: a.domainName,
Auth: []ssh.AuthMethod{ssh.PublicKeys(a.signers...)},
HostKeyCallback: a.checkHostSignature,
})
if err != nil {
var c *ssh.Client
var err error
for _, authMethod := range a.authMethods {
c, err = ssh.Dial(a.addr.AddrNetwork, a.addr.Addr, &ssh.ClientConfig{
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)
return trace.Wrap(err)
}
a.conn = c
go a.startHeartbeat()

View file

@ -46,6 +46,7 @@ type RemoteSite interface {
GetStatus() string
GetClient() *auth.Client
GetServers() ([]services.Server, error)
GetHangoutInfo() (hostKey *services.CertificateAuthority, OSUser, AuthPort, NodePort string)
}
type Server interface {
@ -63,6 +64,7 @@ type server struct {
certChecker ssh.CertChecker
l net.Listener
srv *sshutils.Server
upsertSite func(c ssh.Conn) (*remoteSite, error)
sites []*remoteSite
}
@ -87,6 +89,32 @@ func NewServer(addr utils.NetAddr, hostSigners []ssh.Signer,
return nil, err
}
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
return srv, nil
}
@ -144,6 +172,20 @@ func (s *server) isAuthority(auth ssh.PublicKey) bool {
return false
}
// isAuthority is called during checking the client key, to see if the signing
// key is the real CA authority key.
func (s *server) isHangoutAuthority(auth ssh.PublicKey) bool {
_, found, err := s.ap.GetCertificateID(services.UserCert, auth)
if err != nil {
log.Errorf("failed to retrieve trused keys, err: %v", err)
return false
}
if found {
return true
}
return false
}
func (s *server) getTrustedCAKeys() ([]ssh.PublicKey, error) {
out := []ssh.PublicKey{}
authKeys := [][]byte{}
@ -155,6 +197,7 @@ func (s *server) getTrustedCAKeys() ([]ssh.PublicKey, error) {
certs, err := s.ap.GetRemoteCertificates(services.HostCert, "")
if err != nil {
log.Errorf(err.Error())
return nil, err
}
for _, c := range certs {
@ -201,7 +244,44 @@ func (s *server) keyAuth(
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()
defer s.Unlock()
@ -227,6 +307,56 @@ func (s *server) upsertSite(c ssh.Conn) (*remoteSite, error) {
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 {
s.RLock()
defer s.RUnlock()
@ -245,7 +375,7 @@ func (s *server) GetSite(domainName string) (RemoteSite, error) {
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.
@ -289,6 +419,11 @@ type remoteSite struct {
lastActive time.Time
srv *server
clt *auth.Client
hangoutHostKey *services.CertificateAuthority
hangoutOSUser string
hangoutAuthPort string
hangoutNodePort string
}
func (s *remoteSite) GetClient() *auth.Client {
@ -316,7 +451,7 @@ func (s *remoteSite) init(c ssh.Conn) error {
log.Errorf("remoteSite:authProxy %v", err)
return nil, err
}
return newChConn(s.conn, ch), nil
return utils.NewChConn(s.conn, ch), nil
},
}
clt, err := auth.NewClient(
@ -374,7 +509,7 @@ func (s *remoteSite) ConnectToServer(server, user string, auth []ssh.AuthMethod)
if !dialed {
return nil, trace.Errorf("remote server %v is not available", server)
}
transportConn := newChConn(s.conn, ch)
transportConn := utils.NewChConn(s.conn, ch)
conn, chans, reqs, err := ssh.NewClientConn(
transportConn, server,
&ssh.ClientConfig{
@ -419,7 +554,7 @@ func (s *remoteSite) DialServer(server string) (net.Conn, error) {
if !dialed {
return nil, trace.Errorf("remote server %v is not available", server)
}
return newChConn(s.conn, ch), nil
return utils.NewChConn(s.conn, ch), nil
}
func (s *remoteSite) GetServers() ([]services.Server, error) {
@ -434,7 +569,7 @@ func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
log.Errorf("remoteSite:authProxy %v", err)
return nil, err
}
return newChConn(s.conn, ch), nil
return utils.NewChConn(s.conn, ch), nil
},
}
@ -449,36 +584,8 @@ func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
fwd.ServeHTTP(w, r)
}
func newChConn(conn ssh.Conn, ch ssh.Channel) *chConn {
c := &chConn{}
c.Channel = ch
c.conn = conn
return c
}
type chConn struct {
ssh.Channel
conn ssh.Conn
}
func (c *chConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *chConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *chConn) SetDeadline(t time.Time) error {
return nil
}
func (c *chConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *chConn) SetWriteDeadline(t time.Time) error {
return nil
func (s *remoteSite) GetHangoutInfo() (hostKey *services.CertificateAuthority, OSUser, AuthPort, NodePort string) {
return s.hangoutHostKey, s.hangoutOSUser, s.hangoutAuthPort, s.hangoutNodePort
}
const ExtHost = "host@teleport"

View file

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

View file

@ -408,13 +408,32 @@ func initProxyEndpoint(supervisor Supervisor, cfg Config) error {
return trace.Wrap(err)
}
var hsrv reversetunnel.Server
if cfg.Proxy.HangoutsEnabled {
hsrv, err = reversetunnel.NewHangoutServer(
cfg.Proxy.HangoutsListenAddr,
[]ssh.Signer{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,
cfg.Hostname,
[]ssh.Signer{i.KeySigner},
client,
proxyLimiter,
cfg.DataDir,
srv.SetProxyMode(tsrv),
options...,
)
if err != nil {
return trace.Wrap(err)
@ -432,6 +451,20 @@ func initProxyEndpoint(supervisor Supervisor, cfg Config) error {
return nil
})
if cfg.Proxy.HangoutsEnabled {
// register SSH reverse tunnel server that accepts connections
// from remote teleport nodes
supervisor.RegisterFunc(func() error {
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
supervisor.RegisterFunc(func() error {
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
}
// 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 {
ID string `json:"id"`
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) {
switch role {
case "Auth":
case TokenRoleAuth:
return "a" + token, nil
case "Node":
case TokenRoleNode:
return "n" + token, nil
}
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")
}
if outputToken[0] == 'n' {
return outputToken[1:], "Node", nil
return outputToken[1:], TokenRoleNode, nil
}
if outputToken[0] == 'a' {
return outputToken[1:], "Auth", nil
return outputToken[1:], TokenRoleAuth, nil
}
return outputToken, "", trace.Errorf("Unknown role")
}
@ -94,3 +94,8 @@ type ProvisionToken struct {
DomainName 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"
"code.google.com/p/go-uuid/uuid"
"github.com/codahale/lunk"
log "github.com/Sirupsen/logrus"
"github.com/codahale/lunk"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
// Server implements SSH server that uses configuration backend and certificate-based authentication:
@ -68,8 +68,9 @@ type Server struct {
certificatesCache *services.CAService
proxyMode bool
proxyTun reversetunnel.Server
proxyMode bool
proxyTun reversetunnel.Server
hangoutsTun reversetunnel.Server
}
type ServerOption func(s *Server) error
@ -110,6 +111,13 @@ func SetProxyMode(tsrv reversetunnel.Server) ServerOption {
}
}
func EnableHangouts(hangoutsTun reversetunnel.Server) ServerOption {
return func(s *Server) error {
s.hangoutsTun = hangoutsTun
return nil
}
}
func SetLabels(labels map[string]string,
cmdLabels services.CommandLabels) ServerOption {
return func(s *Server) error {
@ -147,6 +155,11 @@ func New(addr utils.NetAddr, hostname string, signers []ssh.Signer,
return nil, err
}
}
if (s.hangoutsTun != nil) && (!s.proxyMode) {
return nil, trace.Errorf("Hangout available only in proxy mode")
}
if s.elog == nil {
s.elog = utils.NullEventLogger
}
@ -417,7 +430,6 @@ func (s *Server) HandleRequest(r *ssh.Request) {
func (s *Server) HandleNewChan(sconn *ssh.ServerConn, nch ssh.NewChannel) {
channelType := nch.ChannelType()
if s.proxyMode {
if channelType == "session" { // interactive sessions
ch, requests, err := nch.Accept()

View file

@ -50,6 +50,9 @@ func parseSubsystemRequest(srv *Server, req *ssh.Request) (subsystem, error) {
if srv.proxyMode && strings.HasPrefix(s.Name, "proxy:") {
return parseProxySubsys(s.Name, srv)
}
if srv.proxyMode && srv.hangoutsTun != nil && strings.HasPrefix(s.Name, "hangout:") {
return parseHangoutsSubsys(s.Name, srv)
}
if srv.proxyMode && strings.HasPrefix(s.Name, "proxysites") {
return parseProxySitesSubsys(s.Name, srv)
}

View file

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

View file

@ -20,6 +20,8 @@ import (
"io"
"net"
"time"
"golang.org/x/crypto/ssh"
)
// PipeNetConn implemetns net.Conn from io.Reader,io.Writer and io.Closer
@ -81,3 +83,35 @@ func SplitReaders(r1 io.Reader, r2 io.Reader) io.Reader {
go io.Copy(writer, r2)
return reader
}
func NewChConn(conn ssh.Conn, ch ssh.Channel) *chConn {
c := &chConn{}
c.Channel = ch
c.conn = conn
return c
}
type chConn struct {
ssh.Channel
conn ssh.Conn
}
func (c *chConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *chConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *chConn) SetDeadline(t time.Time) error {
return nil
}
func (c *chConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *chConn) SetWriteDeadline(t time.Time) error {
return nil
}

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"
"io"
"io/ioutil"
"math/rand"
"net"
"os"
"path/filepath"
"strconv"
"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) {
s, err := filepath.Abs(path)
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 (
CertExtensionUser = "x-teleport-user"
CertExtensionRole = "x-teleport-role"

View file

@ -23,6 +23,7 @@ import (
"net/http"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils"
@ -123,7 +124,7 @@ type RequestHandler func(http.ResponseWriter, *http.Request, httprouter.Params,
type AuthHandler interface {
GetHost() string
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)
NewUserFinish(token, password, hotpToken string) 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))
}
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),
c.HOTPToken)
if err != nil {
return nil, trace.Wrap(err)
return SSHLoginResponse{}, trace.Wrap(err)
}
clt, err := auth.NewTunClient(s.authServers[0], c.User, method)
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)
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,

View file

@ -266,7 +266,13 @@ func (h *MultiSiteHandler) loginSSHProxy(w http.ResponseWriter, r *http.Request,
w.Write([]byte(trace.Wrap(err).Error()))
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 {

View file

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

View file

@ -21,11 +21,12 @@ import (
"os"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/hangout"
"github.com/gravitational/kingpin"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/gravitational/kingpin"
)
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()
connectCommand := connect.Arg("command", "Run provided command instead of shell").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")
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()
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:]))
if (selectedCommand == getServers.FullCommand()) && (len(*proxy) == 0) {
@ -67,16 +78,18 @@ func RunTSH(app *kingpin.Application) error {
}
passwordCallback := client.GetPasswordFromConsole(*proxyUser)
webAuth, hostKeyCallback := client.NewWebAuth(
teleportFileSSHAgent,
*proxyUser,
passwordCallback,
*webProxyAddress,
*loginTTL,
)
authMethods := []ssh.AuthMethod{
client.AuthMethodFromAgent(standartSSHAgent),
client.AuthMethodFromAgent(teleportFileSSHAgent),
client.NewWebAuth(
teleportFileSSHAgent,
*proxyUser,
passwordCallback,
*webProxyAddress,
*loginTTL,
),
webAuth,
}
err = trace.Errorf("No command")
@ -84,13 +97,17 @@ func RunTSH(app *kingpin.Application) error {
switch selectedCommand {
case connect.FullCommand():
err = SSH(*connectAddress, *proxy, *connectCommand,
*connectPort, authMethods)
*connectPort, *connectSessionID, authMethods, hostKeyCallback)
case getServers.FullCommand():
err = GetServers(*proxy, *getServersLabelName,
*getServersLabelValue, authMethods)
*getServersLabelValue, authMethods, hostKeyCallback)
case scp.FullCommand():
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

View file

@ -21,19 +21,23 @@ import (
"os"
"os/exec"
"os/signal"
"os/user"
"strings"
"sync"
"syscall"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/hangout"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace"
"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)
if !strings.Contains(target, ":") {
target += ":" + port
@ -43,11 +47,11 @@ func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMetho
return fmt.Errorf("Error: please provide user name")
}
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,
authMethods)
authMethods, hostKeyCallback)
if err != nil {
return trace.Wrap(err)
}
@ -59,22 +63,26 @@ func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMetho
var c *client.NodeClient
if len(proxyAddress) > 0 {
proxyClient, err := client.ConnectToProxy(proxyAddress, authMethods, user)
proxyClient, err := client.ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, user)
if err != nil {
return trace.Wrap(err)
}
defer proxyClient.Close()
c, err = proxyClient.ConnectToNode(address, authMethods, user)
c, err = proxyClient.ConnectToNode(address, authMethods, hostKeyCallback, user)
if err != nil {
return trace.Wrap(err)
}
} else {
var err error
c, err = client.ConnectToNode(nil, address, authMethods, user)
c, err = client.ConnectToNode(nil, address, authMethods, hostKeyCallback, user)
if err != nil {
return trace.Wrap(err)
}
}
return shell(c, address, sessionID)
}
func shell(c *client.NodeClient, address string, sessionID string) error {
defer c.Close()
// disable input buffering
@ -100,7 +108,7 @@ func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMetho
return trace.Wrap(err)
}
shell, err := c.Shell(width, height)
shell, err := c.Shell(width, height, sessionID)
if err != nil {
// restore the console echoing state when exiting
exec.Command("stty", "-F", "/dev/tty", "echo").Run()
@ -198,12 +206,12 @@ func getTerminalSize() (width int, height int, e error) {
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)
if len(user) == 0 {
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 {
return trace.Wrap(err)
}
@ -235,7 +243,7 @@ func GetServers(proxyAddress, labelName, labelValueRegexp string, authMethods []
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, ":") {
user, source := client.SplitUserAndAddress(source)
if len(user) == 0 {
@ -249,7 +257,7 @@ func SCP(proxyAddress, source, dest string, isDir bool, port string, authMethods
targetServers += ":" + port
}
return client.Download(user, targetServers, proxyAddress, path,
dest, isDir, authMethods)
dest, isDir, authMethods, hostKeyCallback)
} else {
user, dest := client.SplitUserAndAddress(dest)
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,
path, authMethods)
path, authMethods, hostKeyCallback)
}
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/encryptedbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/events/boltlog"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/recorder/boltrec"
@ -90,6 +91,7 @@ var _ = Suite(&TshSuite{})
func (s *TshSuite) SetUpSuite(c *C) {
utils.InitLoggerCLI()
client.KeysDir = c.MkDir()
key, err := secret.NewKey()
c.Assert(err, IsNil)
scrt, err := secret.New(&secret.Config{KeyBytes: key})