working hangouts

This commit is contained in:
Alex Lyulkov 2016-02-16 15:51:33 +03:00
parent f35f74cb46
commit 66dd4436e9
20 changed files with 725 additions and 102 deletions

View file

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

View file

@ -191,7 +191,7 @@ func (s *AuthServer) CreateToken() (string, error) {
return "", err
}
return string(p.SID)
return string(p.SID), nil
}
func (s *AuthServer) GenerateToken(domainName, role string, ttl time.Duration) (string, error) {

View file

@ -36,6 +36,7 @@ func NewStandardPermissions() PermissionChecker {
ActionSignIn: true,
ActionGenerateUserCert: true,
ActionGetTrustedCertificates: true,
ActionGetRemoteCertificates: true,
}
sp.permissions[RoleProvisionToken] = map[string]bool{
@ -73,6 +74,53 @@ func NewStandardPermissions() PermissionChecker {
return &sp
}
func NewHangoutPermissions() PermissionChecker {
sp := standardPermissions{}
sp.permissions = make(map[string](map[string]bool))
sp.permissions[RoleUser] = map[string]bool{
ActionSignIn: true,
ActionGenerateUserCert: true,
ActionGetTrustedCertificates: true,
ActionGetRemoteCertificates: true,
}
sp.permissions[RoleProvisionToken] = map[string]bool{
ActionRegisterUsingToken: true,
ActionRegisterNewAuthServer: true,
ActionGenerateUserCert: true,
}
sp.permissions[RoleNode] = map[string]bool{
ActionUpsertServer: true,
ActionGetUserCertificateAuthority: true,
ActionGetRemoteCertificates: true,
ActionGetTrustedCertificates: true,
ActionGetCertificateID: true,
ActionGetAllUserMappings: true,
ActionUserMappingExists: true,
ActionGetUserKeys: true,
ActionGetServers: true,
ActionGetAuthServers: true,
ActionGetHostCertificateAuthority: true,
ActionUpsertParty: true,
ActionLogEntry: true,
ActionGetChunkWriter: true,
}
sp.permissions[RoleWeb] = map[string]bool{
ActionGetWebSession: true,
ActionDeleteWebSession: true,
}
sp.permissions[RoleSignup] = map[string]bool{
ActionGetSignupTokenData: true,
ActionCreateUserWithToken: true,
}
return &sp
}
func (sp *standardPermissions) HasPermission(role, action string) error {
if role == RoleAdmin {
return nil
@ -114,6 +162,7 @@ var StandardRoles = []string{
var HangoutRoles = []string{
RoleAdmin,
RoleHangoutUser,
RoleProvisionToken,
}
const (

View file

@ -530,6 +530,30 @@ func (t *TunDialer) Dial(network, address string) (net.Conn, error) {
}
}
func NewClientFromSSHClient(sshClient *ssh.Client) (*Client, error) {
tr := &http.Transport{
Dial: sshClient.Dial,
/*Dial: func(network, addr string) (net.Conn, error) {
ch, _, err := conn.OpenChannel(ReqDirectTCPIP, nil)
if err != nil {
log.Errorf(err.Error())
return nil, err
}
return utils.NewChConn(conn, ch), nil
},*/
}
clt, err := NewClient(
"http://stub:0",
roundtrip.HTTPClient(&http.Client{
Transport: tr,
}))
if err != nil {
return nil, trace.Wrap(err)
}
return clt, nil
}
const (
ReqWebSessionAgent = "web-session-agent@teleport"
ReqProvision = "provision@teleport"

View file

@ -247,6 +247,88 @@ func (proxy *ProxyClient) ConnectToNode(nodeAddress string, authMethods []ssh.Au
return nil, e
}
// ConnectToNode connects to the ssh server via Proxy.
// It returns connected and authenticated NodeClient
//DialHangout
func (proxy *ProxyClient) ConnectToHangout(nodeAddress string, authMethods []ssh.AuthMethod,
hostKeyCallback utils.HostKeyCallback, user string) (*NodeClient, error) {
if len(authMethods) == 0 {
return nil, trace.Errorf("no authMethods were provided")
}
proxy.Lock()
defer proxy.Unlock()
e := trace.Errorf("unknown Error")
for _, authMethod := range authMethods {
proxySession, err := proxy.Client.NewSession()
if err != nil {
return nil, trace.Wrap(err)
}
proxyWriter, err := proxySession.StdinPipe()
if err != nil {
return nil, trace.Wrap(err)
}
proxyReader, err := proxySession.StdoutPipe()
if err != nil {
return nil, trace.Wrap(err)
}
err = proxySession.RequestSubsystem(fmt.Sprintf("hangout:%v", nodeAddress))
if err != nil {
return nil, trace.Wrap(err)
}
localAddr, err := utils.ParseAddr("tcp://" + proxy.proxyAddress)
if err != nil {
return nil, trace.Wrap(err)
}
remoteAddr, err := utils.ParseAddr("tcp://" + nodeAddress)
if err != nil {
return nil, trace.Wrap(err)
}
pipeNetConn := utils.NewPipeNetConn(
proxyReader,
proxyWriter,
proxySession,
localAddr,
remoteAddr,
)
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: hostKeyCallback,
}
conn, chans, reqs, err := ssh.NewClientConn(pipeNetConn,
nodeAddress, sshConfig)
if err != nil {
if strings.Contains(err.Error(), "handshake failed") ||
strings.Contains(err.Error(), "CheckHostSigners") {
e = trace.Wrap(err)
proxySession.Close()
continue
}
return nil, trace.Wrap(err)
}
client := ssh.NewClient(conn, chans, reqs)
if err != nil {
return nil, trace.Wrap(err)
}
return &NodeClient{Client: client}, nil
}
return nil, e
}
func (proxy *ProxyClient) Close() error {
return proxy.Client.Close()
}

View file

@ -32,6 +32,7 @@ import (
"time"
"github.com/gravitational/teleport/lib/auth/native"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/web"
"github.com/gravitational/trace"
@ -60,6 +61,7 @@ func NewWebAuth(ag agent.Agent,
callbackFunc := func() (signers []ssh.Signer, err error) {
err = Login(ag, webProxyAddress, user, certificateTTL, passwordCallback)
if err != nil {
fmt.Printf("Can't login to the server: %v\n", err)
return nil, trace.Wrap(err)
}
@ -71,6 +73,7 @@ func NewWebAuth(ag agent.Agent,
if err != nil {
err = Login(ag, webProxyAddress, user, certificateTTL, passwordCallback)
if err != nil {
fmt.Printf("Can't login to the server: %v\n", err)
return trace.Wrap(err)
}
return CheckHostSignerFromCache(hostname, remote, key)

View file

@ -22,37 +22,44 @@ import (
"os/user"
//"path/filepath"
//"strings"
//"time"
"net"
"os"
"path"
"time"
"github.com/gravitational/teleport/lib/auth"
authority "github.com/gravitational/teleport/lib/auth/native"
//"github.com/gravitational/teleport/lib/backend/boltbk"
//"github.com/gravitational/teleport/lib/backend/encryptedbk"
//"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/boltbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor"
"github.com/gravitational/teleport/lib/backend/etcdbk"
"github.com/gravitational/teleport/lib/events"
//"github.com/gravitational/teleport/lib/events/boltlog"
"github.com/gravitational/teleport/lib/events/boltlog"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/recorder"
//"github.com/gravitational/teleport/lib/recorder/boltrec"
"github.com/gravitational/teleport/lib/recorder/boltrec"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/service"
//"github.com/gravitational/teleport/lib/services"
//sess "github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/srv"
//"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/session"
//"github.com/gravitational/session"
log "github.com/Sirupsen/logrus"
//"github.com/gravitational/log"
"github.com/gravitational/trace"
//"github.com/gokyle/hotp"
//"github.com/mailgun/lemma/secret"
"github.com/codahale/lunk"
"golang.org/x/crypto/ssh"
//"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh/agent"
)
type Hangout struct {
auth auth.AuthServer
auth *auth.AuthServer
tunClt reversetunnel.Agent
elog events.Log
rec recorder.Recorder
@ -62,45 +69,53 @@ type Hangout struct {
authPort string
ClientAuthMethod ssh.AuthMethod
HostKeyCallback utils.HostKeyCallback
client *auth.TunClient
passwordToken string
HangoutInfo HangoutInfo
Token string
}
func New(proxyTunnelAddress, nodeListeningAddress, authListeningAddress string,
readOnly bool, authMethods []ssh.AuthMethod,
hostKeyCallback utils.HostKeyCallback) (*Hangout, error) {
//log.SetOutput(os.Stderr)
//log.SetLevel(log.InfoLevel)
//log.Initialize("console", "INFO")
cfg := service.Config{}
service.SetDefaults(&cfg)
cfg.DataDir = "/tmp/teleporthangout"
cfg.DataDir = HangoutDataDir
cfg.Hostname = "localhost"
cfg.Auth.HostAuthorityDomain = "localhost"
cfg.Auth.KeysBackend.Type = "bolt"
cfg.Auth.KeysBackend.Params = `{"path": "` + DataDir + `/teleport.auth.db"}`
cfg.Auth.KeysBackend.Params = `{"path": "` + cfg.DataDir + `/teleport.auth.db"}`
cfg.Auth.EventsBackend.Type = "bolt"
cfg.Auth.EventsBackend.Params = `{"path": "` + DataDir + `/teleport.event.db"}`
cfg.Auth.EventsBackend.Params = `{"path": "` + cfg.DataDir + `/teleport.event.db"}`
cfg.Auth.RecordsBackend.Type = "bolt"
cfg.Auth.RecordsBackend.Params = `{"path": "` + DataDir + `/teleport.records.db"}`
authAddress, err := utils.ParseAddr(authListeningAddress)
cfg.Auth.RecordsBackend.Params = `{"path": "` + cfg.DataDir + `/teleport.records.db"}`
authAddress, err := utils.ParseAddr("tcp://" + authListeningAddress)
if err != nil {
return nil, trace.Wrap(err)
}
cfg.Auth.SSHAddr = authAddress
cfg.Auth.SSHAddr = *authAddress
cfg.AuthServers = []utils.NetAddr{cfg.Auth.SSHAddr}
nodeAddress, err := utils.ParseAddr(nodeListeningAddress)
nodeAddress, err := utils.ParseAddr("tcp://" + nodeListeningAddress)
if err != nil {
return nil, trace.Wrap(err)
}
cfg.SSH.Addr = nodeAddress
cfg.SSH.Addr = *nodeAddress
tunnelAddress, err := utils.ParseAddr(proxyTunnelAddress)
tunnelAddress, err := utils.ParseAddr("tcp://" + proxyTunnelAddress)
if err != nil {
return nil, trace.Wrap(err)
}
cfg.ReverseTunnel.DialAddr = tunnelAddress
cfg.ReverseTunnel.DialAddr = *tunnelAddress
_, err := os.Stat(cfg.DataDir)
_, err = os.Stat(cfg.DataDir)
if os.IsNotExist(err) {
err := os.MkdirAll(cfg.DataDir, os.ModeDir|0777)
if err != nil {
@ -113,19 +128,31 @@ func New(proxyTunnelAddress, nodeListeningAddress, authListeningAddress string,
return nil, trace.Wrap(err)
}
var err error
h.hangoutID, err = h.auth.CreateToken()
if err != nil {
return nil, trace.Wrap(err)
}
thisSrv := services.Server{
ID: cfg.Auth.SSHAddr.Addr,
Addr: cfg.Auth.SSHAddr.Addr,
Hostname: h.hangoutID,
}
err = h.auth.UpsertServer(thisSrv, 0)
if err != nil {
return nil, trace.Wrap(err)
}
log.Infof("***************** init SSH")
if err := h.initSSHEndpoint(cfg); err != nil {
return nil, trace.Wrap(err)
}
log.Infof("***************** init Tun Agent")
if err := h.initTunAgent(cfg, authMethods, hostKeyCallback); err != nil {
return nil, trace.Wrap(err)
}
log.Infof("***************** create User")
if err := h.createUser(); err != nil {
return nil, trace.Wrap(err)
}
@ -139,12 +166,25 @@ func New(proxyTunnelAddress, nodeListeningAddress, authListeningAddress string,
return nil, trace.Wrap(err)
}
h.ClientAuthMethod, h.HostKeyCallback, err = Authorize(auth, h.userPassword)
h.ClientAuthMethod, h.HostKeyCallback, err = Authorize(h.client, h.userPassword)
h.HangoutInfo.AuthPort = h.authPort
h.HangoutInfo.NodePort = h.nodePort
h.HangoutInfo.HangoutID = h.hangoutID
h.HangoutInfo.HangoutPassword = h.passwordToken
h.Token, err = MarshalHangoutInfo(&h.HangoutInfo)
if err != nil {
return nil, trace.Wrap(err)
}
return h, nil
}
func (h *Hangout) createUser() error {
var err error
h.userPassword, err = h.auth.GenerateToken(HangoutUser, services.TokenRoleHangout, 0)
h.passwordToken, err = h.auth.GenerateToken(HangoutUser, services.TokenRoleHangout, 0)
h.userPassword = h.passwordToken[0:100]
if err != nil {
return trace.Wrap(err)
}
@ -154,18 +194,24 @@ func (h *Hangout) createUser() error {
return trace.Wrap(err)
}
osUser := u.Username
h.HangoutInfo.OSUser = osUser
_, _, err := s.a.UpsertPassword(HangoutUser, h.userPassword)
_, _, err = h.auth.UpsertPassword(HangoutUser, []byte(h.userPassword))
if err != nil {
return trace.Wrap(err)
}
err = h.auth.UpsertUserMapping("local", HangoutUser, osUser, 0)
if err != nil {
return trace.Wrap(err)
}
return nil
}
func Authorize(auth auth.Client, userPassword string) (ssh.AuthMethod, utils.HostKeyCallback, error) {
func Authorize(auth auth.ClientI, userPassword string) (ssh.AuthMethod, utils.HostKeyCallback, error) {
priv, pub, err := native.New().GenerateKeyPair("")
priv, pub, err := authority.New().GenerateKeyPair("")
if err != nil {
return nil, nil, trace.Wrap(err)
}
@ -174,7 +220,7 @@ func Authorize(auth auth.Client, userPassword string) (ssh.AuthMethod, utils.Hos
return nil, nil, trace.Wrap(err)
}
pcert, _, _, _, err := ssh.ParseAuthorizedKey(login.Cert)
pcert, _, _, _, err := ssh.ParseAuthorizedKey(cert)
if err != nil {
return nil, nil, trace.Wrap(err)
}
@ -244,8 +290,9 @@ func (h *Hangout) initAuth(cfg service.Config, readOnlyHangout bool) error {
if err != nil {
return trace.Wrap(err)
}
apisrv := auth.NewAPIWithRoles(asrv, elog, session.New(b), rec,
auth.NewStandardPermissions(), auth.HangoutRoles,
h.auth = asrv
apisrv := auth.NewAPIWithRoles(asrv, h.elog, session.New(b), h.rec,
auth.NewHangoutPermissions(), auth.HangoutRoles,
)
go apisrv.Serve()
@ -270,16 +317,10 @@ func (h *Hangout) initAuth(cfg service.Config, readOnlyHangout bool) error {
}
}()
thisSrv := services.Server{
ID: cfg.Auth.SSHAddr.Addr(),
Addr: cfg.Auth.SSHAddr.Addr(),
Hostname: h.HangoutID,
}
err := asrv.UpsertServer(thisSrv, 0)
return nil
}
func (h *Hangout) initTunAgent(cfg Config, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
func (h *Hangout) initTunAgent(cfg service.Config, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
signer, err := auth.ReadKeys(cfg.Hostname, cfg.DataDir)
if err != nil {
return trace.Wrap(err)
@ -293,6 +334,8 @@ func (h *Hangout) initTunAgent(cfg Config, authMethods []ssh.AuthMethod, hostKey
return trace.Wrap(err)
}
h.client = client
elog := &service.FanOutEventLogger{
Loggers: []lunk.EventLogger{
lunk.NewTextEventLogger(log.StandardLogger().Writer()),
@ -311,10 +354,11 @@ func (h *Hangout) initTunAgent(cfg Config, authMethods []ssh.AuthMethod, hostKey
}
log.Infof("[REVERSE TUNNEL] teleport tunnel agent starting")
if err := a.Start(); err != nil {
log.Fatalf("failed to start: %v", err)
}
go func() {
if err := a.Start(); err != nil {
log.Fatalf("failed to start: %v", err)
}
if err := a.Wait(); err != nil {
log.Fatalf("failed to start: %v", err)
}
@ -322,7 +366,7 @@ func (h *Hangout) initTunAgent(cfg Config, authMethods []ssh.AuthMethod, hostKey
return nil
}
func (h *Hangout) initSSHEndpoint(cfg Config) error {
func (h *Hangout) initSSHEndpoint(cfg service.Config) error {
signer, err := auth.ReadKeys(cfg.Hostname, cfg.DataDir)
if err != nil {
return trace.Wrap(err)
@ -349,7 +393,7 @@ func (h *Hangout) initSSHEndpoint(cfg Config) error {
}
s, err := srv.New(cfg.SSH.Addr,
cfg.Hostname,
h.hangoutID,
[]ssh.Signer{signer},
client,
limiter,
@ -367,18 +411,86 @@ func (h *Hangout) initSSHEndpoint(cfg Config) error {
log.Infof("[SSH] server is starting on %v", cfg.SSH.Addr)
go func() {
if err := s.Start(); err != nil {
log.Errorf(err)
}
if err := s.Wait(); err != nil {
log.Errorf(err)
log.Errorf(err.Error())
}
s.Wait()
}()
return nil
}
func initBackend(dataDir, domainName string, peers service.NetAddrSlice, cfg service.AuthConfig) (*encryptedbk.ReplicatedBackend, error) {
var bk backend.Backend
var err error
switch cfg.KeysBackend.Type {
case "etcd":
bk, err = etcdbk.FromJSON(cfg.KeysBackend.Params)
case "bolt":
bk, err = boltbk.FromJSON(cfg.KeysBackend.Params)
default:
return nil, trace.Errorf("unsupported backend type: %v", cfg.KeysBackend.Type)
}
if err != nil {
return nil, trace.Wrap(err)
}
keyStorage := path.Join(dataDir, "backend_keys")
encryptionKeys := []encryptor.Key{}
for _, strKey := range cfg.KeysBackend.EncryptionKeys {
encKey, err := encryptedbk.KeyFromString(strKey)
if err != nil {
return nil, err
}
encryptionKeys = append(encryptionKeys, encKey)
}
encryptedBk, err := encryptedbk.NewReplicatedBackend(bk,
keyStorage, encryptionKeys, encryptor.GenerateGPGKey)
if err != nil {
log.Errorf(err.Error())
log.Infof("Initializing backend as follower node")
myKey, err := encryptor.GenerateGPGKey(domainName + " key")
if err != nil {
return nil, err
}
masterKey, err := auth.RegisterNewAuth(
domainName, cfg.Token, myKey.Public(), peers)
if err != nil {
return nil, err
}
log.Infof(" ", myKey, masterKey)
encryptedBk, err = encryptedbk.NewReplicatedBackend(bk,
keyStorage, []encryptor.Key{myKey, masterKey},
encryptor.GenerateGPGKey)
if err != nil {
return nil, err
}
}
return encryptedBk, nil
}
func initEventBackend(btype string, params string) (events.Log, error) {
switch btype {
case "bolt":
return boltlog.FromJSON(params)
}
return nil, trace.Errorf("unsupported backend type: %v", btype)
}
func initRecordBackend(btype string, params string) (recorder.Recorder, error) {
switch btype {
case "bolt":
return boltrec.FromJSON(params)
}
return nil, trace.Errorf("unsupported backend type: %v", btype)
}
func (h *Hangout) GetJoinCommand() string {
nodeAddress := h.hangoutID + ":" + h.nodePort
authAddress := h.hangoutID + ":" + h.authPort
//nodeAddress := h.hangoutID + ":" + h.nodePort
//authAddress := h.hangoutID + ":" + h.authPort
return ""
}
const HangoutUser = "hangoutUser"
const HangoutDataDir = "/tmp/teleport_hangouts"

53
lib/hangout/token.go Normal file
View file

@ -0,0 +1,53 @@
/*
Copyright 2015 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hangout
import (
"encoding/base64"
"encoding/json"
"github.com/gravitational/trace"
)
type HangoutInfo struct {
AuthPort string
NodePort string
HangoutID string
HangoutPassword string
OSUser string
}
func MarshalHangoutInfo(h *HangoutInfo) (string, error) {
jsonString, err := json.Marshal(h)
if err != nil {
return "", trace.Wrap(err)
}
b64str := base64.StdEncoding.EncodeToString(jsonString)
return string(b64str), nil
}
func UnmarshalHangoutInfo(id string) (*HangoutInfo, error) {
jsonString, err := base64.StdEncoding.DecodeString(id)
if err != nil {
return nil, trace.Wrap(err)
}
var h HangoutInfo
err = json.Unmarshal(jsonString, &h)
if err != nil {
return nil, trace.Wrap(err)
}
return &h, nil
}

View file

@ -180,9 +180,6 @@ func (a *Agent) connect() error {
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: a.hostKeyCallback,
})
if err != nil {
log.Warningf(err.Error())
}
if c != nil {
break
}

View file

@ -91,6 +91,30 @@ func NewServer(addr utils.NetAddr, hostSigners []ssh.Signer,
return srv, nil
}
// New returns an unstarted server
func NewHangoutServer(addr utils.NetAddr, hostSigners []ssh.Signer,
ap auth.AccessPoint, limiter *limiter.Limiter) (Server, error) {
srv := &server{
sites: []*remoteSite{},
ap: ap,
}
s, err := sshutils.NewServer(
addr,
srv,
hostSigners,
sshutils.AuthMethods{
PublicKey: srv.hangoutKeyAuth,
},
limiter,
)
if err != nil {
return nil, err
}
srv.certChecker = ssh.CertChecker{IsAuthority: srv.isHangoutAuthority}
srv.srv = s
return srv, nil
}
func (s *server) Wait() {
s.srv.Wait()
}
@ -144,6 +168,20 @@ func (s *server) isAuthority(auth ssh.PublicKey) bool {
return false
}
// isAuthority is called during checking the client key, to see if the signing
// key is the real CA authority key.
func (s *server) isHangoutAuthority(auth ssh.PublicKey) bool {
_, found, err := s.ap.GetCertificateID(services.UserCert, auth)
if err != nil {
log.Errorf("failed to retrieve trused keys, err: %v", err)
return false
}
if found {
return true
}
return false
}
func (s *server) getTrustedCAKeys() ([]ssh.PublicKey, error) {
out := []ssh.PublicKey{}
authKeys := [][]byte{}
@ -155,6 +193,7 @@ func (s *server) getTrustedCAKeys() ([]ssh.PublicKey, error) {
certs, err := s.ap.GetRemoteCertificates(services.HostCert, "")
if err != nil {
log.Errorf(err.Error())
return nil, err
}
for _, c := range certs {
@ -201,6 +240,44 @@ func (s *server) keyAuth(
return perms, nil
}
func (s *server) hangoutKeyAuth(
conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
cid := fmt.Sprintf(
"reversetunnelconn(%v->%v, user=%v)", conn.RemoteAddr(),
conn.LocalAddr(), conn.User())
log.Infof("%v auth attempt with key %v", cid, key.Type())
_, ok := key.(*ssh.Certificate)
if !ok {
log.Warningf("conn(%v->%v, user=%v) ERROR: Server doesn't support provided key type",
conn.RemoteAddr(), conn.LocalAddr(), conn.User())
return nil, trace.Errorf("ERROR: Server doesn't support provided key type")
}
//teleportUser := cert.Permissions.Extensions[utils.CertExtensionUser]
_, err := s.certChecker.Authenticate(conn, key)
if err != nil {
log.Warningf("conn(%v->%v, user=%v) ERROR: Failed to authorize user %v, err: %v",
conn.RemoteAddr(), conn.LocalAddr(), conn.User(), conn.User(), err)
return nil, err
}
if err := s.certChecker.CheckCert(conn.User(), key.(*ssh.Certificate)); err != nil {
log.Warningf("conn(%v->%v, user=%v) ERROR: Failed to authorize user %v, err: %v",
conn.RemoteAddr(), conn.LocalAddr(), conn.User(), conn.User(), err)
return nil, trace.Wrap(err)
}
perms := &ssh.Permissions{
Extensions: map[string]string{
ExtHost: conn.User(),
},
}
return perms, nil
}
func (s *server) upsertSite(c ssh.Conn) (*remoteSite, error) {
s.Lock()
defer s.Unlock()
@ -316,7 +393,7 @@ func (s *remoteSite) init(c ssh.Conn) error {
log.Errorf("remoteSite:authProxy %v", err)
return nil, err
}
return newChConn(s.conn, ch), nil
return utils.NewChConn(s.conn, ch), nil
},
}
clt, err := auth.NewClient(
@ -374,7 +451,7 @@ func (s *remoteSite) ConnectToServer(server, user string, auth []ssh.AuthMethod)
if !dialed {
return nil, trace.Errorf("remote server %v is not available", server)
}
transportConn := newChConn(s.conn, ch)
transportConn := utils.NewChConn(s.conn, ch)
conn, chans, reqs, err := ssh.NewClientConn(
transportConn, server,
&ssh.ClientConfig{
@ -419,7 +496,7 @@ func (s *remoteSite) DialServer(server string) (net.Conn, error) {
if !dialed {
return nil, trace.Errorf("remote server %v is not available", server)
}
return newChConn(s.conn, ch), nil
return utils.NewChConn(s.conn, ch), nil
}
func (s *remoteSite) GetServers() ([]services.Server, error) {
@ -434,7 +511,7 @@ func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
log.Errorf("remoteSite:authProxy %v", err)
return nil, err
}
return newChConn(s.conn, ch), nil
return utils.NewChConn(s.conn, ch), nil
},
}
@ -449,36 +526,4 @@ func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
fwd.ServeHTTP(w, r)
}
func newChConn(conn ssh.Conn, ch ssh.Channel) *chConn {
c := &chConn{}
c.Channel = ch
c.conn = conn
return c
}
type chConn struct {
ssh.Channel
conn ssh.Conn
}
func (c *chConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *chConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *chConn) SetDeadline(t time.Time) error {
return nil
}
func (c *chConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *chConn) SetWriteDeadline(t time.Time) error {
return nil
}
const ExtHost = "host@teleport"

View file

@ -90,6 +90,11 @@ type ProxyConfig struct {
// ReverseTunnelListenAddr is address where reverse tunnel dialers connect to
ReverseTunnelListenAddr utils.NetAddr `yaml:"reverse_tunnel_listen_addr" env:"TELEPORT_PROXY_REVERSE_TUNNEL_LISTEN_ADDR"`
HangoutsEnabled bool `yaml:"hangouts_enabled" env:"TELEPORT_PROXY_HANGOUTS_ENABLED`
// HangoutListenAddr is address where hangout reverse tunnel dialers connect to
HangoutsListenAddr utils.NetAddr `yaml:"hangouts_listen_addr" env:"TELEPORT_PROXY_HANGOUTS_LISTEN_ADDR"`
// WebAddr is address for web portal of the proxy
WebAddr utils.NetAddr `yaml:"web_addr" env:"TELEPORT_PROXY_WEB_ADDR"`

View file

@ -405,13 +405,32 @@ func initProxyEndpoint(supervisor Supervisor, cfg Config) error {
return trace.Wrap(err)
}
var hsrv reversetunnel.Server
if cfg.Proxy.HangoutsEnabled {
hsrv, err = reversetunnel.NewHangoutServer(
cfg.Proxy.HangoutsListenAddr,
[]ssh.Signer{signer},
client,
reverseTunnelLimiter,
)
if err != nil {
return trace.Wrap(err)
}
}
options := []srv.ServerOption{srv.SetProxyMode(tsrv)}
if cfg.Proxy.HangoutsEnabled {
options = append(options, srv.EnableHangouts(hsrv))
}
SSHProxy, err := srv.New(cfg.Proxy.SSHAddr,
cfg.Hostname,
[]ssh.Signer{signer},
client,
proxyLimiter,
cfg.DataDir,
srv.SetProxyMode(tsrv),
options...,
)
if err != nil {
return trace.Wrap(err)
@ -429,6 +448,20 @@ func initProxyEndpoint(supervisor Supervisor, cfg Config) error {
return nil
})
if cfg.Proxy.HangoutsEnabled {
// register SSH reverse tunnel server that accepts connections
// from remote teleport nodes
supervisor.RegisterFunc(func() error {
log.Infof("[PROXY] hangouts listening server starting on %v",
cfg.Proxy.HangoutsListenAddr)
if err := hsrv.Start(); err != nil {
return trace.Wrap(err)
}
hsrv.Wait()
return nil
})
}
// Register web proxy server
supervisor.RegisterFunc(func() error {
log.Infof("[PROXY] teleport web proxy server starting on %v",

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

@ -0,0 +1,102 @@
package srv
import (
"fmt"
"io"
"net"
"strings"
"sync"
"github.com/gravitational/teleport/lib/services"
log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
)
// hangoutsSubsys is an SSH subsystem for easy proxyneling through proxy server
// This subsystem creates a new TCP connection and connects ssh channel
// with this connection
type hangoutsSubsys struct {
srv *Server
host string
port string
}
func parseHangoutsSubsys(name string, srv *Server) (*hangoutsSubsys, error) {
out := strings.Split(name, ":")
if len(out) != 3 {
return nil, trace.Errorf("invalid format for proxy request: '%v', expected 'proxy:host:port'", name)
}
return &hangoutsSubsys{
srv: srv,
host: out[1],
port: out[2],
}, nil
}
func (t *hangoutsSubsys) String() string {
return fmt.Sprintf("hangoutsSubsys(host=%v, port=%v)", t.host, t.port)
}
func (t *hangoutsSubsys) execute(sconn *ssh.ServerConn, ch ssh.Channel, req *ssh.Request, ctx *ctx) error {
remoteSrv, err := t.srv.hangoutsTun.GetSite(t.host)
if err != nil {
return trace.Wrap(err)
}
// find matching server in the list of servers for this site
servers, err := remoteSrv.GetServers()
if err != nil {
return trace.Wrap(err)
}
serverAddr := fmt.Sprintf("%v:%v", t.host, t.port)
var server *services.Server
for i := range servers {
log.Infof("%v %v", servers[i].Addr, servers[i].Hostname)
ip, port, err := net.SplitHostPort(servers[i].Addr)
if err != nil {
return trace.Wrap(err)
}
// match either by hostname of ip, based on the match
if (t.host == ip || t.host == servers[i].Hostname) && port == t.port {
server = &servers[i]
break
}
}
if server == nil {
return trace.Errorf("server %v not found", serverAddr)
}
// we must dial by server IP address because hostname
// may not be actually DNS resolvable
conn, err := remoteSrv.DialServer(server.Addr)
if err != nil {
return trace.Wrap(err)
}
wg := &sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
_, err := io.Copy(ch, conn)
if err != nil {
log.Errorf(err.Error())
}
ch.Close()
}()
go func() {
defer wg.Done()
_, err := io.Copy(conn, ch)
if err != nil {
log.Errorf(err.Error())
}
conn.Close()
}()
wg.Wait()
return nil
}

View file

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

View file

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

View file

@ -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
}

View file

@ -20,10 +20,12 @@ import (
"fmt"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
)
type HostKeyCallback func(hostname string, remote net.Addr, key ssh.PublicKey) error

View file

@ -51,6 +51,9 @@ func RunTSH(app *kingpin.Application) error {
scpIsDir := scp.Flag("recursively", "Source path is a directory").Short('r').Bool()
scpPort := scp.Flag("port", "Remote server port").Short('P').String()
share := app.Command("share", "share")
join := app.Command("join", "join")
selectedCommand := kingpin.MustParse(app.Parse(os.Args[1:]))
if (selectedCommand == getServers.FullCommand()) && (len(*proxy) == 0) {
@ -93,6 +96,10 @@ func RunTSH(app *kingpin.Application) error {
case scp.FullCommand():
err = SCP(*proxy, *scpSource, *scpDest, *scpIsDir, *scpPort,
authMethods, hostKeyCallback)
case share.FullCommand():
err = Share("localhost:33009", "localhost:33010", "localhost:33011", authMethods, hostKeyCallback)
case join.FullCommand():
err = Join(*proxy, "", authMethods, hostKeyCallback)
}
return err

View file

@ -21,11 +21,13 @@ import (
"os"
"os/exec"
"os/signal"
"os/user"
"strings"
"sync"
"syscall"
"time"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/hangout"
"github.com/gravitational/teleport/lib/services"
@ -34,7 +36,6 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
@ -79,6 +80,11 @@ func SSH(target, proxyAddress, command, port string, authMethods []ssh.AuthMetho
return trace.Wrap(err)
}
}
return shell(c, address)
}
func shell(c *client.NodeClient, address string) error {
defer c.Close()
// disable input buffering
@ -272,14 +278,65 @@ func SCP(proxyAddress, source, dest string, isDir bool, port string, authMethods
return nil
}
func Share(proxyAddress, nodeListeningAddress, authListeningAddress,
authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) {
hangoutServer := hangout.New(proxyAddress, nodeListeningAddress,
authListeningAddress, authMethods, hostKeyCallback)
func Share(hangoutProxyAddress, nodeListeningAddress, authListeningAddress string,
authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
hangoutServer, err := hangout.New(hangoutProxyAddress, nodeListeningAddress,
authListeningAddress, false, authMethods, hostKeyCallback)
return SSH(nodeListeningAddress, "", "", "", []ssh.AuthMethod{h.AuthMethod}, h.HostKeyCallback)
if err != nil {
return trace.Wrap(err)
}
u, err := user.Current()
if err != nil {
return trace.Wrap(err)
}
return SSH(u.Username+"@"+nodeListeningAddress, "", "", "", []ssh.AuthMethod{hangoutServer.ClientAuthMethod}, hangoutServer.HostKeyCallback)
}
func Join(proxyAddress, nodeAddress, authAddress, userPassword string) {
func Join(proxyAddress, hangoutToken string, authMethods []ssh.AuthMethod, hostKeyCallback utils.HostKeyCallback) error {
hangoutServer, err := hangout.New("localhost:33009", "localhost:33010",
"localhost:33011", false, authMethods, hostKeyCallback)
if err != nil {
return trace.Wrap(err)
}
time.Sleep(time.Second * 1)
hangoutToken = hangoutServer.Token
hInfo, err := hangout.UnmarshalHangoutInfo(hangoutToken)
proxy, err := client.ConnectToProxy(proxyAddress, authMethods, hostKeyCallback, "123")
if err != nil {
return trace.Wrap(err)
}
hangoutAuthMethod, err := auth.NewTokenAuth(hangout.HangoutUser, hInfo.HangoutPassword)
if err != nil {
return trace.Wrap(err)
}
authConn, err := proxy.ConnectToHangout(hInfo.HangoutID+":"+hInfo.AuthPort, hangoutAuthMethod, nil, hangout.HangoutUser)
if err != nil {
return trace.Wrap(err)
}
authClient, err := auth.NewClientFromSSHClient(authConn.Client)
if err != nil {
return trace.Wrap(err)
}
nodeAuthMethod, nodeHostKeyCallback, err := hangout.Authorize(authClient, hInfo.HangoutPassword[0:100])
if err != nil {
return trace.Wrap(err)
}
nodeConn, err := proxy.ConnectToHangout(hInfo.HangoutID+":"+hInfo.NodePort, []ssh.AuthMethod{nodeAuthMethod}, nodeHostKeyCallback, hInfo.OSUser)
if err != nil {
return trace.Wrap(err)
}
return shell(nodeConn, hInfo.HangoutID)
}

View file

@ -32,6 +32,7 @@ import (
"github.com/gravitational/teleport/lib/backend/boltbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/events/boltlog"
"github.com/gravitational/teleport/lib/limiter"
"github.com/gravitational/teleport/lib/recorder/boltrec"