teleport/integration/helpers.go
Russell Jones 6d1c16f745 Added support for nodes dialing back to cluster.
Updated services.ReverseTunnel to support type (proxy or node). For
proxy types, which represent trusted cluster connections, when a
services.ReverseTunnel is created, it's created on the remote side with
name /reverseTunnels/example.com. For node types, services.ReverseTunnel
is created on the main side as /reverseTunnels/{nodeUUID}.clusterName.

Updated services.TunnelConn to support type (proxy or node). For proxy
types, which represent trusted cluster connections, tunnel connections
are created on the main side under
/tunnelConnections/remote.example.com/{proxyUUID}-remote.example.com.
For nodes, tunnel connections are created on the main side under
/tunnelConnections/example.com/{proxyUUID}-example.com. This allows
searching for tunnel connections by cluster then allows easily creating
a set of proxies that are missing matching services.TunnelConn.

The reverse tunnel server has been updated to handle heartbeats from
proxies as well as nodes. Proxy heartbeat behavior has not changed.
Heartbeats from nodes now add remote connections to the matching local
site. In addition, the reverse tunnel server now proxies connection to
the Auth Server for requests that are already authenticated (a second
authentication to the Auth Server is required).

For registration, nodes try and connect to the Auth Server to fetch host
credentials. Upon failure, nodes now try and fallback to fetching host
credentials from the web proxy.

To establish a connection to an Auth Server, nodes first try and connect
directly, and if the connection fails, fallback to obtaining a
connection to the Auth Server through the reverse tunnel. If a
connection is established directly, node startup behavior has not
changed. If a node establishes a connection through the reverse tunnel,
it creates an AgentPool that attempts to dial back to the cluster and
establish a reverse tunnel.

When nodes heartbeat, they also heartbeat if they are connected directly
to the cluster or through a reverse tunnel. For nodes that are connected
through a reverse tunnel, the proxy subsystem now directs the reverse
tunnel server to establish a connection through the reverse tunnel
instead of directly.

When sending discovery requests, the domain field has been replaced with
tunnelID. The tunnelID field is either the cluster name (same as before)
for proxies, or {nodeUUID}.example.com for nodes.
2019-04-26 15:41:45 -07:00

1401 lines
40 KiB
Go

