Added integration tests and minor fixes.

This commit is contained in:
Russell Jones 2017-12-12 18:17:18 -08:00
parent 81a8e6bca6
commit 3bfe61dc0b
16 changed files with 1170 additions and 358 deletions

View file

@ -124,7 +124,7 @@ test: $(VERSRC)
#
.PHONY: integration
integration:
go test -v ./integration/...
go test -v ./integration/... -check.v
# This rule triggers re-generation of version.go and gitref.go if Makefile changes
$(VERSRC): Makefile

View file

@ -8,10 +8,16 @@ import (
"net"
"net/http"
"os"
"os/exec"
"os/user"
"path/filepath"
"strconv"
"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"
@ -23,6 +29,8 @@ import (
"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/utils"
"github.com/gravitational/trace"
@ -253,7 +261,6 @@ func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *servic
tconf = service.MakeDefaultConfig()
}
tconf.DataDir = dataDir
tconf.Auth.ClusterConfig = services.DefaultClusterConfig()
tconf.Auth.ClusterName, err = services.NewClusterName(services.ClusterNameSpecV2{
ClusterName: i.Secrets.SiteName,
})
@ -294,16 +301,23 @@ func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *servic
Type: boltbk.GetName(),
Params: backend.Params{"path": dataDir},
}
tconf.Keygen = testauthority.New()
tconf.Auth.ClusterConfig = services.DefaultClusterConfig()
tconf.Keygen = testauthority.New()
i.Config = tconf
i.Process, err = service.NewTeleport(tconf)
if err != nil {
return trace.Wrap(err)
}
// create users and roles if they don't exist, or sign their keys if they're already present
// 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 {
@ -315,6 +329,12 @@ func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *servic
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.Set(services.ForwardAgent, true)
role.SetOptions(roleOptions)
err = auth.UpsertRole(role, backend.Forever)
if err != nil {
return trace.Wrap(err)
@ -358,30 +378,72 @@ func (i *TeleInstance) CreateEx(trustedSecrets []*InstanceSecrets, tconf *servic
return nil
}
// StartNode starts SSH node and connects it to the cluster
func (i *TeleInstance) StartNode(name string, sshPort, proxyWebPort, proxySSHPort int) error {
// StartNode starts SSH node and connects it to the cluster.
func (i *TeleInstance) StartNode(name string, sshPort int) (*service.TeleportProcess, error) {
dataDir, err := ioutil.TempDir("", "cluster-"+i.Secrets.SiteName)
if err != nil {
return nil, 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.DataDir = dataDir
tconf.CachePolicy = service.CachePolicy{Enabled: true}
tconf.Auth.Enabled = false
tconf.Proxy.Enabled = false
tconf.SSH.Enabled = true
tconf.SSH.Addr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", sshPort))
process, err := service.NewTeleport(tconf)
if err != nil {
return nil, trace.Wrap(err)
}
i.Nodes = append(i.Nodes, process)
err = process.Start()
if err != nil {
return nil, trace.Wrap(err)
}
return process, nil
}
// StartNodeAndProxy starts SSH node and proxy 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()
tconf.HostUUID = name
tconf.Hostname = name
tconf.DataDir = dataDir
tconf.Auth.Enabled = false
tconf.Proxy.Enabled = true
tconf.SSH.Enabled = true
tconf.SSH.Addr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", sshPort))
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.DataDir = dataDir
tconf.CachePolicy = service.CachePolicy{Enabled: true}
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
// Enable caching
tconf.CachePolicy = service.CachePolicy{Enabled: true}
tconf.SSH.Enabled = true
tconf.SSH.Addr.Addr = net.JoinHostPort(i.Hostname, fmt.Sprintf("%v", sshPort))
process, err := service.NewTeleport(tconf)
if err != nil {
@ -531,6 +593,9 @@ type ClientConfig struct {
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
}
// NewClient returns a fully configured and pre-authenticated client
@ -574,6 +639,7 @@ func (i *TeleInstance) NewClient(cfg ClientConfig) (tc *client.TeleportClient, e
InsecureSkipVerify: true,
KeysDir: keyDir,
SiteName: cfg.Cluster,
ForwardAgent: cfg.ForwardAgent,
}
cconf.SetProxy(proxyHost, proxyWebPort, proxySSHPort)
@ -723,6 +789,189 @@ func (p *proxyServer) Count() int {
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,
},
)
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)
}
}
}
}
// externalSSHCommand runs an external SSH command (if an external ssh binary
// exists) with the passed in parameters.
func externalSSHCommand(forwardAgent bool, socketPath string, proxyPort string, nodePort string, command string) (*exec.Cmd, error) {
var execArgs []string
// don't check the host certificate during tests
execArgs = append(execArgs, "-oStrictHostKeyChecking=no")
execArgs = append(execArgs, "-oUserKnownHostsFile=/dev/null")
// connect to node on the passed in port
execArgs = append(execArgs, "-p")
execArgs = append(execArgs, nodePort)
// build proxy command
var proxyCommand string
switch forwardAgent {
case true:
proxyCommand = fmt.Sprintf("ProxyCommand ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oForwardAgent=yes -p %v %%r@localhost -s proxy:%%h:%%p", proxyPort)
case false:
proxyCommand = fmt.Sprintf("ProxyCommand ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -p %v %%r@localhost -s proxy:%%h:%%p", proxyPort)
}
execArgs = append(execArgs, "-o")
execArgs = append(execArgs, proxyCommand)
// add in the host the connect to and the command to run when connected
execArgs = append(execArgs, Host)
execArgs = append(execArgs, command)
// find the ssh binary
sshpath, err := exec.LookPath("ssh")
if err != nil {
return nil, trace.Wrap(err)
}
// create 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", 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
}
func fatalIf(err error) {
if err != nil {
log.Fatal("", err)

File diff suppressed because it is too large Load diff

View file

@ -300,7 +300,7 @@ func (s *APISuite) TestGenerateKeysAndCerts(c *C) {
// user should not have agent forwarding
_, exists := parsedCert.Extensions[teleport.CertExtensionPermitAgentForwarding]
c.Assert(exists, Equals, false)
c.Assert(exists, Equals, true)
// now update role to permit agent forwarding
roleOptions := userRole.GetOptions()

View file

@ -408,26 +408,40 @@ func migrateCertAuthority(asrv *AuthServer) error {
return nil
}
// DELETE IN: 2.5.0
// All users will be migrated to the new roles in Teleport 2.4.0, which means
// this entire function can be removed in Teleport 2.5.0.
func migrateRoles(asrv *AuthServer) error {
roles, err := asrv.GetRoles()
if err != nil {
return trace.Wrap(err)
}
// loop over all roles and only migrate RoleV2 -> RoleV3
// loop over all roles and make sure any v3 roles have permit port
// forward and forward agent allowed
for i, _ := range roles {
role := roles[i]
_, ok := (role.GetRawObject()).(services.RoleV2)
if !ok {
continue
roleOptions := role.GetOptions()
_, err = roleOptions.GetBoolean(services.PortForwarding)
if err != nil {
roleOptions.Set(services.PortForwarding, true)
role.SetOptions(roleOptions)
}
_, err := roleOptions.GetBoolean(services.ForwardAgent)
if err != nil {
roleOptions.Set(services.ForwardAgent, true)
role.SetOptions(roleOptions)
}
// with RoleV2 we never had a TTL so upsert them forever
err = asrv.UpsertRole(role, backend.Forever)
if err != nil {
return trace.Wrap(err)
}
log.Infof("[MIGRATION] Updated Role: %v", role.GetName())
log.Infof("[MIGRATION] Updated Role: %v to include port_forwarding and forward_agent option", role.GetName())
}
return nil

View file

@ -28,6 +28,7 @@ import (
"github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/backend/boltbk"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
@ -252,3 +253,54 @@ func (s *AuthInitSuite) TestClusterID(c *C) {
c.Assert(err, IsNil)
c.Assert(cc.GetClusterID(), Equals, clusterID)
}
func (s *AuthInitSuite) TestOptions(c *C) {
bk, err := boltbk.New(backend.Params{"path": c.MkDir()})
c.Assert(err, IsNil)
clusterName, err := services.NewClusterName(services.ClusterNameSpecV2{
ClusterName: "me.localhost",
})
c.Assert(err, IsNil)
authServer, _, err := Init(InitConfig{
DataDir: c.MkDir(),
HostUUID: "00000000-0000-0000-0000-000000000000",
NodeName: "foo",
Backend: bk,
Authority: testauthority.New(),
ClusterConfig: services.DefaultClusterConfig(),
ClusterName: clusterName,
})
c.Assert(err, IsNil)
role := services.NewAdminRole()
role.SetOptions(services.RoleOptions{
services.MaxSessionTTL: services.NewDuration(defaults.MaxCertDuration),
})
err = authServer.UpsertRole(role, backend.Forever)
c.Assert(err, IsNil)
// do it again and make sure the options have been populated
authServer, _, err = Init(InitConfig{
DataDir: c.MkDir(),
HostUUID: "00000000-0000-0000-0000-000000000000",
NodeName: "foo",
Backend: bk,
Authority: testauthority.New(),
ClusterConfig: services.DefaultClusterConfig(),
ClusterName: clusterName,
})
c.Assert(err, IsNil)
role, err = authServer.GetRole(teleport.AdminRoleName)
c.Assert(err, IsNil)
portForward, err := role.GetOptions().GetBoolean(services.PortForwarding)
c.Assert(err, IsNil)
c.Assert(portForward, Equals, true)
forwardAgent, err := role.GetOptions().GetBoolean(services.ForwardAgent)
c.Assert(err, IsNil)
c.Assert(forwardAgent, Equals, true)
}

View file

@ -182,6 +182,9 @@ func (s *localSite) dialWithAgent(from net.Addr, to net.Addr, userAgent agent.Ag
SrcAddr: from,
DstAddr: to,
HostCertificate: hostCertificate,
Ciphers: s.srv.Config.Ciphers,
KEXAlgorithms: s.srv.Config.KEXAlgorithms,
MACAlgorithms: s.srv.Config.MACAlgorithms,
}
remoteServer, err := forward.New(serverConfig)
if err != nil {

View file

@ -396,20 +396,12 @@ func (s *remoteSite) dialAccessPoint(network, addr string) (net.Conn, error) {
}
func (s *remoteSite) DialAuthServer() (conn net.Conn, err error) {
// get list of remote auth servers
authServers, err := s.remoteClient.GetAuthServers()
conn, err = s.connThroughTunnel(chanTransportDialReq, RemoteAuthServer)
if err != nil {
return nil, trace.Wrap(err)
}
for _, authServer := range authServers {
conn, err = s.connThroughTunnel(chanTransportDialReq, authServer.GetAddr())
if err == nil {
return conn, nil
}
}
return nil, trace.ConnectionProblem(err, "unable to connect to auth server")
return conn, nil
}
// Dial is used to connect a requesting client (say, tsh) to an SSH server
@ -472,6 +464,9 @@ func (s *remoteSite) dialWithAgent(from, to net.Addr, userAgent agent.Agent) (ne
SrcAddr: from,
DstAddr: to,
HostCertificate: hostCertificate,
Ciphers: s.srv.Config.Ciphers,
KEXAlgorithms: s.srv.Config.KEXAlgorithms,
MACAlgorithms: s.srv.Config.MACAlgorithms,
}
remoteServer, err := forward.New(serverConfig)
if err != nil {

View file

@ -121,6 +121,18 @@ type Config struct {
// Clock is a clock used in the server, set up to
// wall clock if not set
Clock clockwork.Clock
// Ciphers is a list of ciphers that the server supports. If omitted,
// the defaults will be used.
Ciphers []string
// KEXAlgorithms is a list of key exchange (KEX) algorithms that the
// server supports. If omitted, the defaults will be used.
KEXAlgorithms []string
// MACAlgorithms is a list of message authentication codes (MAC) that
// the server supports. If omitted the defaults will be used.
MACAlgorithms []string
}
// CheckAndSetDefaults checks parameters and sets default values
@ -745,7 +757,7 @@ func newRemoteSite(srv *server, domainName string) (*remoteSite, error) {
// certificate cache is created in each site (instead of creating it in
// reversetunnel.server and passing it along) so that the host certificate
// is signed by the correct certificate authority.
certificateCache, err := NewHostCertificateCache(clt)
certificateCache, err := NewHostCertificateCache(srv.localAuthClient)
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -755,6 +755,9 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
Client: conn.Client,
},
},
Ciphers: cfg.Ciphers,
KEXAlgorithms: cfg.KEXAlgorithms,
MACAlgorithms: cfg.MACAlgorithms,
})
if err != nil {
return trace.Wrap(err)

View file

@ -93,6 +93,7 @@ func NewAdminRole() Role {
Options: RoleOptions{
MaxSessionTTL: NewDuration(defaults.MaxCertDuration),
PortForwarding: true,
ForwardAgent: true,
},
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},
@ -140,6 +141,7 @@ func RoleForUser(u User) Role {
Options: RoleOptions{
MaxSessionTTL: NewDuration(defaults.MaxCertDuration),
PortForwarding: true,
ForwardAgent: true,
},
Allow: RoleConditions{
Namespaces: []string{defaults.Namespace},

View file

@ -179,8 +179,8 @@ func (h *AuthHandlers) UserKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*s
}
h.Debugf("Successfully authenticated")
// see if the host user is valid (no need to do this in proxy mode)
if !h.isProxy() {
// see if the host user is valid, we only do this when logging into a teleport node
if h.isTeleportNode() {
_, err = user.Lookup(conn.User())
if err != nil {
host, _ := os.Hostname()
@ -424,6 +424,14 @@ func (h *AuthHandlers) isProxy() bool {
return false
}
func (h *AuthHandlers) isTeleportNode() bool {
if h.Component == teleport.ComponentNode {
return true
}
return false
}
// extractRolesFromCert extracts roles from certificate metadata extensions.
func extractRolesFromCert(cert *ssh.Certificate) ([]string, error) {
data, ok := cert.Extensions[teleport.CertExtensionTeleportRoles]

View file

@ -1,61 +0,0 @@
/*
Copyright 2017 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 forward
import (
"net"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/utils/proxy"
"github.com/gravitational/trace"
)
// newRemoteSession will create and return a *ssh.Client and *ssh.Session
// with a remote host.
func newRemoteSession(conn net.Conn, dstAddr string, systemLogin string, userAgent agent.Agent, authHandlers *srv.AuthHandlers) (*ssh.Client, *ssh.Session, error) {
// the proxy will use the agent that has been forwarded to it as the auth
// method when connecting to the remote host
if userAgent == nil {
return nil, nil, trace.AccessDenied("agent must be forwarded to proxy")
}
authMethod := ssh.PublicKeysCallback(userAgent.Signers)
clientConfig := &ssh.ClientConfig{
User: systemLogin,
Auth: []ssh.AuthMethod{
authMethod,
},
HostKeyCallback: authHandlers.HostKeyAuth,
Timeout: defaults.DefaultDialTimeout,
}
client, err := proxy.NewClientConnWithDeadline(conn, dstAddr, clientConfig)
if err != nil {
return nil, nil, trace.Wrap(err)
}
session, err := client.NewSession()
if err != nil {
return nil, nil, trace.Wrap(err)
}
return client, session, nil
}

View file

@ -35,6 +35,7 @@ import (
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/proxy"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
@ -69,10 +70,10 @@ type Server struct {
clientConn net.Conn
serverConn net.Conn
// agent is the SSH user agent that was forwarded to the proxy.
agent agent.Agent
// agentChannel is the channel over which communication with the agent occurs.
agentChannel ssh.Channel
// userAgent is the SSH user agent that was forwarded to the proxy.
userAgent agent.Agent
// userAgentChannel is the channel over which communication with the agent occurs.
userAgentChannel ssh.Channel
// hostCertificate is the SSH host certificate this in-memory server presents
// to the client.
@ -90,6 +91,16 @@ type Server struct {
// forwarding server.
termHandlers *srv.TermHandlers
// ciphers is a list of ciphers that the server supports. If omitted,
// the defaults will be used.
ciphers []string
// kexAlgorithms is a list of key exchange (KEX) algorithms that the
// server supports. If omitted, the defaults will be used.
kexAlgorithms []string
// macAlgorithms is a list of message authentication codes (MAC) that
// the server supports. If omitted the defaults will be used.
macAlgorithms []string
authClient auth.ClientI
auditLog events.IAuditLog
authService auth.AccessPoint
@ -106,6 +117,18 @@ type ServerConfig struct {
SrcAddr net.Addr
DstAddr net.Addr
HostCertificate ssh.Signer
// Ciphers is a list of ciphers that the server supports. If omitted,
// the defaults will be used.
Ciphers []string
// KEXAlgorithms is a list of key exchange (KEX) algorithms that the
// server supports. If omitted, the defaults will be used.
KEXAlgorithms []string
// MACAlgorithms is a list of message authentication codes (MAC) that
// the server supports. If omitted the defaults will be used.
MACAlgorithms []string
}
// CheckDefaults makes sure all required parameters are passed in.
@ -163,7 +186,7 @@ func New(c ServerConfig) (*Server, error) {
targetConn: c.TargetConn,
serverConn: serverConn,
clientConn: clientConn,
agent: c.UserAgent,
userAgent: c.UserAgent,
hostCertificate: c.HostCertificate,
authClient: c.AuthClient,
auditLog: c.AuthClient,
@ -282,7 +305,7 @@ func (s *Server) Serve() {
// build a remote session to the remote node
s.log.Debugf("Creating remote connection to %v@%v", sconn.User(), s.clientConn.RemoteAddr().String())
s.remoteClient, s.remoteSession, err = newRemoteSession(s.targetConn, s.targetConn.RemoteAddr().String(), sconn.User(), s.agent, s.authHandlers)
s.remoteClient, s.remoteSession, err = s.newRemoteSession(sconn.User())
if err != nil {
// reject the connection with an error so the client doesn't hang then
// close the connection
@ -306,6 +329,49 @@ func (s *Server) Serve() {
go s.handleConnection(ctx, sconn, identityContext, chans, reqs)
}
// newRemoteSession will create and return a *ssh.Client and *ssh.Session
// with a remote host.
func (s *Server) newRemoteSession(systemLogin string) (*ssh.Client, *ssh.Session, error) {
// the proxy will use the agent that has been forwarded to it as the auth
// method when connecting to the remote host
if s.userAgent == nil {
return nil, nil, trace.AccessDenied("agent must be forwarded to proxy")
}
authMethod := ssh.PublicKeysCallback(s.userAgent.Signers)
clientConfig := &ssh.ClientConfig{
User: systemLogin,
Auth: []ssh.AuthMethod{
authMethod,
},
HostKeyCallback: s.authHandlers.HostKeyAuth,
Timeout: defaults.DefaultDialTimeout,
}
if len(s.ciphers) > 0 {
clientConfig.Ciphers = s.ciphers
}
if len(s.kexAlgorithms) > 0 {
clientConfig.KeyExchanges = s.kexAlgorithms
}
if len(s.macAlgorithms) > 0 {
clientConfig.MACs = s.macAlgorithms
}
dstAddr := s.targetConn.RemoteAddr().String()
client, err := proxy.NewClientConnWithDeadline(s.targetConn, dstAddr, clientConfig)
if err != nil {
return nil, nil, trace.Wrap(err)
}
session, err := client.NewSession()
if err != nil {
return nil, nil, trace.Wrap(err)
}
return client, session, nil
}
func (s *Server) handleConnection(ctx context.Context, sconn *ssh.ServerConn, identityContext srv.IdentityContext, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) {
for {
select {
@ -428,7 +494,7 @@ func (s *Server) handleDirectTCPIPRequest(sconn *ssh.ServerConn, identityContext
ctx.RemoteClient = s.remoteClient
ctx.RemoteSession = s.remoteSession
ctx.SetAgent(s.agent, s.agentChannel)
ctx.SetAgent(s.userAgent, s.userAgentChannel)
ctx.AddCloser(ch)
ctx.AddCloser(sconn)
@ -507,7 +573,7 @@ func (s *Server) handleSessionRequests(sconn *ssh.ServerConn, identityContext sr
ctx.RemoteClient = s.remoteClient
ctx.RemoteSession = s.remoteSession
ctx.SetAgent(s.agent, s.agentChannel)
ctx.SetAgent(s.userAgent, s.userAgentChannel)
ctx.AddCloser(ch)
ctx.AddCloser(sconn)

View file

@ -86,7 +86,12 @@ const teleportTestUser = "teleport-test"
var _ = Suite(&SrvSuite{})
func (s *SrvSuite) SetUpSuite(c *C) {
var err error
utils.InitLoggerForTests()
s.freePorts, err = utils.GetFreeTCPPorts(100)
c.Assert(err, IsNil)
}
const hostID = "00000000-0000-0000-0000-000000000000"
@ -103,9 +108,6 @@ func (s *SrvSuite) SetUpTest(c *C) {
c.Assert(err, IsNil)
s.user = u.Username
s.freePorts, err = utils.GetFreeTCPPorts(10)
c.Assert(err, IsNil)
s.bk, err = dir.New(backend.Params{"path": s.dir})
c.Assert(err, IsNil)
@ -174,8 +176,7 @@ func (s *SrvSuite) SetUpTest(c *C) {
s.signer, err = sshutils.NewSigner(hpriv, hcert)
c.Assert(err, IsNil)
s.srvPort = s.freePorts[len(s.freePorts)-1]
s.freePorts = s.freePorts[:len(s.freePorts)-1]
s.srvPort = s.freePorts.Pop()
s.srvAddress = "127.0.0.1:" + s.srvPort
s.srvHostPort = fmt.Sprintf("%v:%v", s.domainName, s.srvPort)
@ -237,10 +238,6 @@ func (s *SrvSuite) TestAdvertiseAddr(c *C) {
// TestAgentForwardPermission makes sure if RBAC rules don't allow agent
// forwarding, we don't start an agent even if requested.
func (s *SrvSuite) TestAgentForwardPermission(c *C) {
se, err := s.clt.NewSession()
c.Assert(err, IsNil)
defer se.Close()
// make sure the role does not allow agent forwarding
roleName := services.RoleNameForUser(s.user)
role, err := s.a.GetRole(roleName)
@ -251,6 +248,10 @@ func (s *SrvSuite) TestAgentForwardPermission(c *C) {
err = s.a.UpsertRole(role, backend.Forever)
c.Assert(err, IsNil)
se, err := s.clt.NewSession()
c.Assert(err, IsNil)
defer se.Close()
// to interoperate with OpenSSH, requests for agent forwarding always succeed.
// however that does not mean the users agent will actually be forwarded.
err = agent.RequestAgentForwarding(se)
@ -497,8 +498,7 @@ func (s *SrvSuite) testClient(c *C, proxyAddr, targetAddr, remoteAddr string, ss
func (s *SrvSuite) TestProxyReverseTunnel(c *C) {
log.Infof("[TEST START] TestProxyReverseTunnel")
reverseTunnelPort := s.freePorts[len(s.freePorts)-1]
s.freePorts = s.freePorts[:len(s.freePorts)-1]
reverseTunnelPort := s.freePorts.Pop()
reverseTunnelAddress := utils.NetAddr{AddrNetwork: "tcp", Addr: fmt.Sprintf("%v:%v", s.domainName, reverseTunnelPort)}
reverseTunnelServer, err := reversetunnel.NewServer(reversetunnel.Config{
ID: s.domainName,
@ -587,8 +587,7 @@ func (s *SrvSuite) TestProxyReverseTunnel(c *C) {
s.testClient(c, proxy.Addr(), s.srvHostPort, s.srv.Addr(), sshConfig)
// adding new node
bobAddr := "127.0.0.1:" + s.freePorts[len(s.freePorts)-1]
s.freePorts = s.freePorts[:len(s.freePorts)-1]
bobAddr := "127.0.0.1:" + s.freePorts.Pop()
srv2, err := New(
utils.NetAddr{AddrNetwork: "tcp", Addr: bobAddr},
"bob",
@ -666,8 +665,7 @@ func (s *SrvSuite) TestProxyReverseTunnel(c *C) {
func (s *SrvSuite) TestProxyRoundRobin(c *C) {
log.Infof("[TEST START] TestProxyRoundRobin")
reverseTunnelPort := s.freePorts[len(s.freePorts)-1]
s.freePorts = s.freePorts[:len(s.freePorts)-1]
reverseTunnelPort := s.freePorts.Pop()
reverseTunnelAddress := utils.NetAddr{
AddrNetwork: "tcp",
Addr: fmt.Sprintf("%v:%v", s.domainName, reverseTunnelPort),
@ -894,8 +892,7 @@ func (s *SrvSuite) TestLimiter(c *C) {
)
c.Assert(err, IsNil)
srvAddress := "127.0.0.1:" + s.freePorts[len(s.freePorts)-1]
s.freePorts = s.freePorts[:len(s.freePorts)-1]
srvAddress := "127.0.0.1:" + s.freePorts.Pop()
srv, err := New(
utils.NetAddr{AddrNetwork: "tcp", Addr: srvAddress},
s.domainName,

View file

@ -122,25 +122,11 @@ func (p *PortList) Pop() string {
return val
}
// GetFreeTCPPorts returns a lit of available ports on localhost
// used for testing
// GetFreeTCPPorts returns n ports starting from port 20000.
func GetFreeTCPPorts(n int) (PortList, error) {
list := make(PortList, 0, n)
for i := 0; i < n; i++ {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return nil, trace.Wrap(err)
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
return nil, trace.Wrap(err)
}
defer listener.Close()
tcpAddr, ok := listener.Addr().(*net.TCPAddr)
if !ok {
return nil, trace.Errorf("Can't get tcp address")
}
list = append(list, strconv.Itoa(tcpAddr.Port))
for i := 20000; i < 20000+n; i++ {
list = append(list, strconv.Itoa(i))
}
return list, nil
}