mirror of
https://github.com/gravitational/teleport
synced 2024-10-22 02:03:24 +00:00
merged from alex/sharing
This commit is contained in:
commit
bcb6411a7b
|
@ -67,3 +67,5 @@ proxy:
|
|||
- period: 1s
|
||||
average: 500
|
||||
burst: 300
|
||||
hangouts_enabled: true
|
||||
hangouts_listen_addr: tcp://localhost:33009
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
462
lib/hangout/hangout.go
Normal 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"
|
|
@ -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()
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
54
lib/services/hangouts.go
Normal 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
|
||||
}
|
||||
*/
|
|
@ -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"`
|
||||
|
|
|
@ -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
135
lib/srv/hangouts.go
Normal 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
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
52
lib/utils/hangout.go
Normal 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
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
105
tool/tsh/tsh.go
105
tool/tsh/tsh.go
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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})
|
||||
|
|
Loading…
Reference in a new issue