/*
Copyright 2018 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 integration
import (
"context"
"crypto/rsa"
"crypto/x509/pkix"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime/debug"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/native"
"github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/lite"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/teleagent"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/roundtrip"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
log "github.com/sirupsen/logrus"
)
// SetTestTimeouts affects global timeouts inside Teleport, making connections
// work faster but consuming more CPU (useful for integration testing)
func SetTestTimeouts(t time.Duration) {
defaults.ReverseTunnelAgentHeartbeatPeriod = t
defaults.ServerKeepAliveTTL = t
defaults.SessionRefreshPeriod = t
defaults.HeartbeatCheckPeriod = t
defaults.CachePollPeriod = t
}
// TeleInstance represents an in-memory instance of a teleport
// process for testing
type TeleInstance struct {
// Secrets holds the keys (pub, priv and derived cert) of i instance
Secrets InstanceSecrets
// Slice of TCP ports used by Teleport services
Ports []int
// Hostname is the name of the host where instance is running
Hostname string
// Internal stuff...
Process *service.TeleportProcess
Config *service.Config
Tunnel reversetunnel.Server
Pool *reversetunnel.AgentPool
// Nodes is a list of additional nodes
// started with this instance
Nodes []*service.TeleportProcess
// UploadEventsC is a channel for upload events
UploadEventsC chan *events.UploadEvent
}
type User struct {
Username string `json:"username"`
AllowedLogins []string `json:"logins"`
Key *client.Key `json:"key"`
Roles []services.Role `json:"-"`
}
type InstanceSecrets struct {
// instance name (aka "site name")
SiteName string `json:"site_name"`
// instance keys+cert (reused for hostCA and userCA)
// PubKey is instance public key
PubKey []byte `json:"pub"`
// PrivKey is instance private key
PrivKey []byte `json:"priv"`
// Cert is SSH host certificate
Cert []byte `json:"cert"`
// TLSCACert is the certificate of the trusted certificate authority
TLSCACert []byte `json:"tls_ca_cert"`
// TLSCert is client TLS X509 certificate
TLSCert []byte `json:"tls_cert"`
// ListenAddr is a reverse tunnel listening port, allowing
// other sites to connect to i instance. Set to empty
// string if i instance is not allowing incoming tunnels
ListenAddr string `json:"tunnel_addr"`
// WebProxyAddr is address for web proxy
WebProxyAddr string `json:"web_proxy_addr"`
// list of users i instance trusts (key in the map is username)
Users map[string]*User `json:"users"`
}
func (s *InstanceSecrets) String() string {
bytes, _ := json.MarshalIndent(s, "", "\t")
return string(bytes)
}
// InstanceConfig is an instance configuration
type InstanceConfig struct {
// ClusterName is a cluster name of the instance
ClusterName string
// HostID is a host id of the instance
HostID string
// NodeName is a node name of the instance
NodeName string
// Ports is a list of assigned ports to use
Ports []int
// Priv is SSH private key of the instance
Priv []byte
// Pub is SSH public key of the instance
Pub []byte
// MultiplexProxy uses the same port for web and SSH reverse tunnel proxy
MultiplexProxy bool
}
// NewInstance creates a new Teleport process instance
func NewInstance(cfg InstanceConfig) *TeleInstance {
var err error
if len(cfg.Ports) < 5 {
fatalIf(fmt.Errorf("not enough free ports given: %v", cfg.Ports))
}
if cfg.NodeName == "" {
cfg.NodeName, err = os.Hostname()
fatalIf(err)
}
// generate instance secrets (keys):
keygen, err := native.New(context.TODO(), native.PrecomputeKeys(0))
fatalIf(err)
if cfg.Priv == nil || cfg.Pub == nil {
cfg.Priv, cfg.Pub, _ = keygen.GenerateKeyPair("")
}
rsaKey, err := ssh.ParseRawPrivateKey(cfg.Priv)
fatalIf(err)
tlsCAKey, tlsCACert, err := tlsca.GenerateSelfSignedCAWithPrivateKey(rsaKey.(*rsa.PrivateKey), pkix.Name{
CommonName: cfg.ClusterName,
Organization: []string{cfg.ClusterName},
}, nil, defaults.CATTL)
fatalIf(err)
cert, err := keygen.GenerateHostCert(services.HostCertParams{
PrivateCASigningKey: cfg.Priv,
PublicHostKey: cfg.Pub,
HostID: cfg.HostID,
NodeName: cfg.NodeName,
ClusterName: cfg.ClusterName,
Roles: teleport.Roles{teleport.RoleAdmin},
TTL: time.Duration(time.Hour * 24),
})
fatalIf(err)
tlsCA, err := tlsca.New(tlsCACert, tlsCAKey)
fatalIf(err)
cryptoPubKey, err := sshutils.CryptoPublicKey(cfg.Pub)
identity := tlsca.Identity{
Username: fmt.Sprintf("%v.%v", cfg.HostID, cfg.ClusterName),
Groups: []string{string(teleport.RoleAdmin)},
}
clock := clockwork.NewRealClock()
tlsCert, err := tlsCA.GenerateCertificate(tlsca.CertificateRequest{
Clock: clock,
PublicKey: cryptoPubKey,
Subject: identity.Subject(),
NotAfter: clock.Now().UTC().Add(time.Hour * 24),
})
fatalIf(err)
i := &TeleInstance{
Ports: cfg.Ports,
Hostname: cfg.NodeName,
UploadEventsC: make(chan *events.UploadEvent, 100),
}
secrets := InstanceSecrets{
SiteName: cfg.ClusterName,
PrivKey: cfg.Priv,
PubKey: cfg.Pub,
Cert: cert,
TLSCACert: tlsCACert,
TLSCert: tlsCert,
ListenAddr: net.JoinHostPort(cfg.NodeName, i.GetPortReverseTunnel()),
WebProxyAddr: net.JoinHostPort(cfg.NodeName, i.GetPortWeb()),
Users: make(map[string]*User),
}
if cfg.MultiplexProxy {
secrets.ListenAddr = secrets.WebProxyAddr
}
i.Secrets = secrets
return i
}
// GetRoles returns a list of roles to initiate for this secret
func (s *InstanceSecrets) GetRoles() []services.Role {
var roles []services.Role
for _, ca := range s.GetCAs() {
if ca.GetType() != services.UserCA {
continue
}
role := services.RoleForCertAuthority(ca)
role.SetLogins(services.Allow, s.AllowedLogins())
roles = append(roles, role)
}
return roles
}
// GetCAs return an array of CAs stored by the secrets object. In i
// case we always return hard-coded userCA + hostCA (and they share keys
// for simplicity)
func (s *InstanceSecrets) GetCAs() []services.CertAuthority {
hostCA := services.NewCertAuthority(services.HostCA, s.SiteName, [][]byte{s.PrivKey}, [][]byte{s.PubKey}, []string{})
hostCA.SetTLSKeyPairs([]services.TLSKeyPair{{Cert: s.TLSCACert, Key: s.PrivKey}})
return []services.CertAuthority{
hostCA,
services.NewCertAuthority(services.UserCA, s.SiteName, [][]byte{s.PrivKey}, [][]byte{s.PubKey}, []string{services.RoleNameForCertAuthority(s.SiteName)}),
}
}
func (s *InstanceSecrets) AllowedLogins() []string {
var logins []string
for i := range s.Users {
logins = append(logins, s.Users[i].AllowedLogins...)
}
return logins
}
func (s *InstanceSecrets) AsTrustedCluster(token string, roleMap services.RoleMap) services.TrustedCluster {
return &services.TrustedClusterV2{
Kind: services.KindTrustedCluster,
Version: services.V2,
Metadata: services.Metadata{
Name: s.SiteName,
},
Spec: services.TrustedClusterSpecV2{
Token: token,
Enabled: true,
ProxyAddress: s.WebProxyAddr,
ReverseTunnelAddress: s.ListenAddr,
RoleMap: roleMap,
},
}
}
func (s *InstanceSecrets) AsSlice() []*InstanceSecrets {
return []*InstanceSecrets{s}
}
func (s *InstanceSecrets) GetIdentity() *auth.Identity {
i, err := auth.ReadIdentityFromKeyPair(&auth.PackedKeys{
Key: s.PrivKey,
Cert: s.Cert,
TLSCert: s.TLSCert,
TLSCACerts: [][]byte{s.TLSCACert},
})
fatalIf(err)
return i
}
func (i *TeleInstance) GetPortSSHInt() int {
return i.Ports[0]
}
func (i *TeleInstance) GetPortSSH() string {
return strconv.Itoa(i.GetPortSSHInt())
}
func (i *TeleInstance) GetPortAuth() string {
return strconv.Itoa(i.Ports[1])
}
func (i *TeleInstance) GetPortProxy() string {
return strconv.Itoa(i.Ports[2])
}
func (i *TeleInstance) GetPortWeb() string {
return strconv.Itoa(i.Ports[3])
}
func (i *TeleInstance) GetPortReverseTunnel() string {
return strconv.Itoa(i.Ports[4])
}
// GetSiteAPI() is a helper which returns an API endpoint to a site with
// a given name. i endpoint implements HTTP-over-SSH access to the
// site's auth server.
func (i *TeleInstance) GetSiteAPI(siteName string) auth.ClientI {
siteTunnel, err := i.Tunnel.GetSite(siteName)
if err != nil {
log.Warn(err)
return nil
}
siteAPI, err := siteTunnel.GetClient()
if err != nil {
log.Warn(err)
return nil
}
return siteAPI
}
// Create creates a new instance of Teleport which trusts a lsit of other clusters (other
// instances)
func (i *TeleInstance) Create(trustedSecrets []*InstanceSecrets, enableSSH bool, console io.Writer) error {
tconf := service.MakeDefaultConfig()
tconf.SSH.Enabled = enableSSH
tconf.Console = console
tconf.Proxy.DisableWebService = true
tconf.Proxy.DisableWebInterface = true
return i.CreateEx(trustedSecrets, tconf)
}
// UserCreds holds user client credentials
type UserCreds struct {
// Key is user client key and certificate
Key client.Key
// HostCA is a trusted host certificate authority
HostCA services.CertAuthority
}
// SetupUserCreds sets up user credentials for client
func SetupUserCreds(tc *client.TeleportClient, proxyHost string, creds UserCreds) error {
_, err := tc.AddKey(proxyHost, &creds.Key)
if err != nil {
return trace.Wrap(err)
}
err = tc.AddTrustedCA(creds.HostCA)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// SetupUser sets up user in the cluster
func SetupUser(process *service.TeleportProcess, username string, roles []services.Role) error {
auth := process.GetAuthServer()
teleUser, err := services.NewUser(username)
if err != nil {
return trace.Wrap(err)
}
if len(roles) == 0 {
role := services.RoleForUser(teleUser)
role.SetLogins(services.Allow, []string{username})
// allow tests to forward agent, still needs to be passed in client
roleOptions := role.GetOptions()
roleOptions.ForwardAgent = services.NewBool(true)
role.SetOptions(roleOptions)
err = auth.UpsertRole(role)
if err != nil {
return trace.Wrap(err)
}
teleUser.AddRole(role.GetMetadata().Name)
roles = append(roles, role)
} else {
for _, role := range roles {
err := auth.UpsertRole(role)
if err != nil {
return trace.Wrap(err)
}
teleUser.AddRole(role.GetName())
}
}
err = auth.UpsertUser(teleUser)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// GenerateUserCreds generates key to be used by client
func GenerateUserCreds(process *service.TeleportProcess, username string) (*UserCreds, error) {
priv, pub, err := testauthority.New().GenerateKeyPair("")
if err != nil {
return nil, trace.Wrap(err)
}
a := process.GetAuthServer()
sshCert, x509Cert, err := a.GenerateUserCerts(pub, username, time.Hour, teleport.CertificateFormatStandard)
if err != nil {
return nil, trace.Wrap(err)
}
clusterName, err := a.GetClusterName()
if err != nil {
return nil, trace.Wrap(err)
}
ca, err := a.GetCertAuthority(services.CertAuthID{
Type: services.HostCA,
DomainName: clusterName.GetClusterName(),
}, false)
if err != nil {
return nil, trace.Wrap(err)
}
return &UserCreds{
HostCA: ca,
Key: client.Key{
Priv: priv,
Pub: pub,
Cert: sshCert,
TLSCert: x509Cert,
},
}, nil
}
// GenerateConfig generates instance config
func (i *TeleInstance) GenerateConfig(trustedSecrets []*InstanceSecrets, tconf *service.Config) (*service.Config, error) {
var err error
dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName)
if err != nil {
return nil, trace.Wrap(err)
}
if tconf == nil {
tconf = service.MakeDefaultConfig()
}
tconf.DataDir = dataDir
tconf.UploadEventsC = i.UploadEventsC
tconf.CachePolicy.Enabled = true
tconf.Auth.ClusterName, err = services.NewClusterName(services.ClusterNameSpecV2{
ClusterName: i.Secrets.SiteName,
})
if err != nil {
return nil, trace.Wrap(err)
}
tconf.Auth.StaticTokens, err = services.NewStaticTokens(services.StaticTokensSpecV2{
StaticTokens: []services.ProvisionTokenV1{
{
Roles: []teleport.Role{teleport.RoleNode, teleport.RoleProxy, teleport.RoleTrustedCluster},
Token: "token",
},
},
})
if err != nil {
return nil, trace.Wrap(err)
}
tconf.Auth.Authorities = append(tconf.Auth.Authorities, i.Secrets.GetCAs()...)
tconf.Identities = append(tconf.Identities, i.Secrets.GetIdentity())
for _, trusted := range trustedSecrets {
tconf.Auth.Authorities = append(tconf.Auth.Authorities, trusted.GetCAs()...)
tconf.Auth.Roles = append(tconf.Auth.Roles, trusted.GetRoles()...)
tconf.Identities = append(tconf.Identities, trusted.GetIdentity())
if trusted.ListenAddr != "" {
tconf.ReverseTunnels = []services.ReverseTunnel{
services.NewReverseTunnel(trusted.SiteName, []string{trusted.ListenAddr}),
}
}
}
tconf.Proxy.ReverseTunnelListenAddr.Addr = i.Secrets.ListenAddr
tconf.HostUUID = i.Secrets.GetIdentity().ID.HostUUID
tconf.SSH.Addr.Addr = net.JoinHostPort(i.Hostname, i.GetPortSSH())
tconf.SSH.PublicAddrs = []utils.NetAddr{
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Loopback,
},
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Host,
},
}
tconf.Auth.SSHAddr.Addr = net.JoinHostPort(i.Hostname, i.GetPortAuth())
tconf.Proxy.SSHAddr.Addr = net.JoinHostPort(i.Hostname, i.GetPortProxy())
tconf.Proxy.WebAddr.Addr = net.JoinHostPort(i.Hostname, i.GetPortWeb())
tconf.Proxy.PublicAddrs = []utils.NetAddr{
utils.NetAddr{
AddrNetwork: "tcp",
Addr: i.Hostname,
},
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Loopback,
},
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Host,
},
}
tconf.AuthServers = append(tconf.AuthServers, tconf.Auth.SSHAddr)
tconf.Auth.StorageConfig = backend.Config{
Type: lite.GetName(),
Params: backend.Params{"path": dataDir + string(os.PathListSeparator) + defaults.BackendDir, "poll_stream_period": 50 * time.Millisecond},
}
tconf.Keygen = testauthority.New()
i.Config = tconf
return tconf, nil
}
// CreateEx creates a new instance of Teleport which trusts a list of other clusters (other
// instances)
//
// Unlike Create() it allows for greater customization because it accepts
// a full Teleport config structure
func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *service.Config) error {
tconf, err := i.GenerateConfig(trustedSecrets, tconf)
if err != nil {
return trace.Wrap(err)
}
i.Config = tconf
i.Process, err = service.NewTeleport(tconf)
if err != nil {
return trace.Wrap(err)
}
// if the auth server is not enabled, nothing more to do be done
if !tconf.Auth.Enabled {
return nil
}
// if this instance contains an auth server, configure the auth server as well.
// create users and roles if they don't exist, or sign their keys if they're
// already present
auth := i.Process.GetAuthServer()
for _, user := range i.Secrets.Users {
teleUser, err := services.NewUser(user.Username)
if err != nil {
return trace.Wrap(err)
}
var roles []services.Role
if len(user.Roles) == 0 {
role := services.RoleForUser(teleUser)
role.SetLogins(services.Allow, user.AllowedLogins)
// allow tests to forward agent, still needs to be passed in client
roleOptions := role.GetOptions()
roleOptions.ForwardAgent = services.NewBool(true)
role.SetOptions(roleOptions)
err = auth.UpsertRole(role)
if err != nil {
return trace.Wrap(err)
}
teleUser.AddRole(role.GetMetadata().Name)
roles = append(roles, role)
} else {
roles = user.Roles
for _, role := range user.Roles {
err := auth.UpsertRole(role)
if err != nil {
return trace.Wrap(err)
}
teleUser.AddRole(role.GetName())
}
}
err = auth.UpsertUser(teleUser)
if err != nil {
return trace.Wrap(err)
}
// if user keys are not present, auto-geneate keys:
if user.Key == nil || len(user.Key.Pub) == 0 {
priv, pub, _ := tconf.Keygen.GenerateKeyPair("")
user.Key = &client.Key{
Priv: priv,
Pub: pub,
}
}
// sign user's keys:
ttl := 24 * time.Hour
user.Key.Cert, user.Key.TLSCert, err = auth.GenerateUserCerts(user.Key.Pub, teleUser.GetName(), ttl, teleport.CertificateFormatStandard)
if err != nil {
return err
}
}
return nil
}
// StartNode starts a SSH node and connects it to the cluster.
func (i *TeleInstance) StartNode(tconf *service.Config) (*service.TeleportProcess, error) {
dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName)
if err != nil {
return nil, trace.Wrap(err)
}
tconf.DataDir = dataDir
authServer := utils.MustParseAddr(net.JoinHostPort(i.Hostname, i.GetPortAuth()))
tconf.AuthServers = append(tconf.AuthServers, *authServer)
tconf.Token = "token"
tconf.UploadEventsC = i.UploadEventsC
var ttl time.Duration
tconf.CachePolicy = service.CachePolicy{
Enabled: true,
RecentTTL: &ttl,
}
tconf.SSH.PublicAddrs = []utils.NetAddr{
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Loopback,
},
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Host,
},
}
tconf.Auth.Enabled = false
tconf.Proxy.Enabled = false
// Create a new Teleport process and add it to the list of nodes that
// compose this "cluster".
process, err := service.NewTeleport(tconf)
if err != nil {
return nil, trace.Wrap(err)
}
i.Nodes = append(i.Nodes, process)
// Build a list of expected events to wait for before unblocking based off
// the configuration passed in.
expectedEvents := []string{
service.NodeSSHReady,
}
// Start the process and block until the expected events have arrived.
receivedEvents, err := startAndWait(process, expectedEvents)
if err != nil {
return nil, trace.Wrap(err)
}
log.Debugf("Teleport node (in instance %v) started: %v/%v events received.",
i.Secrets.SiteName, len(expectedEvents), len(receivedEvents))
return process, nil
}
// StartNodeAndProxy starts a SSH node and a Proxy Server and connects it to
// the cluster.
func (i *TeleInstance) StartNodeAndProxy(name string, sshPort, proxyWebPort, proxySSHPort int) error {
dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName)
if err != nil {
return trace.Wrap(err)
}
tconf := service.MakeDefaultConfig()
authServer := utils.MustParseAddr(net.JoinHostPort(i.Hostname, i.GetPortAuth()))
tconf.AuthServers = append(tconf.AuthServers, *authServer)
tconf.Token = "token"
tconf.HostUUID = name
tconf.Hostname = name
tconf.UploadEventsC = i.UploadEventsC
tconf.DataDir = dataDir
var ttl time.Duration
tconf.CachePolicy = service.CachePolicy{
Enabled: true,
RecentTTL: &ttl,
}
tconf.Auth.Enabled = false
tconf.Proxy.Enabled = true
tconf.Proxy.SSHAddr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", proxySSHPort))
tconf.Proxy.WebAddr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", proxyWebPort))
tconf.Proxy.DisableReverseTunnel = true
tconf.Proxy.DisableWebService = true
tconf.SSH.Enabled = true
tconf.SSH.Addr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", sshPort))
tconf.SSH.PublicAddrs = []utils.NetAddr{
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Loopback,
},
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Host,
},
}
// Create a new Teleport process and add it to the list of nodes that
// compose this "cluster".
process, err := service.NewTeleport(tconf)
if err != nil {
return trace.Wrap(err)
}
i.Nodes = append(i.Nodes, process)
// Build a list of expected events to wait for before unblocking based off
// the configuration passed in.
expectedEvents := []string{
service.ProxySSHReady,
service.NodeSSHReady,
}
// Start the process and block until the expected events have arrived.
receivedEvents, err := startAndWait(process, expectedEvents)
if err != nil {
return trace.Wrap(err)
}
log.Debugf("Teleport node and proxy (in instance %v) started: %v/%v events received.",
i.Secrets.SiteName, len(expectedEvents), len(receivedEvents))
return nil
}
// ProxyConfig is a set of configuration parameters for Proxy
type ProxyConfig struct {
// Name is a proxy name
Name string
// SSHPort is SSH proxy port
SSHPort int
// WebPort is web proxy port
WebPort int
// ReverseTunnelPort is a port for reverse tunnel addresses
ReverseTunnelPort int
}
// StartProxy starts another Proxy Server and connects it to the cluster.
func (i *TeleInstance) StartProxy(cfg ProxyConfig) error {
dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName+"-"+cfg.Name)
if err != nil {
return trace.Wrap(err)
}
tconf := service.MakeDefaultConfig()
authServer := utils.MustParseAddr(net.JoinHostPort(i.Hostname, i.GetPortAuth()))
tconf.AuthServers = append(tconf.AuthServers, *authServer)
tconf.CachePolicy = service.CachePolicy{Enabled: true}
tconf.DataDir = dataDir
tconf.UploadEventsC = i.UploadEventsC
tconf.HostUUID = cfg.Name
tconf.Hostname = cfg.Name
tconf.Token = "token"
tconf.Auth.Enabled = false
tconf.SSH.Enabled = false
tconf.Proxy.Enabled = true
tconf.Proxy.SSHAddr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", cfg.SSHPort))
tconf.Proxy.PublicAddrs = []utils.NetAddr{
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Loopback,
},
utils.NetAddr{
AddrNetwork: "tcp",
Addr: Host,
},
}
tconf.Proxy.ReverseTunnelListenAddr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", cfg.ReverseTunnelPort))
tconf.Proxy.WebAddr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", cfg.WebPort))
tconf.Proxy.DisableReverseTunnel = false
tconf.Proxy.DisableWebService = true
// Create a new Teleport process and add it to the list of nodes that
// compose this "cluster".
process, err := service.NewTeleport(tconf)
if err != nil {
return trace.Wrap(err)
}
i.Nodes = append(i.Nodes, process)
// Build a list of expected events to wait for before unblocking based off
// the configuration passed in.
expectedEvents := []string{
service.ProxyReverseTunnelReady,
service.ProxySSHReady,
}
// Start the process and block until the expected events have arrived.
receivedEvents, err := startAndWait(process, expectedEvents)
if err != nil {
return trace.Wrap(err)
}
log.Debugf("Teleport proxy (in instance %v) started: %v/%v events received.",
i.Secrets.SiteName, len(expectedEvents), len(receivedEvents))
return nil
}
// Reset re-creates the teleport instance based on the same configuration
// This is needed if you want to stop the instance, reset it and start again
func (i *TeleInstance) Reset() (err error) {
i.Process, err = service.NewTeleport(i.Config)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// AddUserUserWithRole adds user with assigned role
func (i *TeleInstance) AddUserWithRole(username string, role services.Role) *User {
user := &User{
Username: username,
Roles: []services.Role{role},
}
i.Secrets.Users[username] = user
return user
}
// Adds a new user into i Teleport instance. 'mappings' is a comma-separated
// list of OS users
func (i *TeleInstance) AddUser(username string, mappings []string) *User {
log.Infof("teleInstance.AddUser(%v) mapped to %v", username, mappings)
if mappings == nil {
mappings = make([]string, 0)
}
user := &User{
Username: username,
AllowedLogins: mappings,
}
i.Secrets.Users[username] = user
return user
}
// Start will start the TeleInstance and then block until it is ready to
// process requests based off the passed in configuration.
func (i *TeleInstance) Start() error {
// Build a list of expected events to wait for before unblocking based off
// the configuration passed in.
expectedEvents := []string{}
if i.Config.Auth.Enabled {
expectedEvents = append(expectedEvents, service.AuthTLSReady)
}
if i.Config.Proxy.Enabled {
expectedEvents = append(expectedEvents, service.ProxyReverseTunnelReady)
expectedEvents = append(expectedEvents, service.ProxySSHReady)
expectedEvents = append(expectedEvents, service.ProxyAgentPoolReady)
if !i.Config.Proxy.DisableWebService {
expectedEvents = append(expectedEvents, service.ProxyWebServerReady)
}
}
if i.Config.SSH.Enabled {
expectedEvents = append(expectedEvents, service.NodeSSHReady)
}
// Start the process and block until the expected events have arrived.
receivedEvents, err := startAndWait(i.Process, expectedEvents)
if err != nil {
return trace.Wrap(err)
}
// Extract and set reversetunnel.Server and reversetunnel.AgentPool upon
// receipt of a ProxyReverseTunnelReady and ProxyAgentPoolReady respectively.
for _, re := range receivedEvents {
switch re.Name {
case service.ProxyReverseTunnelReady:
ts, ok := re.Payload.(reversetunnel.Server)
if ok {
i.Tunnel = ts
}
case service.ProxyAgentPoolReady:
ap, ok := re.Payload.(*reversetunnel.AgentPool)
if ok {
i.Pool = ap
}
}
}
log.Debugf("Teleport instance %v started: %v/%v events received.",
i.Secrets.SiteName, len(receivedEvents), len(expectedEvents))
return nil
}
// ClientConfig is a client configuration
type ClientConfig struct {
// Login is SSH login name
Login string
// Cluster is a cluster name to connect to
Cluster string
// Host string is a target host to connect to
Host string
// Port is a target port to connect to
Port int
// Proxy is an optional alternative proxy to use
Proxy *ProxyConfig
// ForwardAgent controls if the client requests it's agent be forwarded to
// the server.
ForwardAgent bool
}
// NewClientWithCreds creates client with credentials
func (i *TeleInstance) NewClientWithCreds(cfg ClientConfig, creds UserCreds) (tc *client.TeleportClient, err error) {
clt, err := i.NewUnauthenticatedClient(cfg)
if err != nil {
return nil, trace.Wrap(err)
}
err = SetupUserCreds(clt, i.Config.Proxy.SSHAddr.Addr, creds)
if err != nil {
return nil, trace.Wrap(err)
}
return clt, nil
}
// NewUnauthenticatedClient returns a fully configured and pre-authenticated client
// (pre-authenticated with server CAs and signed session key)
func (i *TeleInstance) NewUnauthenticatedClient(cfg ClientConfig) (tc *client.TeleportClient, err error) {
keyDir, err := ioutil.TempDir(i.Config.DataDir, "tsh")
if err != nil {
return nil, err
}
proxyConf := &i.Config.Proxy
proxyHost, _, err := net.SplitHostPort(proxyConf.SSHAddr.Addr)
if err != nil {
return nil, trace.Wrap(err)
}
var webProxyAddr string
var sshProxyAddr string
if cfg.Proxy == nil {
webProxyAddr = proxyConf.WebAddr.Addr
sshProxyAddr = proxyConf.SSHAddr.Addr
} else {
webProxyAddr = net.JoinHostPort(proxyHost, strconv.Itoa(cfg.Proxy.WebPort))
sshProxyAddr = net.JoinHostPort(proxyHost, strconv.Itoa(cfg.Proxy.SSHPort))
}
cconf := &client.Config{
Username: cfg.Login,
Host: cfg.Host,
HostPort: cfg.Port,
HostLogin: cfg.Login,
InsecureSkipVerify: true,
KeysDir: keyDir,
SiteName: cfg.Cluster,
ForwardAgent: cfg.ForwardAgent,
WebProxyAddr: webProxyAddr,
SSHProxyAddr: sshProxyAddr,
}
return client.NewClient(cconf)
}
// NewClient returns a fully configured and pre-authenticated client
// (pre-authenticated with server CAs and signed session key)
func (i *TeleInstance) NewClient(cfg ClientConfig) (*client.TeleportClient, error) {
tc, err := i.NewUnauthenticatedClient(cfg)
if err != nil {
return nil, trace.Wrap(err)
}
// configures the client authenticate using the keys from 'secrets':
user, ok := i.Secrets.Users[cfg.Login]
if !ok {
return nil, trace.BadParameter("unknown login %q", cfg.Login)
}
if user.Key == nil {
return nil, trace.BadParameter("user %q has no key", cfg.Login)
}
_, err = tc.AddKey(cfg.Host, user.Key)
if err != nil {
return nil, trace.Wrap(err)
}
// tell the client to trust given CAs (from secrets). this is the
// equivalent of 'known hosts' in openssh
cas := i.Secrets.GetCAs()
for i := range cas {
err = tc.AddTrustedCA(cas[i])
if err != nil {
return nil, trace.Wrap(err)
}
}
return tc, nil
}
// StopProxy loops over the extra nodes in a TeleInstance and stops all
// nodes where the proxy server is enabled.
func (i *TeleInstance) StopProxy() error {
var errors []error
for _, p := range i.Nodes {
if p.Config.Proxy.Enabled {
if err := p.Close(); err != nil {
errors = append(errors, err)
log.Errorf("Failed closing extra proxy: %v.", err)
}
if err := p.Wait(); err != nil {
errors = append(errors, err)
log.Errorf("Failed to stop extra proxy: %v.", err)
}
}
}
return trace.NewAggregate(errors...)
}
// StopNodes stops additional nodes
func (i *TeleInstance) StopNodes() error {
var errors []error
for _, node := range i.Nodes {
if err := node.Close(); err != nil {
errors = append(errors, err)
log.Errorf("failed closing extra node %v", err)
}
if err := node.Wait(); err != nil {
errors = append(errors, err)
log.Errorf("failed stopping extra node %v", err)
}
}
return trace.NewAggregate(errors...)
}
func (i *TeleInstance) Stop(removeData bool) error {
if i.Config != nil && removeData {
err := os.RemoveAll(i.Config.DataDir)
if err != nil {
log.Error("failed removing temporary local Teleport directory", err)
}
}
log.Infof("Asking Teleport to stop")
err := i.Process.Close()
if err != nil {
log.Error(err)
return trace.Wrap(err)
}
defer func() {
log.Infof("Teleport instance '%v' stopped!", i.Secrets.SiteName)
}()
return i.Process.Wait()
}
func startAndWait(process *service.TeleportProcess, expectedEvents []string) ([]service.Event, error) {
// register to listen for all ready events on the broadcast channel
broadcastCh := make(chan service.Event)
for _, eventName := range expectedEvents {
process.WaitForEvent(context.TODO(), eventName, broadcastCh)
}
// start the process
err := process.Start()
if err != nil {
return nil, trace.Wrap(err)
}
// wait for all events to arrive or a timeout. if all the expected events
// from above are not received, this instance will not start
receivedEvents := []service.Event{}
timeoutCh := time.After(10 * time.Second)
for idx := 0; idx < len(expectedEvents); idx++ {
select {
case e := <-broadcastCh:
receivedEvents = append(receivedEvents, e)
case <-timeoutCh:
return nil, trace.BadParameter("timed out, only %v/%v events received. received: %v, expected: %v",
len(receivedEvents), len(expectedEvents), receivedEvents, expectedEvents)
}
}
// Not all services follow a non-blocking Start/Wait pattern. This means a
// *Ready event may be emit slightly before the service actually starts for
// blocking services. Long term those services should be re-factored, until
// then sleep for 250ms to handle this situation.
time.Sleep(250 * time.Millisecond)
return receivedEvents, nil
}
type proxyServer struct {
sync.Mutex
count int
}
// ServeHTTP only accepts the CONNECT verb and will tunnel your connection to
// the specified host. Also tracks the number of connections that it proxies for
// debugging purposes.
func (p *proxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Validate http connect parameters.
if r.Method != http.MethodConnect {
trace.WriteError(w, trace.BadParameter("%v not supported", r.Method))
return
}
if r.Host == "" {
trace.WriteError(w, trace.BadParameter("host not set"))
return
}
// Dial to the target host, this is done before hijacking the connection to
// ensure the target host is accessible.
dconn, err := net.Dial("tcp", r.Host)
if err != nil {
trace.WriteError(w, err)
return
}
defer dconn.Close()
// Once the client receives 200 OK, the rest of the data will no longer be
// http, but whatever protocol is being tunneled.
w.WriteHeader(http.StatusOK)
// Hijack request so we can get underlying connection.
hj, ok := w.(http.Hijacker)
if !ok {
trace.WriteError(w, trace.AccessDenied("unable to hijack connection"))
return
}
sconn, _, err := hj.Hijack()
if err != nil {
trace.WriteError(w, err)
return
}
defer sconn.Close()
// Success, we're proxying data now.
p.Lock()
p.count = p.count + 1
p.Unlock()
// Copy from src to dst and dst to src.
errc := make(chan error, 2)
replicate := func(dst io.Writer, src io.Reader) {
_, err := io.Copy(dst, src)
errc <- err
}
go replicate(sconn, dconn)
go replicate(dconn, sconn)
// Wait until done, error, or 10 second.
select {
case <-time.After(10 * time.Second):
case <-errc:
}
}
// Count returns the number of connections that have been proxied.
func (p *proxyServer) Count() int {
p.Lock()
defer p.Unlock()
return p.count
}
// discardServer is a SSH server that discards SSH exec requests and starts
// with the passed in host signer.
type discardServer struct {
sshServer *sshutils.Server
}
func newDiscardServer(host string, port int, hostSigner ssh.Signer) (*discardServer, error) {
ds := &discardServer{}
// create underlying ssh server
sshServer, err := sshutils.NewServer(
"integration-discard-server",
utils.NetAddr{AddrNetwork: "tcp", Addr: fmt.Sprintf("%v:%v", host, port)},
ds,
[]ssh.Signer{hostSigner},
sshutils.AuthMethods{
PublicKey: ds.userKeyAuth,
},
sshutils.SetInsecureSkipHostValidation(),
)
if err != nil {
return nil, trace.Wrap(err)
}
ds.sshServer = sshServer
return ds, nil
}
func (s *discardServer) userKeyAuth(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
return nil, nil
}
func (s *discardServer) Start() error {
return s.sshServer.Start()
}
func (s *discardServer) Stop() {
s.sshServer.Close()
}
func (s *discardServer) HandleNewChan(conn net.Conn, sconn *ssh.ServerConn, newChannel ssh.NewChannel) {
channel, reqs, err := newChannel.Accept()
if err != nil {
sconn.Close()
conn.Close()
return
}
go s.handleChannel(channel, reqs)
}
func (s *discardServer) handleChannel(channel ssh.Channel, reqs <-chan *ssh.Request) {
defer channel.Close()
for {
select {
case req := <-reqs:
if req == nil {
return
}
if req.Type == "exec" {
successPayload := ssh.Marshal(struct{ C uint32 }{C: uint32(0)})
channel.SendRequest("exit-status", false, successPayload)
if req.WantReply {
req.Reply(true, nil)
}
return
}
if req.WantReply {
req.Reply(true, nil)
}
}
}
}
// commandOptions controls how the SSH command is built.
type commandOptions struct {
forwardAgent bool
forcePTY bool
controlPath string
socketPath string
proxyPort string
nodePort string
command string
}
// externalSSHCommand runs an external SSH command (if an external ssh binary
// exists) with the passed in parameters.
func externalSSHCommand(o commandOptions) (*exec.Cmd, error) {
var execArgs []string
// Don't check the host certificate as part of the testing an external SSH
// client, this is done elsewhere.
execArgs = append(execArgs, "-oStrictHostKeyChecking=no")
execArgs = append(execArgs, "-oUserKnownHostsFile=/dev/null")
// ControlMaster is often used by applications like Ansible.
if o.controlPath != "" {
execArgs = append(execArgs, "-oControlMaster=auto")
execArgs = append(execArgs, "-oControlPersist=1s")
execArgs = append(execArgs, "-oConnectTimeout=2")
execArgs = append(execArgs, fmt.Sprintf("-oControlPath=%v", o.controlPath))
}
// The -tt flag is used to force PTY allocation. It's often used by
// applications like Ansible.
if o.forcePTY {
execArgs = append(execArgs, "-tt")
}
// Connect to node on the passed in port.
execArgs = append(execArgs, fmt.Sprintf("-p %v", o.nodePort))
// Build proxy command.
proxyCommand := []string{"ssh"}
proxyCommand = append(proxyCommand, "-oStrictHostKeyChecking=no")
proxyCommand = append(proxyCommand, "-oUserKnownHostsFile=/dev/null")
if o.forwardAgent {
proxyCommand = append(proxyCommand, "-oForwardAgent=yes")
}
proxyCommand = append(proxyCommand, fmt.Sprintf("-p %v", o.proxyPort))
proxyCommand = append(proxyCommand, `%r@localhost -s proxy:%h:%p`)
// Add in ProxyCommand option, needed for all Teleport connections.
execArgs = append(execArgs, fmt.Sprintf("-oProxyCommand=%v", strings.Join(proxyCommand, " ")))
// Add in the host to connect to and the command to run when connected.
execArgs = append(execArgs, Host)
execArgs = append(execArgs, o.command)
// Find the OpenSSH binary.
sshpath, err := exec.LookPath("ssh")
if err != nil {
return nil, trace.Wrap(err)
}
// Create an exec.Command and tell it where to find the SSH agent.
cmd, err := exec.Command(sshpath, execArgs...), nil
if err != nil {
return nil, trace.Wrap(err)
}
cmd.Env = []string{fmt.Sprintf("SSH_AUTH_SOCK=%v", o.socketPath)}
return cmd, nil
}
// createAgent creates a SSH agent with the passed in private key and
// certificate that can be used in tests. This is useful so tests don't
// clobber your system agent.
func createAgent(me *user.User, privateKeyByte []byte, certificateBytes []byte) (*teleagent.AgentServer, string, string, error) {
// create a path to the unix socket
sockDir, err := ioutil.TempDir("", "int-test")
if err != nil {
return nil, "", "", trace.Wrap(err)
}
sockPath := filepath.Join(sockDir, "agent.sock")
uid, err := strconv.Atoi(me.Uid)
if err != nil {
return nil, "", "", trace.Wrap(err)
}
gid, err := strconv.Atoi(me.Gid)
if err != nil {
return nil, "", "", trace.Wrap(err)
}
// transform the key and certificate bytes into something the agent can understand
publicKey, _, _, _, err := ssh.ParseAuthorizedKey(certificateBytes)
if err != nil {
return nil, "", "", trace.Wrap(err)
}
privateKey, err := ssh.ParseRawPrivateKey(privateKeyByte)
if err != nil {
return nil, "", "", trace.Wrap(err)
}
agentKey := agent.AddedKey{
PrivateKey: privateKey,
Certificate: publicKey.(*ssh.Certificate),
Comment: "",
LifetimeSecs: 0,
ConfirmBeforeUse: false,
}
// create a (unstarted) agent and add the key to it
teleAgent := teleagent.NewServer()
teleAgent.Add(agentKey)
// start the SSH agent
err = teleAgent.ListenUnixSocket(sockPath, uid, gid, 0600)
if err != nil {
return nil, "", "", trace.Wrap(err)
}
go teleAgent.Serve()
return teleAgent, sockDir, sockPath, nil
}
func closeAgent(teleAgent *teleagent.AgentServer, socketDirPath string) error {
err := teleAgent.Close()
if err != nil {
return trace.Wrap(err)
}
err = os.RemoveAll(socketDirPath)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// createWebClient builds a *client.WebClient that is used to simulate
// browser requests.
func createWebClient(cluster *TeleInstance, opts ...roundtrip.ClientParam) (*client.WebClient, error) {
// Craft URL to Web UI.
u := &url.URL{
Scheme: "https",
Host: cluster.Config.Proxy.WebAddr.Addr,
}
opts = append(opts, roundtrip.HTTPClient(client.NewInsecureWebClient()))
wc, err := client.NewWebClient(u.String(), opts...)
if err != nil {
return nil, trace.Wrap(err)
}
return wc, nil
}
func fatalIf(err error) {
if err != nil {
log.Fatalf("%v at %v", string(debug.Stack()), err)
}
}
func makeKey() (priv, pub []byte) {
k, err := native.New(context.TODO(), native.PrecomputeKeys(0))
if err != nil {
panic(err)
}
priv, pub, _ = k.GenerateKeyPair("")
return priv, pub
}