2016-04-08 01:13:13 +00:00
|
|
|
/*
|
2018-12-12 00:22:44 +00:00
|
|
|
Copyright 2016-2019 Gravitational, Inc.
|
2016-04-08 01:13:13 +00:00
|
|
|
|
|
|
|
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 (
|
2019-02-15 02:15:45 +00:00
|
|
|
"bufio"
|
2016-04-14 18:43:08 +00:00
|
|
|
"bytes"
|
2016-12-23 19:50:32 +00:00
|
|
|
"context"
|
2018-09-06 21:52:41 +00:00
|
|
|
"crypto/x509"
|
2019-02-15 02:15:45 +00:00
|
|
|
"encoding/json"
|
2016-04-14 20:30:13 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2017-05-31 18:48:20 +00:00
|
|
|
"io/ioutil"
|
2018-08-11 00:50:06 +00:00
|
|
|
"net"
|
2017-05-31 18:48:20 +00:00
|
|
|
"net/http/httptest"
|
|
|
|
"net/url"
|
2016-04-14 18:43:08 +00:00
|
|
|
"os"
|
2017-12-13 02:17:18 +00:00
|
|
|
"os/exec"
|
2016-04-11 23:30:58 +00:00
|
|
|
"os/user"
|
2017-05-31 18:48:20 +00:00
|
|
|
"path/filepath"
|
2017-12-31 23:31:57 +00:00
|
|
|
"regexp"
|
2020-12-07 14:35:15 +00:00
|
|
|
"runtime/pprof"
|
2016-04-08 17:38:19 +00:00
|
|
|
"strconv"
|
2016-05-04 23:49:59 +00:00
|
|
|
"strings"
|
2020-04-17 15:55:48 +00:00
|
|
|
"sync"
|
2016-04-08 01:13:13 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
|
2017-05-20 02:03:28 +00:00
|
|
|
"github.com/gravitational/teleport"
|
2017-09-10 03:59:38 +00:00
|
|
|
"github.com/gravitational/teleport/lib"
|
2017-12-13 02:17:18 +00:00
|
|
|
"github.com/gravitational/teleport/lib/auth"
|
2016-04-11 23:30:58 +00:00
|
|
|
"github.com/gravitational/teleport/lib/auth/testauthority"
|
2019-11-16 00:39:40 +00:00
|
|
|
"github.com/gravitational/teleport/lib/bpf"
|
2016-10-22 02:11:13 +00:00
|
|
|
"github.com/gravitational/teleport/lib/client"
|
2016-12-19 00:58:53 +00:00
|
|
|
"github.com/gravitational/teleport/lib/defaults"
|
2016-05-01 08:36:21 +00:00
|
|
|
"github.com/gravitational/teleport/lib/events"
|
2020-09-14 02:48:13 +00:00
|
|
|
"github.com/gravitational/teleport/lib/fixtures"
|
2018-02-24 01:23:09 +00:00
|
|
|
"github.com/gravitational/teleport/lib/pam"
|
2019-05-03 04:34:15 +00:00
|
|
|
"github.com/gravitational/teleport/lib/reversetunnel"
|
2017-05-20 19:52:03 +00:00
|
|
|
"github.com/gravitational/teleport/lib/service"
|
2017-05-20 02:03:28 +00:00
|
|
|
"github.com/gravitational/teleport/lib/services"
|
2016-05-01 08:36:21 +00:00
|
|
|
"github.com/gravitational/teleport/lib/session"
|
2019-11-16 00:39:40 +00:00
|
|
|
"github.com/gravitational/teleport/lib/srv"
|
2021-01-29 18:28:24 +00:00
|
|
|
"github.com/gravitational/teleport/lib/sshutils"
|
2016-04-08 02:01:31 +00:00
|
|
|
"github.com/gravitational/teleport/lib/utils"
|
2020-12-07 14:35:15 +00:00
|
|
|
"github.com/gravitational/teleport/lib/utils/testlog"
|
2016-04-08 02:01:31 +00:00
|
|
|
|
2017-05-20 02:03:28 +00:00
|
|
|
"github.com/gravitational/trace"
|
2017-08-22 22:30:30 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2016-04-08 01:13:13 +00:00
|
|
|
"gopkg.in/check.v1"
|
|
|
|
)
|
|
|
|
|
2016-04-13 01:44:09 +00:00
|
|
|
const (
|
2020-02-01 00:33:57 +00:00
|
|
|
HostID = "00000000-0000-0000-0000-000000000000"
|
|
|
|
Site = "local-site"
|
2016-04-14 20:30:13 +00:00
|
|
|
|
2020-09-14 02:48:13 +00:00
|
|
|
AllocatePortsNum = 600
|
2016-04-13 01:44:09 +00:00
|
|
|
)
|
|
|
|
|
2016-04-08 17:38:19 +00:00
|
|
|
type IntSuite struct {
|
2016-04-14 20:30:13 +00:00
|
|
|
ports utils.PortList
|
2016-04-11 23:30:58 +00:00
|
|
|
me *user.User
|
2016-04-13 01:44:09 +00:00
|
|
|
// priv/pub pair to avoid re-generating it
|
|
|
|
priv []byte
|
|
|
|
pub []byte
|
2020-12-07 14:35:15 +00:00
|
|
|
// log defines the test-specific logger
|
|
|
|
log utils.Logger
|
|
|
|
w *testlog.TestWrapper
|
2016-04-08 17:38:19 +00:00
|
|
|
}
|
2016-04-08 01:13:13 +00:00
|
|
|
|
|
|
|
// bootstrap check
|
|
|
|
func TestIntegrations(t *testing.T) { check.TestingT(t) }
|
|
|
|
|
2016-05-08 18:53:57 +00:00
|
|
|
var _ = check.Suite(&IntSuite{})
|
2016-04-08 01:13:13 +00:00
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
// TestMain will re-execute Teleport to run a command if "exec" is passed to
|
|
|
|
// it as an argument. Otherwise it will run tests as normal.
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
// If the test is re-executing itself, execute the command that comes over
|
|
|
|
// the pipe.
|
2020-02-12 21:29:30 +00:00
|
|
|
if len(os.Args) == 2 &&
|
|
|
|
(os.Args[1] == teleport.ExecSubCommand || os.Args[1] == teleport.ForwardSubCommand) {
|
|
|
|
srv.RunAndExit(os.Args[1])
|
2019-11-16 00:39:40 +00:00
|
|
|
return
|
|
|
|
}
|
2016-04-14 18:43:08 +00:00
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
// Otherwise run tests as normal.
|
|
|
|
code := m.Run()
|
|
|
|
os.Exit(code)
|
2016-06-17 06:50:12 +00:00
|
|
|
}
|
|
|
|
|
2016-04-08 01:13:13 +00:00
|
|
|
func (s *IntSuite) SetUpSuite(c *check.C) {
|
2019-12-07 00:17:50 +00:00
|
|
|
utils.InitLoggerForTests(testing.Verbose())
|
2016-08-28 21:01:57 +00:00
|
|
|
SetTestTimeouts(time.Millisecond * time.Duration(100))
|
2016-04-08 17:38:19 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
var err error
|
2016-05-08 18:53:57 +00:00
|
|
|
s.priv, s.pub, err = testauthority.New().GenerateKeyPair("")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2019-05-14 22:16:27 +00:00
|
|
|
// Find AllocatePortsNum free listening ports to use.
|
2016-04-14 20:30:13 +00:00
|
|
|
s.ports, err = utils.GetFreeTCPPorts(AllocatePortsNum)
|
2016-04-08 17:38:19 +00:00
|
|
|
if err != nil {
|
|
|
|
c.Fatal(err)
|
|
|
|
}
|
2016-04-11 23:30:58 +00:00
|
|
|
s.me, _ = user.Current()
|
2016-04-14 18:43:08 +00:00
|
|
|
|
|
|
|
// close & re-open stdin because 'go test' runs with os.stdin connected to /dev/null
|
2016-04-14 21:42:10 +00:00
|
|
|
stdin, err := os.Open("/dev/tty")
|
2020-12-07 14:35:15 +00:00
|
|
|
if err == nil {
|
2016-04-14 21:42:10 +00:00
|
|
|
os.Stdin.Close()
|
|
|
|
os.Stdin = stdin
|
|
|
|
}
|
2016-04-08 01:13:13 +00:00
|
|
|
}
|
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
func (s *IntSuite) TearDownSuite(c *check.C) {
|
|
|
|
var err error
|
|
|
|
// restore os.Stdin to its original condition: connected to /dev/null
|
|
|
|
os.Stdin.Close()
|
|
|
|
os.Stdin, err = os.Open("/dev/null")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IntSuite) SetUpTest(c *check.C) {
|
|
|
|
os.RemoveAll(client.FullProfilePath(""))
|
|
|
|
}
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// setUpTest configures the specific test identified with the given c.
|
|
|
|
// Note, that this c is different from the one passed into SetUpTest/TearDownTest
|
|
|
|
// and reflects the actual test's state - e.g. c.Failed() will properly reflect whether
|
|
|
|
// the test failed
|
|
|
|
func (s *IntSuite) setUpTest(c *check.C) {
|
|
|
|
s.w = testlog.NewCheckTestWrapper(c)
|
|
|
|
s.log = s.w.Log
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IntSuite) tearDownTest(c *check.C) {
|
|
|
|
s.w.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// newUnstartedTeleport helper returns a created but not started Teleport instance pre-configured
|
2017-12-13 02:17:18 +00:00
|
|
|
// with the current user os.user.Current().
|
2018-04-08 21:37:33 +00:00
|
|
|
func (s *IntSuite) newUnstartedTeleport(c *check.C, logins []string, enableSSH bool) *TeleInstance {
|
2020-12-07 14:35:15 +00:00
|
|
|
t := s.newTeleportInstance(c)
|
2016-05-04 23:49:59 +00:00
|
|
|
// use passed logins, but use suite's default login if nothing was passed
|
Fix remaining gosimple findings
List of fixed items:
```
integration/helpers.go:1279:2 gosimple S1000: should use for range instead of for { select {} }
integration/integration_test.go:144:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:173:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:296:28 gosimple S1019: should use make(chan error) instead
integration/integration_test.go:570:41 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:685:40 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:759:33 gosimple S1019: should use make(chan string) instead
lib/auth/init_test.go:62:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/auth/tls_test.go:1658:22 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/backend/dynamo/dynamodbbk.go:420:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/dynamo/dynamodbbk.go:656:12 gosimple S1039: unnecessary use of fmt.Sprintf
lib/backend/etcdbk/etcd.go:458:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/firestore/firestorebk.go:407:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:317:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:336:6 gosimple S1004: should use !bytes.Equal(value, expected.Value) instead
lib/backend/memory/memory.go:365:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/memory/memory.go:376:5 gosimple S1004: should use !bytes.Equal(existingItem.Value, expected.Value) instead
lib/backend/test/suite.go:327:10 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/client/api.go:1410:9 gosimple S1003: should use strings.ContainsRune(name, ':') instead
lib/client/api.go:2355:32 gosimple S1019: should use make([]ForwardedPort, len(spec)) instead
lib/client/keyagent_test.go:85:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/client/player.go:54:33 gosimple S1019: should use make(chan int) instead
lib/config/configuration.go:1024:52 gosimple S1019: should use make(services.CommandLabels) instead
lib/config/configuration.go:1025:44 gosimple S1019: should use make(map[string]string) instead
lib/config/configuration.go:930:21 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleNode) instead
lib/config/configuration.go:931:22 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleAuthService) instead
lib/config/configuration.go:932:23 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleProxy) instead
lib/service/supervisor.go:387:2 gosimple S1001: should use copy() instead of a loop
lib/tlsca/parsegen.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:167:40 gosimple S1010: should omit second index in slice, s[a:len(s)] is identical to s[a:]
lib/utils/certs.go:204:5 gosimple S1004: should use !bytes.Equal(certificateChain[0].SubjectKeyId, certificateChain[0].AuthorityKeyId) instead
lib/utils/parse/parse.go:116:45 gosimple S1003: should use strings.Contains(variable, "}}") instead
lib/utils/parse/parse.go:116:6 gosimple S1003: should use strings.Contains(variable, "{{") instead
lib/utils/socks/socks.go:192:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/utils/socks/socks.go:199:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/web/apiserver.go:1054:18 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/web/apiserver.go:1954:9 gosimple S1039: unnecessary use of fmt.Sprintf
tool/tsh/tsh.go:1193:14 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
```
2020-05-20 18:38:32 +00:00
|
|
|
if len(logins) == 0 {
|
2016-05-04 23:49:59 +00:00
|
|
|
logins = []string{s.me.Username}
|
|
|
|
}
|
|
|
|
for _, login := range logins {
|
|
|
|
t.AddUser(login, []string{login})
|
|
|
|
}
|
2017-12-13 02:17:18 +00:00
|
|
|
if err := t.Create(nil, enableSSH, nil); err != nil {
|
|
|
|
c.Fatalf("Unexpected response from Create: %v", err)
|
2016-04-13 01:44:09 +00:00
|
|
|
}
|
2018-04-08 21:37:33 +00:00
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
// newTeleport helper returns a running Teleport instance pre-configured
|
|
|
|
// with the current user os.user.Current().
|
|
|
|
func (s *IntSuite) newTeleport(c *check.C, logins []string, enableSSH bool) *TeleInstance {
|
|
|
|
t := s.newUnstartedTeleport(c, logins, enableSSH)
|
2017-12-13 02:17:18 +00:00
|
|
|
if err := t.Start(); err != nil {
|
|
|
|
c.Fatalf("Unexpected response from Start: %v", err)
|
2016-04-13 01:44:09 +00:00
|
|
|
}
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2020-06-26 20:58:40 +00:00
|
|
|
// newTeleportIoT helper returns a running Teleport instance with Host as a
|
|
|
|
// reversetunnel node.
|
|
|
|
func (s *IntSuite) newTeleportIoT(c *check.C, logins []string) *TeleInstance {
|
|
|
|
// Create a Teleport instance with Auth/Proxy.
|
|
|
|
mainConfig := func() *service.Config {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2020-06-26 20:58:40 +00:00
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = false
|
|
|
|
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
main := s.newTeleportWithConfig(c, logins, nil, mainConfig())
|
|
|
|
|
|
|
|
// Create a Teleport instance with a Node.
|
|
|
|
nodeConfig := func() *service.Config {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2020-06-26 20:58:40 +00:00
|
|
|
tconf.Hostname = Host
|
|
|
|
tconf.Token = "token"
|
|
|
|
tconf.AuthServers = []utils.NetAddr{
|
2020-12-07 14:35:15 +00:00
|
|
|
{
|
2020-06-26 20:58:40 +00:00
|
|
|
AddrNetwork: "tcp",
|
|
|
|
Addr: net.JoinHostPort(Loopback, main.GetPortWeb()),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
tconf.Auth.Enabled = false
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = false
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
_, err := main.StartReverseTunnelNode(nodeConfig())
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
return main
|
|
|
|
}
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// newTeleportWithConfig is a helper function that will create a running
|
|
|
|
// Teleport instance with the passed in user, instance secrets, and Teleport
|
|
|
|
// configuration.
|
|
|
|
func (s *IntSuite) newTeleportWithConfig(c *check.C, logins []string, instanceSecrets []*InstanceSecrets, teleportConfig *service.Config) *TeleInstance {
|
2020-12-07 14:35:15 +00:00
|
|
|
t := s.newTeleportInstance(c)
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// use passed logins, but use suite's default login if nothing was passed
|
Fix remaining gosimple findings
List of fixed items:
```
integration/helpers.go:1279:2 gosimple S1000: should use for range instead of for { select {} }
integration/integration_test.go:144:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:173:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:296:28 gosimple S1019: should use make(chan error) instead
integration/integration_test.go:570:41 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:685:40 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:759:33 gosimple S1019: should use make(chan string) instead
lib/auth/init_test.go:62:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/auth/tls_test.go:1658:22 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/backend/dynamo/dynamodbbk.go:420:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/dynamo/dynamodbbk.go:656:12 gosimple S1039: unnecessary use of fmt.Sprintf
lib/backend/etcdbk/etcd.go:458:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/firestore/firestorebk.go:407:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:317:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:336:6 gosimple S1004: should use !bytes.Equal(value, expected.Value) instead
lib/backend/memory/memory.go:365:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/memory/memory.go:376:5 gosimple S1004: should use !bytes.Equal(existingItem.Value, expected.Value) instead
lib/backend/test/suite.go:327:10 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/client/api.go:1410:9 gosimple S1003: should use strings.ContainsRune(name, ':') instead
lib/client/api.go:2355:32 gosimple S1019: should use make([]ForwardedPort, len(spec)) instead
lib/client/keyagent_test.go:85:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/client/player.go:54:33 gosimple S1019: should use make(chan int) instead
lib/config/configuration.go:1024:52 gosimple S1019: should use make(services.CommandLabels) instead
lib/config/configuration.go:1025:44 gosimple S1019: should use make(map[string]string) instead
lib/config/configuration.go:930:21 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleNode) instead
lib/config/configuration.go:931:22 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleAuthService) instead
lib/config/configuration.go:932:23 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleProxy) instead
lib/service/supervisor.go:387:2 gosimple S1001: should use copy() instead of a loop
lib/tlsca/parsegen.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:167:40 gosimple S1010: should omit second index in slice, s[a:len(s)] is identical to s[a:]
lib/utils/certs.go:204:5 gosimple S1004: should use !bytes.Equal(certificateChain[0].SubjectKeyId, certificateChain[0].AuthorityKeyId) instead
lib/utils/parse/parse.go:116:45 gosimple S1003: should use strings.Contains(variable, "}}") instead
lib/utils/parse/parse.go:116:6 gosimple S1003: should use strings.Contains(variable, "{{") instead
lib/utils/socks/socks.go:192:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/utils/socks/socks.go:199:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/web/apiserver.go:1054:18 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/web/apiserver.go:1954:9 gosimple S1039: unnecessary use of fmt.Sprintf
tool/tsh/tsh.go:1193:14 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
```
2020-05-20 18:38:32 +00:00
|
|
|
if len(logins) == 0 {
|
2017-12-13 02:17:18 +00:00
|
|
|
logins = []string{s.me.Username}
|
|
|
|
}
|
|
|
|
for _, login := range logins {
|
|
|
|
t.AddUser(login, []string{login})
|
|
|
|
}
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// create a new teleport instance with passed in configuration
|
|
|
|
if err := t.CreateEx(instanceSecrets, teleportConfig); err != nil {
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Fatalf("Unexpected response from CreateEx: %v", trace.DebugReport(err))
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
|
|
|
if err := t.Start(); err != nil {
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Fatalf("Unexpected response from Start: %v", trace.DebugReport(err))
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
return t
|
|
|
|
}
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-31 23:31:57 +00:00
|
|
|
// TestAuditOn creates a live session, records a bunch of data through it
|
2017-12-13 02:17:18 +00:00
|
|
|
// and then reads it back and compares against simulated reality.
|
2017-12-31 23:31:57 +00:00
|
|
|
func (s *IntSuite) TestAuditOn(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
var tests = []struct {
|
2020-12-07 14:35:15 +00:00
|
|
|
comment string
|
2017-12-13 02:17:18 +00:00
|
|
|
inRecordLocation string
|
|
|
|
inForwardAgent bool
|
2018-03-04 02:26:44 +00:00
|
|
|
auditSessionsURI string
|
2017-12-13 02:17:18 +00:00
|
|
|
}{
|
|
|
|
{
|
2020-12-07 14:35:15 +00:00
|
|
|
comment: "normal teleport",
|
2018-03-04 02:26:44 +00:00
|
|
|
inRecordLocation: services.RecordAtNode,
|
|
|
|
inForwardAgent: false,
|
2017-12-13 02:17:18 +00:00
|
|
|
},
|
|
|
|
{
|
2020-12-07 14:35:15 +00:00
|
|
|
comment: "recording proxy",
|
2018-03-04 02:26:44 +00:00
|
|
|
inRecordLocation: services.RecordAtProxy,
|
|
|
|
inForwardAgent: true,
|
|
|
|
},
|
|
|
|
{
|
2020-12-07 14:35:15 +00:00
|
|
|
comment: "normal teleport with upload to file server",
|
2018-03-04 02:26:44 +00:00
|
|
|
inRecordLocation: services.RecordAtNode,
|
|
|
|
inForwardAgent: false,
|
|
|
|
auditSessionsURI: c.MkDir(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
inRecordLocation: services.RecordAtProxy,
|
|
|
|
inForwardAgent: false,
|
|
|
|
auditSessionsURI: c.MkDir(),
|
2017-12-13 02:17:18 +00:00
|
|
|
},
|
2020-07-15 00:15:01 +00:00
|
|
|
{
|
2020-12-07 14:35:15 +00:00
|
|
|
comment: "normal teleport, sync recording",
|
2020-07-15 00:15:01 +00:00
|
|
|
inRecordLocation: services.RecordAtNodeSync,
|
|
|
|
inForwardAgent: false,
|
|
|
|
},
|
|
|
|
{
|
2020-12-07 14:35:15 +00:00
|
|
|
comment: "recording proxy, sync recording",
|
2020-07-15 00:15:01 +00:00
|
|
|
inRecordLocation: services.RecordAtProxySync,
|
|
|
|
inForwardAgent: true,
|
|
|
|
},
|
2016-05-04 23:49:59 +00:00
|
|
|
}
|
2016-05-21 02:35:16 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
for _, tt := range tests {
|
2020-12-07 14:35:15 +00:00
|
|
|
comment := check.Commentf(tt.comment)
|
2017-12-13 02:17:18 +00:00
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: tt.inRecordLocation,
|
2018-03-04 02:26:44 +00:00
|
|
|
Audit: services.AuditConfig{AuditSessionsURI: tt.auditSessionsURI},
|
2019-02-21 01:47:50 +00:00
|
|
|
LocalAuth: services.NewBool(true),
|
2017-12-13 02:17:18 +00:00
|
|
|
})
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2017-12-13 02:17:18 +00:00
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = true
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
t := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2016-05-06 06:51:56 +00:00
|
|
|
|
2018-08-11 00:50:06 +00:00
|
|
|
// Start a node.
|
2017-12-13 02:17:18 +00:00
|
|
|
nodeSSHPort := s.getPorts(1)[0]
|
2018-08-11 00:50:06 +00:00
|
|
|
nodeConfig := func() *service.Config {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2018-08-11 00:50:06 +00:00
|
|
|
|
|
|
|
tconf.HostUUID = "node"
|
|
|
|
tconf.Hostname = "node"
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.SSH.Addr.Addr = net.JoinHostPort(t.Hostname, fmt.Sprintf("%v", nodeSSHPort))
|
|
|
|
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
nodeProcess, err := t.StartNode(nodeConfig())
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// get access to a authClient for the cluster
|
|
|
|
site := t.GetSiteAPI(Site)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(site, check.NotNil, comment)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
|
|
|
// wait 10 seconds for both nodes to show up, otherwise
|
|
|
|
// we'll have trouble connecting to the node below.
|
|
|
|
waitForNodes := func(site auth.ClientI, count int) error {
|
|
|
|
tickCh := time.Tick(500 * time.Millisecond)
|
|
|
|
stopCh := time.After(10 * time.Second)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-tickCh:
|
2018-06-29 18:39:06 +00:00
|
|
|
nodesInSite, err := site.GetNodes(defaults.Namespace, services.SkipValidation())
|
2018-02-08 02:32:50 +00:00
|
|
|
if err != nil && !trace.IsNotFound(err) {
|
2017-12-13 02:17:18 +00:00
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
if got, want := len(nodesInSite), count; got == want {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
case <-stopCh:
|
|
|
|
return trace.BadParameter("waited 10s, did find %v nodes", count)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = waitForNodes(site, 2)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// should have no sessions:
|
|
|
|
sessions, err := site.GetSessions(defaults.Namespace)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
|
|
|
c.Assert(len(sessions), check.Equals, 0, comment)
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// create interactive session (this goroutine is this user's terminal time)
|
Fix remaining gosimple findings
List of fixed items:
```
integration/helpers.go:1279:2 gosimple S1000: should use for range instead of for { select {} }
integration/integration_test.go:144:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:173:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:296:28 gosimple S1019: should use make(chan error) instead
integration/integration_test.go:570:41 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:685:40 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:759:33 gosimple S1019: should use make(chan string) instead
lib/auth/init_test.go:62:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/auth/tls_test.go:1658:22 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/backend/dynamo/dynamodbbk.go:420:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/dynamo/dynamodbbk.go:656:12 gosimple S1039: unnecessary use of fmt.Sprintf
lib/backend/etcdbk/etcd.go:458:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/firestore/firestorebk.go:407:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:317:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:336:6 gosimple S1004: should use !bytes.Equal(value, expected.Value) instead
lib/backend/memory/memory.go:365:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/memory/memory.go:376:5 gosimple S1004: should use !bytes.Equal(existingItem.Value, expected.Value) instead
lib/backend/test/suite.go:327:10 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/client/api.go:1410:9 gosimple S1003: should use strings.ContainsRune(name, ':') instead
lib/client/api.go:2355:32 gosimple S1019: should use make([]ForwardedPort, len(spec)) instead
lib/client/keyagent_test.go:85:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/client/player.go:54:33 gosimple S1019: should use make(chan int) instead
lib/config/configuration.go:1024:52 gosimple S1019: should use make(services.CommandLabels) instead
lib/config/configuration.go:1025:44 gosimple S1019: should use make(map[string]string) instead
lib/config/configuration.go:930:21 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleNode) instead
lib/config/configuration.go:931:22 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleAuthService) instead
lib/config/configuration.go:932:23 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleProxy) instead
lib/service/supervisor.go:387:2 gosimple S1001: should use copy() instead of a loop
lib/tlsca/parsegen.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:167:40 gosimple S1010: should omit second index in slice, s[a:len(s)] is identical to s[a:]
lib/utils/certs.go:204:5 gosimple S1004: should use !bytes.Equal(certificateChain[0].SubjectKeyId, certificateChain[0].AuthorityKeyId) instead
lib/utils/parse/parse.go:116:45 gosimple S1003: should use strings.Contains(variable, "}}") instead
lib/utils/parse/parse.go:116:6 gosimple S1003: should use strings.Contains(variable, "{{") instead
lib/utils/socks/socks.go:192:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/utils/socks/socks.go:199:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/web/apiserver.go:1054:18 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/web/apiserver.go:1954:9 gosimple S1039: unnecessary use of fmt.Sprintf
tool/tsh/tsh.go:1193:14 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
```
2020-05-20 18:38:32 +00:00
|
|
|
endC := make(chan error)
|
2017-12-13 02:17:18 +00:00
|
|
|
myTerm := NewTerminal(250)
|
|
|
|
go func() {
|
|
|
|
cl, err := t.NewClient(ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: nodeSSHPort,
|
|
|
|
ForwardAgent: tt.inForwardAgent,
|
|
|
|
})
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2020-04-17 15:55:48 +00:00
|
|
|
cl.Stdout = myTerm
|
|
|
|
cl.Stdin = myTerm
|
2017-12-13 02:17:18 +00:00
|
|
|
err = cl.SSH(context.TODO(), []string{}, false)
|
|
|
|
endC <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
// wait until we've found the session in the audit log
|
|
|
|
getSession := func(site auth.ClientI) (*session.Session, error) {
|
|
|
|
tickCh := time.Tick(500 * time.Millisecond)
|
|
|
|
stopCh := time.After(10 * time.Second)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-tickCh:
|
|
|
|
sessions, err = site.GetSessions(defaults.Namespace)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2018-01-15 21:31:30 +00:00
|
|
|
if len(sessions) != 1 {
|
|
|
|
continue
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
2018-01-15 21:31:30 +00:00
|
|
|
return &sessions[0], nil
|
2017-12-13 02:17:18 +00:00
|
|
|
case <-stopCh:
|
|
|
|
return nil, trace.BadParameter("unable to find sessions after 10s (mode=%v)", tt.inRecordLocation)
|
|
|
|
}
|
|
|
|
}
|
2016-05-06 23:10:50 +00:00
|
|
|
}
|
2017-12-13 02:17:18 +00:00
|
|
|
session, err := getSession(site)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// wait for the user to join this session:
|
|
|
|
for len(session.Parties) == 0 {
|
|
|
|
time.Sleep(time.Millisecond * 5)
|
|
|
|
session, err = site.GetSession(defaults.Namespace, sessions[0].ID)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
|
|
|
// make sure it's us who joined! :)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(session.Parties[0].User, check.Equals, s.me.Username, comment)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
|
|
|
// lets type "echo hi" followed by "enter" and then "exit" + "enter":
|
|
|
|
myTerm.Type("\aecho hi\n\r\aexit\n\r\a")
|
|
|
|
|
|
|
|
// wait for session to end:
|
2018-03-04 02:26:44 +00:00
|
|
|
select {
|
|
|
|
case <-endC:
|
|
|
|
case <-time.After(10 * time.Second):
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Fatalf("%s: Timeout waiting for session to finish.", tt.comment)
|
2018-03-04 02:26:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// wait for the upload of the right session to complete
|
2018-03-29 20:47:53 +00:00
|
|
|
timeoutC := time.After(10 * time.Second)
|
|
|
|
loop:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case event := <-t.UploadEventsC:
|
|
|
|
if event.SessionID != string(session.ID) {
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Skipping mismatching session %v, expecting upload of %v.", event.SessionID, session.ID)
|
2018-03-29 20:47:53 +00:00
|
|
|
continue
|
2018-03-04 02:26:44 +00:00
|
|
|
}
|
2018-03-29 20:47:53 +00:00
|
|
|
break loop
|
|
|
|
case <-timeoutC:
|
2020-12-07 14:35:15 +00:00
|
|
|
dumpGoroutineProfile()
|
|
|
|
c.Fatalf("%s: Timeout waiting for upload of session %v to complete to %v",
|
|
|
|
tt.comment, session.ID, tt.auditSessionsURI)
|
2018-03-04 02:26:44 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-13 02:17:18 +00:00
|
|
|
|
|
|
|
// read back the entire session (we have to try several times until we get back
|
|
|
|
// everything because the session is closing)
|
|
|
|
var sessionStream []byte
|
2018-01-12 22:50:09 +00:00
|
|
|
for i := 0; i < 6; i++ {
|
2017-12-13 02:17:18 +00:00
|
|
|
sessionStream, err = site.GetSessionChunk(defaults.Namespace, session.ID, 0, events.MaxChunkBytes)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2017-12-31 23:31:57 +00:00
|
|
|
if strings.Contains(string(sessionStream), "exit") {
|
|
|
|
break
|
|
|
|
}
|
2017-12-13 02:17:18 +00:00
|
|
|
time.Sleep(time.Millisecond * 250)
|
2018-01-12 22:50:09 +00:00
|
|
|
if i >= 5 {
|
2017-12-13 02:17:18 +00:00
|
|
|
// session stream keeps coming back short
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Fatalf("%s: Stream is not getting data: %q.", tt.comment, string(sessionStream))
|
2016-05-06 06:51:56 +00:00
|
|
|
}
|
|
|
|
}
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// see what we got. It looks different based on bash settings, but here it is
|
|
|
|
// on Ev's machine (hostname is 'edsger'):
|
|
|
|
//
|
|
|
|
// edsger ~: echo hi
|
|
|
|
// hi
|
|
|
|
// edsger ~: exit
|
|
|
|
// logout
|
|
|
|
//
|
2020-12-07 14:35:15 +00:00
|
|
|
sessionComment := check.Commentf("%q", string(sessionStream))
|
|
|
|
c.Assert(strings.Contains(string(sessionStream), "echo hi"), check.Equals, true, sessionComment)
|
|
|
|
c.Assert(strings.Contains(string(sessionStream), "exit"), check.Equals, true, sessionComment)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2018-02-14 22:55:01 +00:00
|
|
|
// Wait until session.start, session.leave, and session.end events have arrived.
|
|
|
|
getSessions := func(site auth.ClientI) ([]events.EventFields, error) {
|
|
|
|
tickCh := time.Tick(500 * time.Millisecond)
|
|
|
|
stopCh := time.After(10 * time.Second)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-tickCh:
|
|
|
|
// Get all session events from the backend.
|
2018-03-04 02:26:44 +00:00
|
|
|
sessionEvents, err := site.GetSessionEvents(defaults.Namespace, session.ID, 0, false)
|
2018-02-14 22:55:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look through all session events for the three wanted.
|
|
|
|
var hasStart bool
|
|
|
|
var hasEnd bool
|
|
|
|
var hasLeave bool
|
|
|
|
for _, se := range sessionEvents {
|
|
|
|
if se.GetType() == events.SessionStartEvent {
|
|
|
|
hasStart = true
|
|
|
|
}
|
|
|
|
if se.GetType() == events.SessionEndEvent {
|
|
|
|
hasEnd = true
|
|
|
|
}
|
|
|
|
if se.GetType() == events.SessionLeaveEvent {
|
|
|
|
hasLeave = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure all three events were found.
|
|
|
|
if hasStart && hasEnd && hasLeave {
|
|
|
|
return sessionEvents, nil
|
|
|
|
}
|
|
|
|
case <-stopCh:
|
|
|
|
return nil, trace.BadParameter("unable to find all session events after 10s (mode=%v)", tt.inRecordLocation)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
history, err := getSessions(site)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2016-05-06 06:51:56 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
getChunk := func(e events.EventFields, maxlen int) string {
|
|
|
|
offset := e.GetInt("offset")
|
|
|
|
length := e.GetInt("bytes")
|
|
|
|
if length == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if length > maxlen {
|
|
|
|
length = maxlen
|
|
|
|
}
|
|
|
|
return string(sessionStream[offset : offset+length])
|
2016-05-06 23:10:50 +00:00
|
|
|
}
|
2016-05-06 06:51:56 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
findByType := func(et string) events.EventFields {
|
|
|
|
for _, e := range history {
|
|
|
|
if e.GetType() == et {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2016-05-06 06:51:56 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// there should alwys be 'session.start' event (and it must be first)
|
|
|
|
first := history[0]
|
|
|
|
start := findByType(events.SessionStartEvent)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(start, check.DeepEquals, first, comment)
|
|
|
|
c.Assert(start.GetInt("bytes"), check.Equals, 0, comment)
|
|
|
|
c.Assert(start.GetString(events.SessionEventID) != "", check.Equals, true, comment)
|
|
|
|
c.Assert(start.GetString(events.TerminalSize) != "", check.Equals, true, comment)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2019-09-05 23:37:42 +00:00
|
|
|
// If session are being recorded at nodes, the SessionServerID should contain
|
|
|
|
// the ID of the node. If sessions are being recorded at the proxy, then
|
|
|
|
// SessionServerID should be that of the proxy.
|
|
|
|
expectedServerID := nodeProcess.Config.HostUUID
|
2020-07-15 00:15:01 +00:00
|
|
|
if services.IsRecordAtProxy(tt.inRecordLocation) {
|
2019-09-05 23:37:42 +00:00
|
|
|
expectedServerID = t.Process.Config.HostUUID
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(start.GetString(events.SessionServerID), check.Equals, expectedServerID, comment)
|
2016-05-04 23:49:59 +00:00
|
|
|
|
2017-12-31 23:31:57 +00:00
|
|
|
// make sure data is recorded properly
|
|
|
|
out := &bytes.Buffer{}
|
2017-12-13 02:17:18 +00:00
|
|
|
for _, e := range history {
|
2017-12-31 23:31:57 +00:00
|
|
|
out.WriteString(getChunk(e, 1000))
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
2017-12-31 23:31:57 +00:00
|
|
|
recorded := replaceNewlines(out.String())
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(recorded, check.Matches, ".*exit.*", comment)
|
|
|
|
c.Assert(recorded, check.Matches, ".*echo hi.*", comment)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
|
|
|
// there should alwys be 'session.end' event
|
|
|
|
end := findByType(events.SessionEndEvent)
|
|
|
|
c.Assert(end, check.NotNil)
|
|
|
|
c.Assert(end.GetInt("bytes"), check.Equals, 0)
|
|
|
|
c.Assert(end.GetString(events.SessionEventID) != "", check.Equals, true)
|
|
|
|
|
|
|
|
// there should alwys be 'session.leave' event
|
|
|
|
leave := findByType(events.SessionLeaveEvent)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(leave, check.NotNil, comment)
|
|
|
|
c.Assert(leave.GetInt("bytes"), check.Equals, 0, comment)
|
|
|
|
c.Assert(leave.GetString(events.SessionEventID) != "", check.Equals, true, comment)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
|
|
|
// all of them should have a proper time:
|
|
|
|
for _, e := range history {
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(e.GetTime("time").IsZero(), check.Equals, false, comment)
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
2016-05-04 23:49:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-31 23:31:57 +00:00
|
|
|
func replaceNewlines(in string) string {
|
|
|
|
return regexp.MustCompile(`\r?\n`).ReplaceAllString(in, `\n`)
|
|
|
|
}
|
|
|
|
|
2017-05-31 18:48:20 +00:00
|
|
|
// TestInteroperability checks if Teleport and OpenSSH behave in the same way
|
|
|
|
// when executing commands.
|
|
|
|
func (s *IntSuite) TestInteroperability(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-05-31 18:48:20 +00:00
|
|
|
tempdir, err := ioutil.TempDir("", "teleport-")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
defer os.RemoveAll(tempdir)
|
|
|
|
tempfile := filepath.Join(tempdir, "file.txt")
|
|
|
|
|
|
|
|
// create new teleport server that will be used by all tests
|
|
|
|
t := s.newTeleport(c, nil, true)
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2017-05-31 18:48:20 +00:00
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
inCommand string
|
|
|
|
inStdin string
|
|
|
|
outContains string
|
|
|
|
outFile bool
|
|
|
|
}{
|
|
|
|
// 0 - echo "1\n2\n" | ssh localhost "cat -"
|
|
|
|
// this command can be used to copy files by piping stdout to stdin over ssh.
|
|
|
|
{
|
2018-07-08 17:52:15 +00:00
|
|
|
inCommand: "cat -",
|
|
|
|
inStdin: "1\n2\n",
|
|
|
|
outContains: "1\n2\n",
|
|
|
|
outFile: false,
|
2017-05-31 18:48:20 +00:00
|
|
|
},
|
|
|
|
// 1 - ssh -tt locahost '/bin/sh -c "mkdir -p /tmp && echo a > /tmp/file.txt"'
|
|
|
|
// programs like ansible execute commands like this
|
|
|
|
{
|
2018-07-08 17:52:15 +00:00
|
|
|
inCommand: fmt.Sprintf(`/bin/sh -c "mkdir -p /tmp && echo a > %v"`, tempfile),
|
|
|
|
inStdin: "",
|
|
|
|
outContains: "a",
|
|
|
|
outFile: true,
|
2017-05-31 18:48:20 +00:00
|
|
|
},
|
|
|
|
// 2 - ssh localhost tty
|
|
|
|
// should print "not a tty"
|
|
|
|
{
|
2018-07-08 17:52:15 +00:00
|
|
|
inCommand: "tty",
|
|
|
|
inStdin: "",
|
|
|
|
outContains: "not a tty",
|
|
|
|
outFile: false,
|
2017-05-31 18:48:20 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tt := range tests {
|
|
|
|
// create new teleport client
|
2017-10-12 17:35:46 +00:00
|
|
|
cl, err := t.NewClient(ClientConfig{Login: s.me.Username, Cluster: Site, Host: Host, Port: t.GetPortSSHInt()})
|
2017-05-31 18:48:20 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// hook up stdin and stdout to a buffer for reading and writing
|
|
|
|
inbuf := bytes.NewReader([]byte(tt.inStdin))
|
2018-07-08 17:52:15 +00:00
|
|
|
outbuf := utils.NewSyncBuffer()
|
2017-05-31 18:48:20 +00:00
|
|
|
cl.Stdin = inbuf
|
|
|
|
cl.Stdout = outbuf
|
|
|
|
cl.Stderr = outbuf
|
|
|
|
|
|
|
|
// run command and wait a maximum of 10 seconds for it to complete
|
Fix remaining gosimple findings
List of fixed items:
```
integration/helpers.go:1279:2 gosimple S1000: should use for range instead of for { select {} }
integration/integration_test.go:144:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:173:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:296:28 gosimple S1019: should use make(chan error) instead
integration/integration_test.go:570:41 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:685:40 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:759:33 gosimple S1019: should use make(chan string) instead
lib/auth/init_test.go:62:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/auth/tls_test.go:1658:22 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/backend/dynamo/dynamodbbk.go:420:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/dynamo/dynamodbbk.go:656:12 gosimple S1039: unnecessary use of fmt.Sprintf
lib/backend/etcdbk/etcd.go:458:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/firestore/firestorebk.go:407:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:317:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:336:6 gosimple S1004: should use !bytes.Equal(value, expected.Value) instead
lib/backend/memory/memory.go:365:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/memory/memory.go:376:5 gosimple S1004: should use !bytes.Equal(existingItem.Value, expected.Value) instead
lib/backend/test/suite.go:327:10 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/client/api.go:1410:9 gosimple S1003: should use strings.ContainsRune(name, ':') instead
lib/client/api.go:2355:32 gosimple S1019: should use make([]ForwardedPort, len(spec)) instead
lib/client/keyagent_test.go:85:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/client/player.go:54:33 gosimple S1019: should use make(chan int) instead
lib/config/configuration.go:1024:52 gosimple S1019: should use make(services.CommandLabels) instead
lib/config/configuration.go:1025:44 gosimple S1019: should use make(map[string]string) instead
lib/config/configuration.go:930:21 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleNode) instead
lib/config/configuration.go:931:22 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleAuthService) instead
lib/config/configuration.go:932:23 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleProxy) instead
lib/service/supervisor.go:387:2 gosimple S1001: should use copy() instead of a loop
lib/tlsca/parsegen.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:167:40 gosimple S1010: should omit second index in slice, s[a:len(s)] is identical to s[a:]
lib/utils/certs.go:204:5 gosimple S1004: should use !bytes.Equal(certificateChain[0].SubjectKeyId, certificateChain[0].AuthorityKeyId) instead
lib/utils/parse/parse.go:116:45 gosimple S1003: should use strings.Contains(variable, "}}") instead
lib/utils/parse/parse.go:116:6 gosimple S1003: should use strings.Contains(variable, "{{") instead
lib/utils/socks/socks.go:192:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/utils/socks/socks.go:199:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/web/apiserver.go:1054:18 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/web/apiserver.go:1954:9 gosimple S1039: unnecessary use of fmt.Sprintf
tool/tsh/tsh.go:1193:14 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
```
2020-05-20 18:38:32 +00:00
|
|
|
sessionEndC := make(chan interface{})
|
2017-05-31 18:48:20 +00:00
|
|
|
go func() {
|
|
|
|
// don't check for err, because sometimes this process should fail
|
|
|
|
// with an error and that's what the test is checking for.
|
|
|
|
cl.SSH(context.TODO(), []string{tt.inCommand}, false)
|
|
|
|
sessionEndC <- true
|
|
|
|
}()
|
2020-06-02 00:06:46 +00:00
|
|
|
err = waitFor(sessionEndC, time.Second*10)
|
|
|
|
c.Assert(err, check.IsNil)
|
2017-05-31 18:48:20 +00:00
|
|
|
|
|
|
|
// if we are looking for the output in a file, look in the file
|
|
|
|
// otherwise check stdout and stderr for the expected output
|
|
|
|
if tt.outFile {
|
|
|
|
bytes, err := ioutil.ReadFile(tempfile)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
comment := check.Commentf("Test %v: %q does not contain: %q", i, string(bytes), tt.outContains)
|
|
|
|
c.Assert(strings.Contains(string(bytes), tt.outContains), check.Equals, true, comment)
|
|
|
|
} else {
|
|
|
|
comment := check.Commentf("Test %v: %q does not contain: %q", i, outbuf.String(), tt.outContains)
|
|
|
|
c.Assert(strings.Contains(outbuf.String(), tt.outContains), check.Equals, true, comment)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-03 23:27:49 +00:00
|
|
|
// TestUUIDBasedProxy verifies that attempts to proxy to nodes using ambiguous
|
|
|
|
// hostnames fails with the correct error, and that proxying by UUID succeeds.
|
|
|
|
func (s *IntSuite) TestUUIDBasedProxy(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2020-03-03 23:27:49 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
t := s.newTeleport(c, nil, true)
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2020-03-03 23:27:49 +00:00
|
|
|
|
|
|
|
site := t.GetSiteAPI(Site)
|
|
|
|
|
|
|
|
// addNode adds a node to the teleport instance, returning its uuid.
|
|
|
|
// All nodes added this way have the same hostname.
|
|
|
|
addNode := func() (string, error) {
|
|
|
|
nodeSSHPort := s.getPorts(1)[0]
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2020-03-03 23:27:49 +00:00
|
|
|
tconf.Hostname = Host
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.SSH.Addr.Addr = net.JoinHostPort(t.Hostname, fmt.Sprintf("%v", nodeSSHPort))
|
|
|
|
|
|
|
|
node, err := t.StartNode(tconf)
|
|
|
|
if err != nil {
|
|
|
|
return "", trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ident, err := node.GetIdentity(teleport.RoleNode)
|
|
|
|
if err != nil {
|
|
|
|
return "", trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ident.ID.HostID()
|
|
|
|
}
|
|
|
|
|
|
|
|
// add two nodes with the same hostname.
|
|
|
|
uuid1, err := addNode()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
uuid2, err := addNode()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait up to 10 seconds for supplied node names to show up.
|
|
|
|
waitForNodes := func(site auth.ClientI, nodes ...string) error {
|
|
|
|
tickCh := time.Tick(500 * time.Millisecond)
|
|
|
|
stopCh := time.After(10 * time.Second)
|
|
|
|
Outer:
|
|
|
|
for _, nodeName := range nodes {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-tickCh:
|
|
|
|
nodesInSite, err := site.GetNodes(defaults.Namespace, services.SkipValidation())
|
|
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
for _, node := range nodesInSite {
|
|
|
|
if node.GetName() == nodeName {
|
|
|
|
continue Outer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case <-stopCh:
|
|
|
|
return trace.BadParameter("waited 10s, did find node %s", nodeName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err = waitForNodes(site, uuid1, uuid2)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// attempting to run a command by hostname should generate NodeIsAmbiguous error.
|
|
|
|
_, err = runCommand(t, []string{"echo", "Hello there!"}, ClientConfig{Login: s.me.Username, Cluster: Site, Host: Host}, 1)
|
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
if !strings.Contains(err.Error(), teleport.NodeIsAmbiguous) {
|
|
|
|
c.Errorf("Expected %s, got %s", teleport.NodeIsAmbiguous, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// attempting to run a command by uuid should succeed.
|
|
|
|
_, err = runCommand(t, []string{"echo", "Hello there!"}, ClientConfig{Login: s.me.Username, Cluster: Site, Host: uuid1}, 1)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
|
2016-04-14 20:30:13 +00:00
|
|
|
// TestInteractive covers SSH into shell and joining the same session from another client
|
2020-06-26 20:58:40 +00:00
|
|
|
// against a standard teleport node.
|
|
|
|
func (s *IntSuite) TestInteractiveRegular(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2016-05-04 23:49:59 +00:00
|
|
|
t := s.newTeleport(c, nil, true)
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2016-04-13 01:44:09 +00:00
|
|
|
|
2020-06-26 20:58:40 +00:00
|
|
|
s.verifySessionJoin(c, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestInteractive covers SSH into shell and joining the same session from another client
|
|
|
|
// against a reversetunnel node.
|
|
|
|
func (s *IntSuite) TestInteractiveReverseTunnel(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2020-06-26 20:58:40 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
// InsecureDevMode needed for IoT node handshake
|
|
|
|
lib.SetInsecureDevMode(true)
|
|
|
|
defer lib.SetInsecureDevMode(false)
|
|
|
|
|
|
|
|
t := s.newTeleportIoT(c, nil)
|
|
|
|
defer t.StopAll()
|
|
|
|
|
|
|
|
s.verifySessionJoin(c, t)
|
|
|
|
}
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// verifySessionJoin covers SSH into shell and joining the same session from another client
|
2020-06-26 20:58:40 +00:00
|
|
|
func (s *IntSuite) verifySessionJoin(c *check.C, t *TeleInstance) {
|
Fix remaining gosimple findings
List of fixed items:
```
integration/helpers.go:1279:2 gosimple S1000: should use for range instead of for { select {} }
integration/integration_test.go:144:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:173:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:296:28 gosimple S1019: should use make(chan error) instead
integration/integration_test.go:570:41 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:685:40 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:759:33 gosimple S1019: should use make(chan string) instead
lib/auth/init_test.go:62:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/auth/tls_test.go:1658:22 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/backend/dynamo/dynamodbbk.go:420:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/dynamo/dynamodbbk.go:656:12 gosimple S1039: unnecessary use of fmt.Sprintf
lib/backend/etcdbk/etcd.go:458:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/firestore/firestorebk.go:407:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:317:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:336:6 gosimple S1004: should use !bytes.Equal(value, expected.Value) instead
lib/backend/memory/memory.go:365:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/memory/memory.go:376:5 gosimple S1004: should use !bytes.Equal(existingItem.Value, expected.Value) instead
lib/backend/test/suite.go:327:10 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/client/api.go:1410:9 gosimple S1003: should use strings.ContainsRune(name, ':') instead
lib/client/api.go:2355:32 gosimple S1019: should use make([]ForwardedPort, len(spec)) instead
lib/client/keyagent_test.go:85:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/client/player.go:54:33 gosimple S1019: should use make(chan int) instead
lib/config/configuration.go:1024:52 gosimple S1019: should use make(services.CommandLabels) instead
lib/config/configuration.go:1025:44 gosimple S1019: should use make(map[string]string) instead
lib/config/configuration.go:930:21 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleNode) instead
lib/config/configuration.go:931:22 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleAuthService) instead
lib/config/configuration.go:932:23 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleProxy) instead
lib/service/supervisor.go:387:2 gosimple S1001: should use copy() instead of a loop
lib/tlsca/parsegen.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:167:40 gosimple S1010: should omit second index in slice, s[a:len(s)] is identical to s[a:]
lib/utils/certs.go:204:5 gosimple S1004: should use !bytes.Equal(certificateChain[0].SubjectKeyId, certificateChain[0].AuthorityKeyId) instead
lib/utils/parse/parse.go:116:45 gosimple S1003: should use strings.Contains(variable, "}}") instead
lib/utils/parse/parse.go:116:6 gosimple S1003: should use strings.Contains(variable, "{{") instead
lib/utils/socks/socks.go:192:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/utils/socks/socks.go:199:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/web/apiserver.go:1054:18 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/web/apiserver.go:1954:9 gosimple S1039: unnecessary use of fmt.Sprintf
tool/tsh/tsh.go:1193:14 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
```
2020-05-20 18:38:32 +00:00
|
|
|
sessionEndC := make(chan interface{})
|
2016-04-14 20:30:13 +00:00
|
|
|
|
|
|
|
// get a reference to site obj:
|
2016-05-04 23:49:59 +00:00
|
|
|
site := t.GetSiteAPI(Site)
|
2016-04-14 20:30:13 +00:00
|
|
|
c.Assert(site, check.NotNil)
|
|
|
|
|
|
|
|
personA := NewTerminal(250)
|
|
|
|
personB := NewTerminal(250)
|
|
|
|
|
2016-05-04 23:49:59 +00:00
|
|
|
// PersonA: SSH into the server, wait one second, then type some commands on stdin:
|
|
|
|
openSession := func() {
|
2020-06-26 20:58:40 +00:00
|
|
|
cl, err := t.NewClient(ClientConfig{Login: s.me.Username, Cluster: Site, Host: Host})
|
2016-05-04 23:49:59 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-04-17 15:55:48 +00:00
|
|
|
cl.Stdout = personA
|
|
|
|
cl.Stdin = personA
|
2016-05-04 23:49:59 +00:00
|
|
|
// Person A types something into the terminal (including "exit")
|
|
|
|
personA.Type("\aecho hi\n\r\aexit\n\r\a")
|
2016-12-23 19:50:32 +00:00
|
|
|
err = cl.SSH(context.TODO(), []string{}, false)
|
2016-05-04 23:49:59 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
sessionEndC <- true
|
2018-02-08 02:32:50 +00:00
|
|
|
|
2016-05-04 23:49:59 +00:00
|
|
|
}
|
|
|
|
|
2016-04-14 20:30:13 +00:00
|
|
|
// PersonB: wait for a session to become available, then join:
|
|
|
|
joinSession := func() {
|
|
|
|
var sessionID string
|
|
|
|
for {
|
2016-04-27 05:55:06 +00:00
|
|
|
time.Sleep(time.Millisecond)
|
2016-12-19 00:58:53 +00:00
|
|
|
sessions, _ := site.GetSessions(defaults.Namespace)
|
2016-04-14 20:30:13 +00:00
|
|
|
if len(sessions) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
sessionID = string(sessions[0].ID)
|
|
|
|
break
|
|
|
|
}
|
2020-06-26 20:58:40 +00:00
|
|
|
cl, err := t.NewClient(ClientConfig{Login: s.me.Username, Cluster: Site, Host: Host})
|
2016-04-14 20:30:13 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-04-17 15:55:48 +00:00
|
|
|
cl.Stdout = personB
|
2016-04-14 21:42:10 +00:00
|
|
|
for i := 0; i < 10; i++ {
|
2020-04-17 15:55:48 +00:00
|
|
|
err = cl.Join(context.TODO(), defaults.Namespace, session.ID(sessionID), personB)
|
2016-04-14 20:30:13 +00:00
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
|
|
|
|
go openSession()
|
|
|
|
go joinSession()
|
|
|
|
|
|
|
|
// wait for the session to end
|
2020-06-02 00:06:46 +00:00
|
|
|
err := waitFor(sessionEndC, time.Second*10)
|
|
|
|
c.Assert(err, check.IsNil)
|
2016-04-14 20:30:13 +00:00
|
|
|
|
2016-09-10 04:44:04 +00:00
|
|
|
// make sure the output of B is mirrored in A
|
2020-05-06 17:13:05 +00:00
|
|
|
outputOfA := personA.Output(100)
|
|
|
|
outputOfB := personB.Output(100)
|
2016-09-10 04:44:04 +00:00
|
|
|
c.Assert(strings.Contains(outputOfA, outputOfB), check.Equals, true)
|
2016-04-13 01:44:09 +00:00
|
|
|
}
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
// TestShutdown tests scenario with a graceful shutdown,
|
|
|
|
// that session will be working after
|
|
|
|
func (s *IntSuite) TestShutdown(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2018-02-08 02:32:50 +00:00
|
|
|
t := s.newTeleport(c, nil, true)
|
|
|
|
|
|
|
|
// get a reference to site obj:
|
|
|
|
site := t.GetSiteAPI(Site)
|
|
|
|
c.Assert(site, check.NotNil)
|
|
|
|
|
|
|
|
person := NewTerminal(250)
|
|
|
|
|
|
|
|
// commandsC receive commands
|
Fix remaining gosimple findings
List of fixed items:
```
integration/helpers.go:1279:2 gosimple S1000: should use for range instead of for { select {} }
integration/integration_test.go:144:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:173:5 gosimple S1009: should omit nil check; len() for nil slices is defined as zero
integration/integration_test.go:296:28 gosimple S1019: should use make(chan error) instead
integration/integration_test.go:570:41 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:685:40 gosimple S1019: should use make(chan interface{}) instead
integration/integration_test.go:759:33 gosimple S1019: should use make(chan string) instead
lib/auth/init_test.go:62:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/auth/tls_test.go:1658:22 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/backend/dynamo/dynamodbbk.go:420:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/dynamo/dynamodbbk.go:656:12 gosimple S1039: unnecessary use of fmt.Sprintf
lib/backend/etcdbk/etcd.go:458:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/firestore/firestorebk.go:407:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:317:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/lite/lite.go:336:6 gosimple S1004: should use !bytes.Equal(value, expected.Value) instead
lib/backend/memory/memory.go:365:5 gosimple S1004: should use !bytes.Equal(expected.Key, replaceWith.Key) instead
lib/backend/memory/memory.go:376:5 gosimple S1004: should use !bytes.Equal(existingItem.Value, expected.Value) instead
lib/backend/test/suite.go:327:10 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/client/api.go:1410:9 gosimple S1003: should use strings.ContainsRune(name, ':') instead
lib/client/api.go:2355:32 gosimple S1019: should use make([]ForwardedPort, len(spec)) instead
lib/client/keyagent_test.go:85:2 gosimple S1021: should merge variable declaration with assignment on next line
lib/client/player.go:54:33 gosimple S1019: should use make(chan int) instead
lib/config/configuration.go:1024:52 gosimple S1019: should use make(services.CommandLabels) instead
lib/config/configuration.go:1025:44 gosimple S1019: should use make(map[string]string) instead
lib/config/configuration.go:930:21 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleNode) instead
lib/config/configuration.go:931:22 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleAuthService) instead
lib/config/configuration.go:932:23 gosimple S1003: should use strings.Contains(clf.Roles, defaults.RoleProxy) instead
lib/service/supervisor.go:387:2 gosimple S1001: should use copy() instead of a loop
lib/tlsca/parsegen.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:140:9 gosimple S1034: assigning the result of this type assertion to a variable (switch generalKey := generalKey.(type)) could eliminate type assertions in switch cases
lib/utils/certs.go:167:40 gosimple S1010: should omit second index in slice, s[a:len(s)] is identical to s[a:]
lib/utils/certs.go:204:5 gosimple S1004: should use !bytes.Equal(certificateChain[0].SubjectKeyId, certificateChain[0].AuthorityKeyId) instead
lib/utils/parse/parse.go:116:45 gosimple S1003: should use strings.Contains(variable, "}}") instead
lib/utils/parse/parse.go:116:6 gosimple S1003: should use strings.Contains(variable, "{{") instead
lib/utils/socks/socks.go:192:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/utils/socks/socks.go:199:10 gosimple S1025: should use String() instead of fmt.Sprintf
lib/web/apiserver.go:1054:18 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
lib/web/apiserver.go:1954:9 gosimple S1039: unnecessary use of fmt.Sprintf
tool/tsh/tsh.go:1193:14 gosimple S1024: should use time.Until instead of t.Sub(time.Now())
```
2020-05-20 18:38:32 +00:00
|
|
|
commandsC := make(chan string)
|
2018-02-08 02:32:50 +00:00
|
|
|
|
|
|
|
// PersonA: SSH into the server, wait one second, then type some commands on stdin:
|
|
|
|
openSession := func() {
|
|
|
|
cl, err := t.NewClient(ClientConfig{Login: s.me.Username, Cluster: Site, Host: Host, Port: t.GetPortSSHInt()})
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-04-17 15:55:48 +00:00
|
|
|
cl.Stdout = person
|
|
|
|
cl.Stdin = person
|
2018-02-08 02:32:50 +00:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
for command := range commandsC {
|
|
|
|
person.Type(command)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
err = cl.SSH(context.TODO(), []string{}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
|
|
|
|
go openSession()
|
|
|
|
|
|
|
|
retry := func(command, pattern string) {
|
|
|
|
person.Type(command)
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime := time.Now().Add(10 * time.Second)
|
|
|
|
var matched bool
|
|
|
|
var output string
|
|
|
|
for {
|
2020-05-06 17:13:05 +00:00
|
|
|
output = replaceNewlines(person.Output(1000))
|
2018-02-08 02:32:50 +00:00
|
|
|
matched, _ = regexp.MatchString(pattern, output)
|
|
|
|
if matched {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(time.Millisecond * 200)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("failed to capture output: %v", pattern)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !matched {
|
|
|
|
c.Fatalf("output %q does not match pattern %q", output, pattern)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
retry("echo start \r\n", ".*start.*")
|
|
|
|
|
|
|
|
// initiate shutdown
|
|
|
|
ctx := context.TODO()
|
|
|
|
shutdownContext := t.Process.StartShutdown(ctx)
|
|
|
|
|
|
|
|
// make sure that terminal still works
|
|
|
|
retry("echo howdy \r\n", ".*howdy.*")
|
|
|
|
|
|
|
|
// now type exit and wait for shutdown to complete
|
|
|
|
person.Type("exit\n\r")
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-shutdownContext.Done():
|
|
|
|
case <-time.After(5 * time.Second):
|
2018-12-12 00:22:44 +00:00
|
|
|
c.Fatalf("Failed to shut down the server.")
|
2018-02-08 02:32:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-08 17:52:15 +00:00
|
|
|
type disconnectTestCase struct {
|
|
|
|
recordingMode string
|
|
|
|
options services.RoleOptions
|
|
|
|
disconnectTimeout time.Duration
|
2020-08-25 19:02:16 +00:00
|
|
|
concurrentConns int
|
|
|
|
sessCtlTimeout time.Duration
|
|
|
|
assertExpected func(*check.C, error)
|
|
|
|
postFunc func(context.Context, *check.C, *TeleInstance)
|
2020-12-07 14:35:15 +00:00
|
|
|
comment string
|
2018-07-08 17:52:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestDisconnectScenarios tests multiple scenarios with client disconnects
|
|
|
|
func (s *IntSuite) TestDisconnectScenarios(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2018-07-08 17:52:15 +00:00
|
|
|
testCases := []disconnectTestCase{
|
|
|
|
{
|
|
|
|
recordingMode: services.RecordAtNode,
|
|
|
|
options: services.RoleOptions{
|
|
|
|
ClientIdleTimeout: services.NewDuration(500 * time.Millisecond),
|
|
|
|
},
|
|
|
|
disconnectTimeout: time.Second,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
recordingMode: services.RecordAtProxy,
|
|
|
|
options: services.RoleOptions{
|
2020-04-23 22:44:09 +00:00
|
|
|
ForwardAgent: services.NewBool(true),
|
2018-07-08 17:52:15 +00:00
|
|
|
ClientIdleTimeout: services.NewDuration(500 * time.Millisecond),
|
|
|
|
},
|
|
|
|
disconnectTimeout: time.Second,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
recordingMode: services.RecordAtNode,
|
|
|
|
options: services.RoleOptions{
|
|
|
|
DisconnectExpiredCert: services.NewBool(true),
|
|
|
|
MaxSessionTTL: services.NewDuration(2 * time.Second),
|
|
|
|
},
|
|
|
|
disconnectTimeout: 4 * time.Second,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
recordingMode: services.RecordAtProxy,
|
|
|
|
options: services.RoleOptions{
|
2020-04-23 22:44:09 +00:00
|
|
|
ForwardAgent: services.NewBool(true),
|
2018-07-08 17:52:15 +00:00
|
|
|
DisconnectExpiredCert: services.NewBool(true),
|
|
|
|
MaxSessionTTL: services.NewDuration(2 * time.Second),
|
|
|
|
},
|
|
|
|
disconnectTimeout: 4 * time.Second,
|
|
|
|
},
|
2020-12-07 14:35:15 +00:00
|
|
|
{
|
|
|
|
comment: "verify that concurrent connection limits are applied when recording at node",
|
2020-08-25 19:02:16 +00:00
|
|
|
recordingMode: services.RecordAtNode,
|
|
|
|
options: services.RoleOptions{
|
|
|
|
MaxConnections: 1,
|
|
|
|
},
|
|
|
|
disconnectTimeout: 1 * time.Second,
|
|
|
|
concurrentConns: 2,
|
|
|
|
assertExpected: func(c *check.C, err error) {
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "administratively prohibited") {
|
|
|
|
c.Fatalf("Expected 'administratively prohibited', got: %v", err)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2020-12-07 14:35:15 +00:00
|
|
|
{
|
|
|
|
comment: "verify that concurrent connection limits are applied when recording at proxy",
|
2020-08-25 19:02:16 +00:00
|
|
|
recordingMode: services.RecordAtProxy,
|
|
|
|
options: services.RoleOptions{
|
|
|
|
ForwardAgent: services.NewBool(true),
|
|
|
|
MaxConnections: 1,
|
|
|
|
},
|
|
|
|
disconnectTimeout: 1 * time.Second,
|
|
|
|
concurrentConns: 2,
|
|
|
|
assertExpected: func(c *check.C, err error) {
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "administratively prohibited") {
|
|
|
|
c.Fatalf("Expected 'administratively prohibited', got: %v", err)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2020-12-07 14:35:15 +00:00
|
|
|
{
|
|
|
|
comment: "verify that lost connections to auth server terminate controlled conns",
|
2020-08-25 19:02:16 +00:00
|
|
|
recordingMode: services.RecordAtNode,
|
|
|
|
options: services.RoleOptions{
|
|
|
|
MaxConnections: 1,
|
|
|
|
},
|
|
|
|
disconnectTimeout: time.Second,
|
|
|
|
sessCtlTimeout: 500 * time.Millisecond,
|
|
|
|
// use postFunc to wait for the semaphore to be acquired and a session
|
|
|
|
// to be started, then shut down the auth server.
|
|
|
|
postFunc: func(ctx context.Context, c *check.C, t *TeleInstance) {
|
|
|
|
site := t.GetSiteAPI(Site)
|
|
|
|
var sems []services.Semaphore
|
|
|
|
var err error
|
|
|
|
for i := 0; i < 6; i++ {
|
|
|
|
sems, err = site.GetSemaphores(ctx, services.SemaphoreFilter{
|
|
|
|
SemaphoreKind: services.SemaphoreKindConnection,
|
|
|
|
})
|
|
|
|
if err == nil && len(sems) > 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-time.After(time.Millisecond * 100):
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(len(sems), check.Equals, 1)
|
|
|
|
var ss []session.Session
|
|
|
|
for i := 0; i < 6; i++ {
|
|
|
|
ss, err = site.GetSessions(defaults.Namespace)
|
|
|
|
if err == nil && len(ss) > 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-time.After(time.Millisecond * 100):
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(len(ss), check.Equals, 1)
|
|
|
|
c.Assert(t.StopAuth(false), check.IsNil)
|
|
|
|
},
|
|
|
|
},
|
2018-07-08 17:52:15 +00:00
|
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
|
|
s.runDisconnectTest(c, tc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IntSuite) runDisconnectTest(c *check.C, tc disconnectTestCase) {
|
2020-12-07 14:35:15 +00:00
|
|
|
t := s.newTeleportInstance(c)
|
|
|
|
comment := check.Commentf(tc.comment)
|
2018-07-08 17:52:15 +00:00
|
|
|
|
|
|
|
username := s.me.Username
|
|
|
|
role, err := services.NewRole("devs", services.RoleSpecV3{
|
|
|
|
Options: tc.options,
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{username},
|
|
|
|
},
|
|
|
|
})
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2018-07-08 17:52:15 +00:00
|
|
|
t.AddUserWithRole(username, role)
|
|
|
|
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
2020-08-25 19:02:16 +00:00
|
|
|
SessionRecording: tc.recordingMode,
|
|
|
|
LocalAuth: services.NewBool(true),
|
|
|
|
SessionControlTimeout: services.Duration(tc.sessCtlTimeout),
|
2018-07-08 17:52:15 +00:00
|
|
|
})
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2018-07-08 17:52:15 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
cfg := s.defaultServiceConfig()
|
2018-07-08 17:52:15 +00:00
|
|
|
cfg.Auth.Enabled = true
|
|
|
|
cfg.Auth.ClusterConfig = clusterConfig
|
|
|
|
cfg.Proxy.DisableWebService = true
|
|
|
|
cfg.Proxy.DisableWebInterface = true
|
|
|
|
cfg.Proxy.Enabled = true
|
|
|
|
cfg.SSH.Enabled = true
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(t.CreateEx(nil, cfg), check.IsNil, comment)
|
|
|
|
c.Assert(t.Start(), check.IsNil, comment)
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2018-07-08 17:52:15 +00:00
|
|
|
|
|
|
|
// get a reference to site obj:
|
|
|
|
site := t.GetSiteAPI(Site)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(site, check.NotNil, comment)
|
2018-07-08 17:52:15 +00:00
|
|
|
|
2020-08-25 19:02:16 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.TODO())
|
|
|
|
defer cancel()
|
2018-07-08 17:52:15 +00:00
|
|
|
|
2020-08-25 19:02:16 +00:00
|
|
|
if tc.concurrentConns < 1 {
|
|
|
|
// test cases that don't specify concurrentConns are single-connection tests.
|
|
|
|
tc.concurrentConns = 1
|
|
|
|
}
|
2018-07-08 17:52:15 +00:00
|
|
|
|
2020-08-25 19:02:16 +00:00
|
|
|
for i := 0; i < tc.concurrentConns; i++ {
|
|
|
|
person := NewTerminal(250)
|
|
|
|
|
|
|
|
openSession := func() {
|
|
|
|
defer cancel()
|
|
|
|
cl, err := t.NewClient(ClientConfig{Login: username, Cluster: Site, Host: Host, Port: t.GetPortSSHInt()})
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(err, check.IsNil, comment)
|
2020-08-25 19:02:16 +00:00
|
|
|
cl.Stdout = person
|
|
|
|
cl.Stdin = person
|
|
|
|
|
|
|
|
err = cl.SSH(ctx, []string{}, false)
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
// either we timed out, or a different session
|
|
|
|
// triggered closure.
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
if tc.assertExpected != nil {
|
|
|
|
tc.assertExpected(c, err)
|
|
|
|
} else if err != nil && !trace.IsEOF(err) {
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Fatalf("%s: expected EOF or nil, got %v instead", tc.comment, err)
|
2020-08-25 19:02:16 +00:00
|
|
|
}
|
2018-07-08 17:52:15 +00:00
|
|
|
}
|
2020-08-25 19:02:16 +00:00
|
|
|
|
|
|
|
go openSession()
|
|
|
|
|
|
|
|
go enterInput(ctx, c, person, "echo start \r\n", ".*start.*")
|
2018-07-08 17:52:15 +00:00
|
|
|
}
|
|
|
|
|
2020-08-25 19:02:16 +00:00
|
|
|
if tc.postFunc != nil {
|
|
|
|
// test case modifies the teleport instance after session start
|
|
|
|
tc.postFunc(ctx, c, t)
|
|
|
|
}
|
2018-07-08 17:52:15 +00:00
|
|
|
|
|
|
|
select {
|
2020-08-25 19:02:16 +00:00
|
|
|
case <-time.After(tc.disconnectTimeout + time.Second):
|
2020-12-07 14:35:15 +00:00
|
|
|
dumpGoroutineProfile()
|
|
|
|
c.Fatalf("%s: timeout waiting for session to exit: %+v", timeNow(), tc)
|
2020-08-25 19:02:16 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
// session closed. a test case is successful if the first
|
|
|
|
// session to close encountered the expected error variant.
|
2018-07-08 17:52:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
func timeNow() string {
|
|
|
|
return time.Now().Format(time.StampMilli)
|
|
|
|
}
|
|
|
|
|
2020-08-25 19:02:16 +00:00
|
|
|
func enterInput(ctx context.Context, c *check.C, person *Terminal, command, pattern string) {
|
2019-03-24 20:14:34 +00:00
|
|
|
person.Type(command)
|
|
|
|
abortTime := time.Now().Add(10 * time.Second)
|
|
|
|
var matched bool
|
|
|
|
var output string
|
|
|
|
for {
|
2020-05-06 17:13:05 +00:00
|
|
|
output = replaceNewlines(person.Output(1000))
|
2019-03-24 20:14:34 +00:00
|
|
|
matched, _ = regexp.MatchString(pattern, output)
|
|
|
|
if matched {
|
2020-08-25 19:02:16 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-time.After(time.Millisecond * 50):
|
|
|
|
case <-ctx.Done():
|
|
|
|
// cancellation means that we don't care about the input being
|
|
|
|
// confirmed anymore; not equivalent to a timeout.
|
|
|
|
return
|
2019-03-24 20:14:34 +00:00
|
|
|
}
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("failed to capture pattern %q in %q", pattern, output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-10 16:38:19 +00:00
|
|
|
// TestInvalidLogins validates that you can't login with invalid login or
|
|
|
|
// with invalid 'site' parameter
|
|
|
|
func (s *IntSuite) TestEnvironmentVariables(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2016-06-10 16:38:19 +00:00
|
|
|
t := s.newTeleport(c, nil, true)
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2016-06-10 16:38:19 +00:00
|
|
|
|
|
|
|
testKey, testVal := "TELEPORT_TEST_ENV", "howdy"
|
2017-02-24 01:05:51 +00:00
|
|
|
cmd := []string{"printenv", testKey}
|
2016-06-10 16:38:19 +00:00
|
|
|
|
|
|
|
// make sure sessions set run command
|
2017-10-12 17:35:46 +00:00
|
|
|
tc, err := t.NewClient(ClientConfig{Login: s.me.Username, Cluster: Site, Host: Host, Port: t.GetPortSSHInt()})
|
2016-09-10 04:44:04 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2016-06-10 16:38:19 +00:00
|
|
|
tc.Env = map[string]string{testKey: testVal}
|
|
|
|
out := &bytes.Buffer{}
|
|
|
|
tc.Stdout = out
|
2016-09-10 04:44:04 +00:00
|
|
|
tc.Stdin = nil
|
2016-12-23 19:50:32 +00:00
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
2016-09-10 04:44:04 +00:00
|
|
|
|
2016-06-10 16:38:19 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(strings.TrimSpace(out.String()), check.Equals, testVal)
|
|
|
|
}
|
|
|
|
|
2016-04-13 01:44:09 +00:00
|
|
|
// TestInvalidLogins validates that you can't login with invalid login or
|
|
|
|
// with invalid 'site' parameter
|
2016-04-14 18:43:08 +00:00
|
|
|
func (s *IntSuite) TestInvalidLogins(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2016-05-04 23:49:59 +00:00
|
|
|
t := s.newTeleport(c, nil, true)
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2016-04-13 01:44:09 +00:00
|
|
|
|
|
|
|
cmd := []string{"echo", "success"}
|
|
|
|
|
|
|
|
// try the wrong site:
|
2017-10-12 17:35:46 +00:00
|
|
|
tc, err := t.NewClient(ClientConfig{Login: s.me.Username, Cluster: "wrong-site", Host: Host, Port: t.GetPortSSHInt()})
|
2016-04-14 18:43:08 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2016-12-23 19:50:32 +00:00
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
2016-12-19 00:58:53 +00:00
|
|
|
c.Assert(err, check.ErrorMatches, "cluster wrong-site not found")
|
2016-04-13 01:44:09 +00:00
|
|
|
}
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
// TestTwoClustersTunnel creates two teleport clusters: "a" and "b" and creates a
|
2017-12-13 02:17:18 +00:00
|
|
|
// tunnel from A to B.
|
|
|
|
//
|
|
|
|
// Two tests are run, first is when both A and B record sessions at nodes. It
|
|
|
|
// executes an SSH command on A by connecting directly to A and by connecting
|
|
|
|
// to B via B<->A tunnel. All sessions should end up in A.
|
2016-04-08 17:38:19 +00:00
|
|
|
//
|
2017-12-13 02:17:18 +00:00
|
|
|
// In the second test, sessions are recorded at B. All sessions still show up on
|
|
|
|
// A (they are Teleport nodes) but in addition, two show up on B when connecting
|
|
|
|
// over the B<->A tunnel because sessions are recorded at the proxy.
|
2018-12-12 00:22:44 +00:00
|
|
|
func (s *IntSuite) TestTwoClustersTunnel(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
now := time.Now().In(time.UTC).Round(time.Second)
|
2017-05-31 18:48:20 +00:00
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
var tests = []struct {
|
|
|
|
inRecordLocation string
|
|
|
|
outExecCountSiteA int
|
|
|
|
outExecCountSiteB int
|
|
|
|
}{
|
|
|
|
// normal teleport. since all events are recorded at the node, all events
|
|
|
|
// end up on site-a and none on site-b.
|
|
|
|
{
|
|
|
|
services.RecordAtNode,
|
|
|
|
3,
|
|
|
|
0,
|
|
|
|
},
|
|
|
|
// recording proxy. since events are recorded at the proxy, 3 events end up
|
|
|
|
// on site-a (because it's a teleport node so it still records at the node)
|
|
|
|
// and 2 events end up on site-b because it's recording.
|
|
|
|
{
|
|
|
|
services.RecordAtProxy,
|
|
|
|
3,
|
|
|
|
2,
|
|
|
|
},
|
2017-05-31 18:48:20 +00:00
|
|
|
}
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
for _, tt := range tests {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.twoClustersTunnel(c, now, tt.inRecordLocation, tt.outExecCountSiteA, tt.outExecCountSiteB)
|
|
|
|
}
|
|
|
|
}
|
2016-04-11 23:30:58 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
func (s *IntSuite) twoClustersTunnel(c *check.C, now time.Time, proxyRecordMode string, execCountSiteA, execCountSiteB int) {
|
|
|
|
// start the http proxy, we need to make sure this was not used
|
|
|
|
ps := &proxyServer{}
|
|
|
|
ts := httptest.NewServer(ps)
|
|
|
|
defer ts.Close()
|
2016-04-08 02:01:31 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// clear out any proxy environment variables
|
|
|
|
for _, v := range []string{"http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY"} {
|
|
|
|
os.Setenv(v, "")
|
|
|
|
}
|
2016-04-08 01:13:13 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
username := s.me.Username
|
2016-04-08 01:13:13 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
a := s.newNamedTeleportInstance(c, "site-A")
|
|
|
|
b := s.newNamedTeleportInstance(c, "site-B")
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
a.AddUser(username, []string{username})
|
|
|
|
b.AddUser(username, []string{username})
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: proxyRecordMode,
|
|
|
|
LocalAuth: services.NewBool(true),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
acfg := s.defaultServiceConfig()
|
|
|
|
acfg.Auth.Enabled = true
|
|
|
|
acfg.Proxy.Enabled = true
|
|
|
|
acfg.Proxy.DisableWebService = true
|
|
|
|
acfg.Proxy.DisableWebInterface = true
|
|
|
|
acfg.SSH.Enabled = true
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
bcfg := s.defaultServiceConfig()
|
|
|
|
bcfg.Auth.Enabled = true
|
|
|
|
bcfg.Auth.ClusterConfig = clusterConfig
|
|
|
|
bcfg.Proxy.Enabled = true
|
|
|
|
bcfg.Proxy.DisableWebService = true
|
|
|
|
bcfg.Proxy.DisableWebInterface = true
|
|
|
|
bcfg.SSH.Enabled = false
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(b.CreateEx(a.Secrets.AsSlice(), bcfg), check.IsNil)
|
|
|
|
defer b.StopAll()
|
|
|
|
c.Assert(a.CreateEx(b.Secrets.AsSlice(), acfg), check.IsNil)
|
|
|
|
defer a.StopAll()
|
|
|
|
|
|
|
|
c.Assert(b.Start(), check.IsNil)
|
|
|
|
c.Assert(a.Start(), check.IsNil)
|
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime := time.Now().Add(time.Second * 10)
|
|
|
|
for len(checkGetClusters(c, a.Tunnel)) < 2 && len(checkGetClusters(c, b.Tunnel)) < 2 {
|
|
|
|
time.Sleep(time.Millisecond * 200)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("Two clusters do not see each other: tunnels are not working.")
|
2016-04-11 23:30:58 +00:00
|
|
|
}
|
2020-12-07 14:35:15 +00:00
|
|
|
}
|
2016-04-11 23:30:58 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
var (
|
|
|
|
outputA bytes.Buffer
|
|
|
|
outputB bytes.Buffer
|
|
|
|
)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// make sure the direct dialer was used and not the proxy dialer
|
|
|
|
c.Assert(ps.Count(), check.Equals, 0)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// if we got here, it means two sites are cross-connected. lets execute SSH commands
|
|
|
|
sshPort := a.GetPortSSHInt()
|
|
|
|
cmd := []string{"echo", "hello world"}
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// directly:
|
|
|
|
tc, err := a.NewClient(ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: a.Secrets.SiteName,
|
|
|
|
Host: Host,
|
|
|
|
Port: sshPort,
|
|
|
|
ForwardAgent: true,
|
|
|
|
})
|
|
|
|
tc.Stdout = &outputA
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(outputA.String(), check.Equals, "hello world\n")
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// Update trusted CAs.
|
|
|
|
err = tc.UpdateTrustedCA(context.TODO(), a.Secrets.SiteName)
|
|
|
|
c.Assert(err, check.IsNil)
|
2018-09-06 21:52:41 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// The known_hosts file should have two certificates, the way bytes.Split
|
|
|
|
// works that means the output will be 3 (2 certs + 1 empty).
|
|
|
|
buffer, err := ioutil.ReadFile(filepath.Join(tc.KeysDir, "known_hosts"))
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
parts := bytes.Split(buffer, []byte("\n"))
|
|
|
|
c.Assert(parts, check.HasLen, 3)
|
2018-09-06 21:52:41 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// The certs.pem file should have 2 certificates.
|
|
|
|
buffer, err = ioutil.ReadFile(filepath.Join(tc.KeysDir, "keys", Host, "certs.pem"))
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
roots := x509.NewCertPool()
|
|
|
|
ok := roots.AppendCertsFromPEM(buffer)
|
|
|
|
c.Assert(ok, check.Equals, true)
|
|
|
|
c.Assert(roots.Subjects(), check.HasLen, 2)
|
2016-04-14 18:43:08 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// wait for active tunnel connections to be established
|
|
|
|
waitForActiveTunnelConnections(c, b.Tunnel, a.Secrets.SiteName, 1)
|
2017-05-31 18:48:20 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// via tunnel b->a:
|
|
|
|
tc, err = b.NewClient(ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: a.Secrets.SiteName,
|
|
|
|
Host: Host,
|
|
|
|
Port: sshPort,
|
|
|
|
ForwardAgent: true,
|
|
|
|
})
|
|
|
|
tc.Stdout = &outputB
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(outputA.String(), check.DeepEquals, outputB.String())
|
2016-04-08 01:13:13 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// Stop "site-A" and try to connect to it again via "site-A" (expect a connection error)
|
|
|
|
err = a.StopAuth(false)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
c.Assert(err, check.FitsTypeOf, trace.ConnectionProblem(nil, ""))
|
|
|
|
|
|
|
|
// Reset and start "Site-A" again
|
|
|
|
err = a.Reset()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = a.Start()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// try to execute an SSH command using the same old client to Site-B
|
|
|
|
// "site-A" and "site-B" reverse tunnels are supposed to reconnect,
|
|
|
|
// and 'tc' (client) is also supposed to reconnect
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
if err == nil {
|
|
|
|
break
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
2020-12-07 14:35:15 +00:00
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
2016-04-14 18:43:08 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
searchAndAssert := func(site auth.ClientI, count int) error {
|
|
|
|
tickCh := time.Tick(500 * time.Millisecond)
|
|
|
|
stopCh := time.After(5 * time.Second)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// only look for exec events
|
|
|
|
execQuery := fmt.Sprintf("%s=%s", events.EventType, events.ExecEvent)
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-tickCh:
|
|
|
|
eventsInSite, err := site.SearchEvents(now, now.Add(1*time.Hour), execQuery, 0)
|
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// found the number of events we were looking for
|
|
|
|
if got, want := len(eventsInSite), count; got == want {
|
|
|
|
return nil
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
2020-12-07 14:35:15 +00:00
|
|
|
case <-stopCh:
|
|
|
|
return trace.BadParameter("unable to find %v events after 5s", count)
|
2017-12-13 02:17:18 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-07 14:35:15 +00:00
|
|
|
}
|
2016-04-08 01:13:13 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
siteA := a.GetSiteAPI(a.Secrets.SiteName)
|
|
|
|
err = searchAndAssert(siteA, execCountSiteA)
|
|
|
|
c.Assert(err, check.IsNil)
|
2016-05-11 05:05:01 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
siteB := b.GetSiteAPI(b.Secrets.SiteName)
|
|
|
|
err = searchAndAssert(siteB, execCountSiteB)
|
|
|
|
c.Assert(err, check.IsNil)
|
2016-05-11 05:05:01 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// stop both sites for real
|
|
|
|
c.Assert(b.StopAll(), check.IsNil)
|
|
|
|
c.Assert(a.StopAll(), check.IsNil)
|
2016-04-08 01:13:13 +00:00
|
|
|
}
|
2016-04-14 20:30:13 +00:00
|
|
|
|
2017-05-31 18:48:20 +00:00
|
|
|
// TestTwoClustersProxy checks if the reverse tunnel uses a HTTP PROXY to
|
|
|
|
// establish a connection.
|
|
|
|
func (s *IntSuite) TestTwoClustersProxy(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-05-31 18:48:20 +00:00
|
|
|
// start the http proxy
|
|
|
|
ps := &proxyServer{}
|
|
|
|
ts := httptest.NewServer(ps)
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
// set the http_proxy environment variable
|
|
|
|
u, err := url.Parse(ts.URL)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
os.Setenv("http_proxy", u.Host)
|
|
|
|
defer os.Setenv("http_proxy", "")
|
|
|
|
|
|
|
|
username := s.me.Username
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
a := s.newNamedTeleportInstance(c, "site-A")
|
|
|
|
b := s.newNamedTeleportInstance(c, "site-B")
|
2017-05-31 18:48:20 +00:00
|
|
|
|
|
|
|
a.AddUser(username, []string{username})
|
|
|
|
b.AddUser(username, []string{username})
|
|
|
|
|
|
|
|
c.Assert(b.Create(a.Secrets.AsSlice(), false, nil), check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
defer b.StopAll()
|
2017-05-31 18:48:20 +00:00
|
|
|
c.Assert(a.Create(b.Secrets.AsSlice(), true, nil), check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
defer a.StopAll()
|
2017-05-31 18:48:20 +00:00
|
|
|
|
|
|
|
c.Assert(b.Start(), check.IsNil)
|
|
|
|
c.Assert(a.Start(), check.IsNil)
|
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime := time.Now().Add(time.Second * 10)
|
2020-09-14 02:48:13 +00:00
|
|
|
for len(checkGetClusters(c, a.Tunnel)) < 2 && len(checkGetClusters(c, b.Tunnel)) < 2 {
|
2017-05-31 18:48:20 +00:00
|
|
|
time.Sleep(time.Millisecond * 200)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("two sites do not see each other: tunnels are not working")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure the reverse tunnel went through the proxy
|
|
|
|
c.Assert(ps.Count() > 0, check.Equals, true, check.Commentf("proxy did not intercept any connection"))
|
|
|
|
|
|
|
|
// stop both sites for real
|
2020-04-17 16:27:34 +00:00
|
|
|
c.Assert(b.StopAll(), check.IsNil)
|
|
|
|
c.Assert(a.StopAll(), check.IsNil)
|
2017-05-31 18:48:20 +00:00
|
|
|
}
|
|
|
|
|
2017-04-07 23:51:31 +00:00
|
|
|
// TestHA tests scenario when auth server for the cluster goes down
|
|
|
|
// and we switch to local persistent caches
|
|
|
|
func (s *IntSuite) TestHA(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-04-07 23:51:31 +00:00
|
|
|
username := s.me.Username
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
a := s.newNamedTeleportInstance(c, "cluster-a")
|
|
|
|
b := s.newNamedTeleportInstance(c, "cluster-b")
|
2017-04-07 23:51:31 +00:00
|
|
|
|
|
|
|
a.AddUser(username, []string{username})
|
|
|
|
b.AddUser(username, []string{username})
|
|
|
|
|
|
|
|
c.Assert(b.Create(a.Secrets.AsSlice(), false, nil), check.IsNil)
|
|
|
|
c.Assert(a.Create(b.Secrets.AsSlice(), true, nil), check.IsNil)
|
|
|
|
|
|
|
|
c.Assert(b.Start(), check.IsNil)
|
|
|
|
c.Assert(a.Start(), check.IsNil)
|
|
|
|
|
|
|
|
nodePorts := s.getPorts(3)
|
|
|
|
sshPort, proxyWebPort, proxySSHPort := nodePorts[0], nodePorts[1], nodePorts[2]
|
2017-12-13 02:17:18 +00:00
|
|
|
c.Assert(a.StartNodeAndProxy("cluster-a-node", sshPort, proxyWebPort, proxySSHPort), check.IsNil)
|
2017-04-07 23:51:31 +00:00
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime := time.Now().Add(time.Second * 10)
|
2020-09-14 02:48:13 +00:00
|
|
|
for len(checkGetClusters(c, a.Tunnel)) < 2 && len(checkGetClusters(c, b.Tunnel)) < 2 {
|
2017-04-07 23:51:31 +00:00
|
|
|
time.Sleep(time.Millisecond * 2000)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("two sites do not see each other: tunnels are not working")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := []string{"echo", "hello world"}
|
2019-04-30 01:19:22 +00:00
|
|
|
tc, err := b.NewClient(ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: "cluster-a",
|
|
|
|
Host: Loopback,
|
|
|
|
Port: sshPort,
|
|
|
|
})
|
2017-04-07 23:51:31 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
|
2017-04-07 23:51:31 +00:00
|
|
|
output := &bytes.Buffer{}
|
|
|
|
tc.Stdout = output
|
|
|
|
// try to execute an SSH command using the same old client to Site-B
|
|
|
|
// "site-A" and "site-B" reverse tunnels are supposed to reconnect,
|
|
|
|
// and 'tc' (client) is also supposed to reconnect
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
time.Sleep(time.Millisecond * 50)
|
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output.String(), check.Equals, "hello world\n")
|
2019-04-30 01:19:22 +00:00
|
|
|
|
2017-04-07 23:51:31 +00:00
|
|
|
// stop auth server a now
|
2020-04-17 16:27:34 +00:00
|
|
|
c.Assert(a.StopAuth(true), check.IsNil)
|
2017-04-07 23:51:31 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// try to execute an SSH command using the same old client to site-B
|
2017-04-07 23:51:31 +00:00
|
|
|
// "site-A" and "site-B" reverse tunnels are supposed to reconnect,
|
|
|
|
// and 'tc' (client) is also supposed to reconnect
|
2019-05-07 21:00:47 +00:00
|
|
|
for i := 0; i < 30; i++ {
|
|
|
|
time.Sleep(1 * time.Second)
|
2017-04-07 23:51:31 +00:00
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// stop cluster and remaining nodes
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(a.StopAll(), check.IsNil)
|
2020-04-17 16:27:34 +00:00
|
|
|
c.Assert(b.StopAll(), check.IsNil)
|
2017-04-07 23:51:31 +00:00
|
|
|
}
|
|
|
|
|
2017-05-20 02:03:28 +00:00
|
|
|
// TestMapRoles tests local to remote role mapping and access patterns
|
|
|
|
func (s *IntSuite) TestMapRoles(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2020-06-15 21:24:34 +00:00
|
|
|
ctx := context.Background()
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-05-20 02:03:28 +00:00
|
|
|
username := s.me.Username
|
|
|
|
|
|
|
|
clusterMain := "cluster-main"
|
|
|
|
clusterAux := "cluster-aux"
|
2020-12-07 14:35:15 +00:00
|
|
|
|
|
|
|
main := s.newNamedTeleportInstance(c, clusterMain)
|
|
|
|
aux := s.newNamedTeleportInstance(c, clusterAux)
|
2017-05-20 02:03:28 +00:00
|
|
|
|
|
|
|
// main cluster has a local user and belongs to role "main-devs"
|
|
|
|
mainDevs := "main-devs"
|
2017-06-30 01:02:47 +00:00
|
|
|
role, err := services.NewRole(mainDevs, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{username},
|
|
|
|
},
|
2017-05-20 02:03:28 +00:00
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
main.AddUserWithRole(username, role)
|
|
|
|
|
2017-05-20 19:52:03 +00:00
|
|
|
// for role mapping test we turn on Web API on the main cluster
|
|
|
|
// as it's used
|
|
|
|
makeConfig := func(enableSSH bool) ([]*InstanceSecrets, *service.Config) {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2017-05-20 19:52:03 +00:00
|
|
|
tconf.SSH.Enabled = enableSSH
|
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
return nil, tconf
|
|
|
|
}
|
2017-09-10 03:59:38 +00:00
|
|
|
lib.SetInsecureDevMode(true)
|
|
|
|
defer lib.SetInsecureDevMode(false)
|
2017-05-20 19:52:03 +00:00
|
|
|
|
|
|
|
c.Assert(main.CreateEx(makeConfig(false)), check.IsNil)
|
|
|
|
c.Assert(aux.CreateEx(makeConfig(true)), check.IsNil)
|
2017-05-20 02:03:28 +00:00
|
|
|
|
2017-10-20 08:20:26 +00:00
|
|
|
// auxiliary cluster has a role aux-devs
|
2017-05-20 02:03:28 +00:00
|
|
|
// connect aux cluster to main cluster
|
|
|
|
// using trusted clusters, so remote user will be allowed to assume
|
|
|
|
// role specified by mapping remote role "devs" to local role "local-devs"
|
|
|
|
auxDevs := "aux-devs"
|
2017-06-30 01:02:47 +00:00
|
|
|
role, err = services.NewRole(auxDevs, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{username},
|
|
|
|
},
|
2017-05-20 02:03:28 +00:00
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-06-15 21:24:34 +00:00
|
|
|
err = aux.Process.GetAuthServer().UpsertRole(ctx, role)
|
2017-05-20 02:03:28 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2018-12-12 00:22:44 +00:00
|
|
|
trustedClusterToken := "trusted-cluster-token"
|
|
|
|
err = main.Process.GetAuthServer().UpsertToken(
|
|
|
|
services.MustCreateProvisionToken(trustedClusterToken, []teleport.Role{teleport.RoleTrustedCluster}, time.Time{}))
|
2017-05-20 02:03:28 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
trustedCluster := main.Secrets.AsTrustedCluster(trustedClusterToken, services.RoleMap{
|
|
|
|
{Remote: mainDevs, Local: []string{auxDevs}},
|
|
|
|
})
|
|
|
|
|
2017-10-18 23:10:32 +00:00
|
|
|
// modify trusted cluster resource name so it would not
|
|
|
|
// match the cluster name to check that it does not matter
|
|
|
|
trustedCluster.SetName(main.Secrets.SiteName + "-cluster")
|
|
|
|
|
2017-05-20 02:03:28 +00:00
|
|
|
c.Assert(main.Start(), check.IsNil)
|
|
|
|
c.Assert(aux.Start(), check.IsNil)
|
|
|
|
|
|
|
|
err = trustedCluster.CheckAndSetDefaults()
|
|
|
|
c.Assert(err, check.IsNil)
|
2017-06-27 00:26:29 +00:00
|
|
|
|
2017-08-11 01:29:24 +00:00
|
|
|
// try and upsert a trusted cluster
|
2018-12-12 00:22:44 +00:00
|
|
|
tryCreateTrustedCluster(c, aux.Process.GetAuthServer(), trustedCluster)
|
2017-06-27 00:26:29 +00:00
|
|
|
|
2017-05-20 02:03:28 +00:00
|
|
|
nodePorts := s.getPorts(3)
|
|
|
|
sshPort, proxyWebPort, proxySSHPort := nodePorts[0], nodePorts[1], nodePorts[2]
|
2017-12-13 02:17:18 +00:00
|
|
|
c.Assert(aux.StartNodeAndProxy("aux-node", sshPort, proxyWebPort, proxySSHPort), check.IsNil)
|
2017-05-20 02:03:28 +00:00
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
2017-06-27 00:26:29 +00:00
|
|
|
abortTime := time.Now().Add(time.Second * 10)
|
2020-09-14 02:48:13 +00:00
|
|
|
for len(checkGetClusters(c, main.Tunnel)) < 2 && len(checkGetClusters(c, aux.Tunnel)) < 2 {
|
2017-05-20 02:03:28 +00:00
|
|
|
time.Sleep(time.Millisecond * 2000)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("two clusters do not see each other: tunnels are not working")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 00:19:24 +00:00
|
|
|
// Make sure that GetNodes returns nodes in the remote site. This makes
|
|
|
|
// sure identity aware GetNodes works for remote clusters. Testing of the
|
|
|
|
// correct nodes that identity aware GetNodes is done in TestList.
|
Events and GRPC API
This commit introduces several key changes to
Teleport backend and API infrastructure
in order to achieve scalability improvements
on 10K+ node deployments.
Events and plain keyspace
--------------------------
New backend interface supports events,
pagination and range queries
and moves away from buckets to
plain keyspace, what better aligns
with DynamoDB and Etcd featuring similar
interfaces.
All backend implementations are
exposing Events API, allowing
multiple subscribers to consume the same
event stream and avoid polling database.
Replacing BoltDB, Dir with SQLite
-------------------------------
BoltDB backend does not support
having two processes access the database at the
same time. This prevented Teleport
using BoltDB backend to be live reloaded.
SQLite supports reads/writes by multiple
processes and makes Dir backend obsolete
as SQLite is more efficient on larger collections,
supports transactions and can detect data
corruption.
Teleport automatically migrates data from
Bolt and Dir backends into SQLite.
GRPC API and protobuf resources
-------------------------------
GRPC API has been introduced for
the auth server. The auth server now serves both GRPC
and JSON-HTTP API on the same TLS socket and uses
the same client certificate authentication.
All future API methods should use GRPC and HTTP-JSON
API is considered obsolete.
In addition to that some resources like
Server and CertificateAuthority are now
generated from protobuf service specifications in
a way that is fully backward compatible with
original JSON spec and schema, so the same resource
can be encoded and decoded from JSON, YAML
and protobuf.
All models should be refactored
into new proto specification over time.
Streaming presence service
--------------------------
In order to cut bandwidth, nodes
are sending full updates only when changes
to labels or spec have occured, otherwise
new light-weight GRPC keep alive updates are sent
over to the presence service, reducing
bandwidth usage on multi-node deployments.
In addition to that nodes are no longer polling
auth server for certificate authority rotation
updates, instead they subscribe to event updates
to detect updates as soon as they happen.
This is a new API, so the errors are inevitable,
that's why polling is still done, but
on a way slower rate.
2018-11-07 23:33:38 +00:00
|
|
|
var nodes []services.Server
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
nodes, err = aux.Process.GetAuthServer().GetNodes(defaults.Namespace, services.SkipValidation())
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
if len(nodes) != 2 {
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2018-08-23 00:19:24 +00:00
|
|
|
c.Assert(nodes, check.HasLen, 2)
|
|
|
|
|
2017-05-20 02:03:28 +00:00
|
|
|
cmd := []string{"echo", "hello world"}
|
2018-11-20 01:36:19 +00:00
|
|
|
tc, err := main.NewClient(ClientConfig{Login: username, Cluster: clusterAux, Host: Loopback, Port: sshPort})
|
2017-05-20 02:03:28 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
output := &bytes.Buffer{}
|
|
|
|
tc.Stdout = output
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
// try to execute an SSH command using the same old client to Site-B
|
|
|
|
// "site-A" and "site-B" reverse tunnels are supposed to reconnect,
|
|
|
|
// and 'tc' (client) is also supposed to reconnect
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
time.Sleep(time.Millisecond * 50)
|
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output.String(), check.Equals, "hello world\n")
|
|
|
|
|
2017-06-27 00:26:29 +00:00
|
|
|
// make sure both clusters have the right certificate authorities with the right signing keys.
|
|
|
|
var tests = []struct {
|
2017-10-18 23:10:32 +00:00
|
|
|
mainClusterName string
|
|
|
|
auxClusterName string
|
2017-06-27 00:26:29 +00:00
|
|
|
inCluster *TeleInstance
|
|
|
|
outChkMainUserCA check.Checker
|
|
|
|
outLenMainUserCA int
|
|
|
|
outChkMainHostCA check.Checker
|
|
|
|
outLenMainHostCA int
|
|
|
|
outChkAuxUserCA check.Checker
|
|
|
|
outLenAuxUserCA int
|
|
|
|
outChkAuxHostCA check.Checker
|
|
|
|
outLenAuxHostCA int
|
|
|
|
}{
|
|
|
|
// 0 - main
|
|
|
|
// * User CA for main has one signing key.
|
|
|
|
// * Host CA for main has one signing key.
|
|
|
|
// * User CA for aux does not exist.
|
|
|
|
// * Host CA for aux has no signing keys.
|
|
|
|
{
|
2017-10-18 23:10:32 +00:00
|
|
|
main.Secrets.SiteName,
|
|
|
|
aux.Secrets.SiteName,
|
2017-06-27 00:26:29 +00:00
|
|
|
main,
|
|
|
|
check.IsNil, 1,
|
|
|
|
check.IsNil, 1,
|
|
|
|
check.NotNil, 0,
|
|
|
|
check.IsNil, 0,
|
|
|
|
},
|
|
|
|
// 1 - aux
|
|
|
|
// * User CA for main has no signing keys.
|
|
|
|
// * Host CA for main has no signing keys.
|
|
|
|
// * User CA for aux has one signing key.
|
|
|
|
// * Host CA for aux has one signing key.
|
|
|
|
{
|
2017-10-18 23:10:32 +00:00
|
|
|
trustedCluster.GetName(),
|
|
|
|
aux.Secrets.SiteName,
|
2017-06-27 00:26:29 +00:00
|
|
|
aux,
|
|
|
|
check.IsNil, 0,
|
|
|
|
check.IsNil, 0,
|
|
|
|
check.IsNil, 1,
|
|
|
|
check.IsNil, 1,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tt := range tests {
|
2017-10-18 23:10:32 +00:00
|
|
|
cid := services.CertAuthID{Type: services.UserCA, DomainName: tt.mainClusterName}
|
2017-06-27 00:26:29 +00:00
|
|
|
mainUserCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(cid, true)
|
|
|
|
c.Assert(err, tt.outChkMainUserCA)
|
|
|
|
if tt.outChkMainUserCA == check.IsNil {
|
|
|
|
c.Assert(mainUserCAs.GetSigningKeys(), check.HasLen, tt.outLenMainUserCA, check.Commentf("Test %v, Main User CA", i))
|
|
|
|
}
|
|
|
|
|
2017-10-18 23:10:32 +00:00
|
|
|
cid = services.CertAuthID{Type: services.HostCA, DomainName: tt.mainClusterName}
|
2017-06-27 00:26:29 +00:00
|
|
|
mainHostCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(cid, true)
|
|
|
|
c.Assert(err, tt.outChkMainHostCA)
|
|
|
|
if tt.outChkMainHostCA == check.IsNil {
|
|
|
|
c.Assert(mainHostCAs.GetSigningKeys(), check.HasLen, tt.outLenMainHostCA, check.Commentf("Test %v, Main Host CA", i))
|
|
|
|
}
|
|
|
|
|
2017-10-18 23:10:32 +00:00
|
|
|
cid = services.CertAuthID{Type: services.UserCA, DomainName: tt.auxClusterName}
|
2017-06-27 00:26:29 +00:00
|
|
|
auxUserCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(cid, true)
|
|
|
|
c.Assert(err, tt.outChkAuxUserCA)
|
|
|
|
if tt.outChkAuxUserCA == check.IsNil {
|
|
|
|
c.Assert(auxUserCAs.GetSigningKeys(), check.HasLen, tt.outLenAuxUserCA, check.Commentf("Test %v, Aux User CA", i))
|
|
|
|
}
|
|
|
|
|
2017-10-18 23:10:32 +00:00
|
|
|
cid = services.CertAuthID{Type: services.HostCA, DomainName: tt.auxClusterName}
|
2017-06-27 00:26:29 +00:00
|
|
|
auxHostCAs, err := tt.inCluster.Process.GetAuthServer().GetCertAuthority(cid, true)
|
|
|
|
c.Assert(err, tt.outChkAuxHostCA)
|
|
|
|
if tt.outChkAuxHostCA == check.IsNil {
|
|
|
|
c.Assert(auxHostCAs.GetSigningKeys(), check.HasLen, tt.outLenAuxHostCA, check.Commentf("Test %v, Aux Host CA", i))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-20 02:03:28 +00:00
|
|
|
// stop clusters and remaining nodes
|
2020-04-17 16:27:34 +00:00
|
|
|
c.Assert(main.StopAll(), check.IsNil)
|
|
|
|
c.Assert(aux.StopAll(), check.IsNil)
|
2017-05-20 02:03:28 +00:00
|
|
|
}
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
// tryCreateTrustedCluster performs several attempts to create a trusted cluster,
|
|
|
|
// retries on connection problems and access denied errors to let caches
|
|
|
|
// propagate and services to start
|
2020-10-05 22:12:25 +00:00
|
|
|
func tryCreateTrustedCluster(c *check.C, authServer *auth.Server, trustedCluster services.TrustedCluster) {
|
2020-06-15 21:24:34 +00:00
|
|
|
ctx := context.TODO()
|
2018-12-12 00:22:44 +00:00
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
log.Debugf("Will create trusted cluster %v, attempt %v.", trustedCluster, i)
|
2020-06-15 21:24:34 +00:00
|
|
|
_, err := authServer.UpsertTrustedCluster(ctx, trustedCluster)
|
2018-12-12 00:22:44 +00:00
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if trace.IsConnectionProblem(err) {
|
|
|
|
log.Debugf("Retrying on connection problem: %v.", err)
|
|
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if trace.IsAccessDenied(err) {
|
|
|
|
log.Debugf("Retrying on access denied: %v.", err)
|
|
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
c.Fatalf("Terminating on unexpected problem %v.", err)
|
|
|
|
}
|
|
|
|
c.Fatalf("Timeout creating trusted cluster")
|
|
|
|
}
|
|
|
|
|
2019-07-22 23:58:38 +00:00
|
|
|
// trustedClusterTest is a test setup for trusted clusters tests
|
|
|
|
type trustedClusterTest struct {
|
|
|
|
// multiplex sets up multiplexing of the reversetunnel SSH
|
|
|
|
// socket and the proxy's web socket
|
|
|
|
multiplex bool
|
|
|
|
// useJumpHost turns on jump host mode for the access
|
|
|
|
// to the proxy instead of the proxy command
|
|
|
|
useJumpHost bool
|
2020-09-14 02:48:13 +00:00
|
|
|
// useLabels turns on trusted cluster labels and
|
|
|
|
// verifies RBAC
|
|
|
|
useLabels bool
|
2019-07-22 23:58:38 +00:00
|
|
|
}
|
|
|
|
|
2018-01-06 00:20:56 +00:00
|
|
|
// TestTrustedClusters tests remote clusters scenarios
|
|
|
|
// using trusted clusters feature
|
|
|
|
func (s *IntSuite) TestTrustedClusters(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2019-07-22 23:58:38 +00:00
|
|
|
s.trustedClusters(c, trustedClusterTest{multiplex: false})
|
|
|
|
}
|
|
|
|
|
2020-09-14 02:48:13 +00:00
|
|
|
// TestTrustedClustersWithLabels tests remote clusters scenarios
|
|
|
|
// using trusted clusters feature and access labels
|
|
|
|
func (s *IntSuite) TestTrustedClustersWithLabels(c *check.C) {
|
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
s.trustedClusters(c, trustedClusterTest{multiplex: false, useLabels: true})
|
|
|
|
}
|
|
|
|
|
2019-07-22 23:58:38 +00:00
|
|
|
// TestJumpTrustedClusters tests remote clusters scenarios
|
|
|
|
// using trusted clusters feature using jumphost connection
|
|
|
|
func (s *IntSuite) TestJumpTrustedClusters(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2019-07-22 23:58:38 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
s.trustedClusters(c, trustedClusterTest{multiplex: false, useJumpHost: true})
|
2018-01-06 00:20:56 +00:00
|
|
|
}
|
|
|
|
|
2020-09-14 02:48:13 +00:00
|
|
|
// TestJumpTrustedClusters tests remote clusters scenarios
|
|
|
|
// using trusted clusters feature using jumphost connection
|
|
|
|
func (s *IntSuite) TestJumpTrustedClustersWithLabels(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2020-09-14 02:48:13 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
s.trustedClusters(c, trustedClusterTest{multiplex: false, useJumpHost: true, useLabels: true})
|
|
|
|
}
|
|
|
|
|
2018-01-06 00:20:56 +00:00
|
|
|
// TestMultiplexingTrustedClusters tests remote clusters scenarios
|
|
|
|
// using trusted clusters feature
|
|
|
|
func (s *IntSuite) TestMultiplexingTrustedClusters(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2019-07-22 23:58:38 +00:00
|
|
|
s.trustedClusters(c, trustedClusterTest{multiplex: true})
|
2018-01-06 00:20:56 +00:00
|
|
|
}
|
|
|
|
|
2019-07-22 23:58:38 +00:00
|
|
|
func (s *IntSuite) trustedClusters(c *check.C, test trustedClusterTest) {
|
2020-06-15 21:24:34 +00:00
|
|
|
ctx := context.Background()
|
2017-12-28 02:51:46 +00:00
|
|
|
username := s.me.Username
|
|
|
|
|
|
|
|
clusterMain := "cluster-main"
|
|
|
|
clusterAux := "cluster-aux"
|
2020-12-07 14:35:15 +00:00
|
|
|
main := NewInstance(InstanceConfig{
|
|
|
|
ClusterName: clusterMain,
|
|
|
|
HostID: HostID,
|
|
|
|
NodeName: Host,
|
2021-02-12 23:09:26 +00:00
|
|
|
Ports: s.getPorts(6),
|
2020-12-07 14:35:15 +00:00
|
|
|
Priv: s.priv,
|
|
|
|
Pub: s.pub,
|
|
|
|
MultiplexProxy: test.multiplex,
|
|
|
|
log: s.log,
|
|
|
|
})
|
|
|
|
aux := s.newNamedTeleportInstance(c, clusterAux)
|
2017-12-28 02:51:46 +00:00
|
|
|
|
2020-01-09 21:52:00 +00:00
|
|
|
// main cluster has a local user and belongs to role "main-devs" and "main-admins"
|
2017-12-28 02:51:46 +00:00
|
|
|
mainDevs := "main-devs"
|
2020-01-09 21:52:00 +00:00
|
|
|
devsRole, err := services.NewRole(mainDevs, services.RoleSpecV3{
|
2017-12-28 02:51:46 +00:00
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{username},
|
|
|
|
},
|
|
|
|
})
|
2020-09-14 02:48:13 +00:00
|
|
|
// If the test is using labels, the cluster will be labeled
|
|
|
|
// and user will be granted access if labels match.
|
|
|
|
// Otherwise, to preserve backwards-compatibility
|
|
|
|
// roles with no labels will grant access to clusters with no labels.
|
|
|
|
if test.useLabels {
|
|
|
|
devsRole.SetClusterLabels(services.Allow, services.Labels{"access": []string{"prod"}})
|
|
|
|
}
|
2017-12-28 02:51:46 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-01-09 21:52:00 +00:00
|
|
|
|
|
|
|
mainAdmins := "main-admins"
|
|
|
|
adminsRole, err := services.NewRole(mainAdmins, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{"superuser"},
|
|
|
|
},
|
|
|
|
})
|
Fix staticcheck findings in integration/ and tool/
Original finding list:
```
tool/tctl/common/node_command.go:163:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
tool/tctl/common/status_command.go:110:2: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(view())
^
tool/tctl/common/status_command.go:126:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(view())
^
tool/tctl/common/token_command.go:201:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(tokensView())
^
tool/tctl/common/token_command.go:207:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(data))
^
tool/tctl/common/user_command.go:248:2: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
tool/tctl/common/user_command.go:294:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
integration/helpers.go:200:2: SA4006: this value of `err` is never used (staticcheck)
cryptoPubKey, err := sshutils.CryptoPublicKey(cfg.Pub)
^
integration/helpers.go:399:3: SA4006: this value of `roles` is never used (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:597:4: SA4006: this value of `roles` is never used (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:599:4: SA4006: this value of `roles` is never used (staticcheck)
roles = user.Roles
^
integration/integration_test.go:1625:2: SA4006: this value of `err` is never used (staticcheck)
adminsRole, err := services.NewRole(mainAdmins, services.RoleSpecV3{
^
integration/integration_test.go:2185:2: SA4006: this value of `output` is never used (staticcheck)
output, err = runCommand(main, []string{"echo", "hello world"}, cfg, 1)
^
integration/integration_test.go:2340:2: SA4006: this value of `output` is never used (staticcheck)
output, err = runCommand(main, []string{"echo", "hello world"}, cfgProxy, 1)
^
integration/kube_integration_test.go:154:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:321:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:366:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:386:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := s.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:465:2: SA4006: this value of `err` is never used (staticcheck)
mainRole, err := services.NewRole("main-kube", services.RoleSpecV3{
^
integration/kube_integration_test.go:579:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := proxyClient.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:727:2: SA4006: this value of `err` is never used (staticcheck)
mainRole, err := services.NewRole("main-kube", services.RoleSpecV3{
^
integration/kube_integration_test.go:840:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := proxyClient.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:1008:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
tool/teleport/common/teleport_test.go:83:2: SA4006: this value of `cmd` is never used (staticcheck)
cmd, conf := Run(Options{
^
tool/teleport/common/teleport_test.go:91:2: SA4006: this value of `cmd` is never used (staticcheck)
cmd, conf = Run(Options{
^
tool/tsh/tsh.go:170:2: SA4006: this value of `cmdLine` is never used (staticcheck)
cmdLine := []string{}
^
integration/helpers.go:399:11: SA4010: this result of append is never used, except maybe in other appends (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:597:12: SA4010: this result of append is never used, except maybe in other appends (staticcheck)
roles = append(roles, role)
^
integration/integration_test.go:1092:7: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(b.Tunnel.GetSites()) < 2 && len(b.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1426:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1691:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1895:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/kube_integration_test.go:548:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/kube_integration_test.go:814:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
```
2020-04-27 17:39:58 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-01-09 21:52:00 +00:00
|
|
|
|
|
|
|
main.AddUserWithRole(username, devsRole, adminsRole)
|
2017-12-28 02:51:46 +00:00
|
|
|
|
2020-09-14 02:48:13 +00:00
|
|
|
// Ops users can only access remote clusters with label 'access': 'ops'
|
|
|
|
mainOps := "main-ops"
|
|
|
|
mainOpsRole, err := services.NewRole(mainOps, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{username},
|
|
|
|
ClusterLabels: services.Labels{"access": []string{"ops"}},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
main.AddUserWithRole(mainOps, mainOpsRole, adminsRole)
|
|
|
|
|
2017-12-28 02:51:46 +00:00
|
|
|
// for role mapping test we turn on Web API on the main cluster
|
|
|
|
// as it's used
|
|
|
|
makeConfig := func(enableSSH bool) ([]*InstanceSecrets, *service.Config) {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2017-12-28 02:51:46 +00:00
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
2019-04-30 01:19:22 +00:00
|
|
|
tconf.SSH.Enabled = enableSSH
|
2017-12-28 02:51:46 +00:00
|
|
|
return nil, tconf
|
|
|
|
}
|
|
|
|
lib.SetInsecureDevMode(true)
|
|
|
|
defer lib.SetInsecureDevMode(false)
|
|
|
|
|
|
|
|
c.Assert(main.CreateEx(makeConfig(false)), check.IsNil)
|
|
|
|
c.Assert(aux.CreateEx(makeConfig(true)), check.IsNil)
|
|
|
|
|
2020-01-09 21:52:00 +00:00
|
|
|
// auxiliary cluster has only a role aux-devs
|
2017-12-28 02:51:46 +00:00
|
|
|
// connect aux cluster to main cluster
|
|
|
|
// using trusted clusters, so remote user will be allowed to assume
|
|
|
|
// role specified by mapping remote role "devs" to local role "local-devs"
|
|
|
|
auxDevs := "aux-devs"
|
2020-01-09 21:52:00 +00:00
|
|
|
auxRole, err := services.NewRole(auxDevs, services.RoleSpecV3{
|
2017-12-28 02:51:46 +00:00
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{username},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-06-15 21:24:34 +00:00
|
|
|
err = aux.Process.GetAuthServer().UpsertRole(ctx, auxRole)
|
2017-12-28 02:51:46 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-09-14 02:48:13 +00:00
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
trustedClusterToken := "trusted-cluster-token"
|
2020-09-14 02:48:13 +00:00
|
|
|
tokenResource, err := services.NewProvisionToken(trustedClusterToken, []teleport.Role{teleport.RoleTrustedCluster}, time.Time{})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
if test.useLabels {
|
|
|
|
meta := tokenResource.GetMetadata()
|
|
|
|
meta.Labels = map[string]string{"access": "prod"}
|
|
|
|
tokenResource.SetMetadata(meta)
|
|
|
|
}
|
|
|
|
err = main.Process.GetAuthServer().UpsertToken(tokenResource)
|
2017-12-28 02:51:46 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-01-09 21:52:00 +00:00
|
|
|
// Note that the mapping omits admins role, this is to cover the scenario
|
|
|
|
// when root cluster and leaf clusters have different role sets
|
2017-12-28 02:51:46 +00:00
|
|
|
trustedCluster := main.Secrets.AsTrustedCluster(trustedClusterToken, services.RoleMap{
|
|
|
|
{Remote: mainDevs, Local: []string{auxDevs}},
|
2020-09-14 02:48:13 +00:00
|
|
|
{Remote: mainOps, Local: []string{auxDevs}},
|
2017-12-28 02:51:46 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// modify trusted cluster resource name so it would not
|
|
|
|
// match the cluster name to check that it does not matter
|
|
|
|
trustedCluster.SetName(main.Secrets.SiteName + "-cluster")
|
|
|
|
|
|
|
|
c.Assert(main.Start(), check.IsNil)
|
|
|
|
c.Assert(aux.Start(), check.IsNil)
|
|
|
|
|
|
|
|
err = trustedCluster.CheckAndSetDefaults()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// try and upsert a trusted cluster
|
2018-12-12 00:22:44 +00:00
|
|
|
tryCreateTrustedCluster(c, aux.Process.GetAuthServer(), trustedCluster)
|
2017-12-28 02:51:46 +00:00
|
|
|
|
|
|
|
nodePorts := s.getPorts(3)
|
|
|
|
sshPort, proxyWebPort, proxySSHPort := nodePorts[0], nodePorts[1], nodePorts[2]
|
|
|
|
c.Assert(aux.StartNodeAndProxy("aux-node", sshPort, proxyWebPort, proxySSHPort), check.IsNil)
|
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime := time.Now().Add(time.Second * 10)
|
2020-09-14 02:48:13 +00:00
|
|
|
for len(checkGetClusters(c, main.Tunnel)) < 2 && len(checkGetClusters(c, aux.Tunnel)) < 2 {
|
2017-12-28 02:51:46 +00:00
|
|
|
time.Sleep(time.Millisecond * 2000)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("two clusters do not see each other: tunnels are not working")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := []string{"echo", "hello world"}
|
2019-04-30 01:19:22 +00:00
|
|
|
|
|
|
|
// Try and connect to a node in the Aux cluster from the Main cluster using
|
|
|
|
// direct dialing.
|
2019-07-22 23:58:38 +00:00
|
|
|
creds, err := GenerateUserCreds(UserCredsRequest{
|
|
|
|
Process: main.Process,
|
|
|
|
Username: username,
|
|
|
|
RouteToCluster: clusterAux,
|
2019-04-30 01:19:22 +00:00
|
|
|
})
|
2017-12-28 02:51:46 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-01-09 21:52:00 +00:00
|
|
|
|
2019-07-22 23:58:38 +00:00
|
|
|
tc, err := main.NewClientWithCreds(ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: clusterAux,
|
|
|
|
Host: Loopback,
|
|
|
|
Port: sshPort,
|
|
|
|
JumpHost: test.useJumpHost,
|
|
|
|
}, *creds)
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-01-09 21:52:00 +00:00
|
|
|
|
|
|
|
// tell the client to trust aux cluster CAs (from secrets). this is the
|
|
|
|
// equivalent of 'known hosts' in openssh
|
|
|
|
auxCAS := aux.Secrets.GetCAs()
|
|
|
|
for i := range auxCAS {
|
|
|
|
err = tc.AddTrustedCA(auxCAS[i])
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
|
2017-12-28 02:51:46 +00:00
|
|
|
output := &bytes.Buffer{}
|
|
|
|
tc.Stdout = output
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
time.Sleep(time.Millisecond * 50)
|
2020-06-15 21:24:34 +00:00
|
|
|
err = tc.SSH(ctx, cmd, false)
|
2017-12-28 02:51:46 +00:00
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output.String(), check.Equals, "hello world\n")
|
|
|
|
|
2020-09-14 02:48:13 +00:00
|
|
|
// Try and connect to a node in the Aux cluster from the Main cluster using
|
|
|
|
// direct dialing as ops user
|
|
|
|
opsCreds, err := GenerateUserCreds(UserCredsRequest{
|
|
|
|
Process: main.Process,
|
|
|
|
Username: mainOps,
|
|
|
|
RouteToCluster: clusterAux,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
opsClient, err := main.NewClientWithCreds(ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: clusterAux,
|
|
|
|
Host: Loopback,
|
|
|
|
Port: sshPort,
|
|
|
|
JumpHost: test.useJumpHost,
|
|
|
|
}, *opsCreds)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// tell the client to trust aux cluster CAs (from secrets). this is the
|
|
|
|
// equivalent of 'known hosts' in openssh
|
|
|
|
auxCAS = aux.Secrets.GetCAs()
|
|
|
|
for _, ca := range auxCAS {
|
|
|
|
err = opsClient.AddTrustedCA(ca)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
|
|
|
|
opsClient.Stdout = &bytes.Buffer{}
|
|
|
|
err = opsClient.SSH(ctx, cmd, false)
|
|
|
|
// verify that ops user can not access the cluster
|
|
|
|
fixtures.ExpectNotFound(c, err)
|
|
|
|
|
2020-01-09 21:52:00 +00:00
|
|
|
// ListNodes expect labels as a value of host
|
|
|
|
tc.Host = ""
|
2020-06-15 21:24:34 +00:00
|
|
|
servers, err := tc.ListNodes(ctx)
|
2020-01-09 21:52:00 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(servers, check.HasLen, 2)
|
|
|
|
tc.Host = Loopback
|
|
|
|
|
2017-12-28 02:51:46 +00:00
|
|
|
// check that remote cluster has been provisioned
|
|
|
|
remoteClusters, err := main.Process.GetAuthServer().GetRemoteClusters()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(remoteClusters, check.HasLen, 1)
|
|
|
|
c.Assert(remoteClusters[0].GetName(), check.Equals, clusterAux)
|
|
|
|
|
|
|
|
// after removing the remote cluster, the connection will start failing
|
|
|
|
err = main.Process.GetAuthServer().DeleteRemoteCluster(clusterAux)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
time.Sleep(time.Millisecond * 50)
|
2020-06-15 21:24:34 +00:00
|
|
|
err = tc.SSH(ctx, cmd, false)
|
2017-12-28 02:51:46 +00:00
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.NotNil, check.Commentf("expected tunnel to close and SSH client to start failing"))
|
|
|
|
|
|
|
|
// remove trusted cluster from aux cluster side, and recrete right after
|
|
|
|
// this should re-establish connection
|
2020-06-15 21:24:34 +00:00
|
|
|
err = aux.Process.GetAuthServer().DeleteTrustedCluster(ctx, trustedCluster.GetName())
|
2017-12-28 02:51:46 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-06-15 21:24:34 +00:00
|
|
|
_, err = aux.Process.GetAuthServer().UpsertTrustedCluster(ctx, trustedCluster)
|
2017-12-28 02:51:46 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// check that remote cluster has been re-provisioned
|
|
|
|
remoteClusters, err = main.Process.GetAuthServer().GetRemoteClusters()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(remoteClusters, check.HasLen, 1)
|
|
|
|
c.Assert(remoteClusters[0].GetName(), check.Equals, clusterAux)
|
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime = time.Now().Add(time.Second * 10)
|
2020-09-14 02:48:13 +00:00
|
|
|
for len(checkGetClusters(c, main.Tunnel)) < 2 {
|
2017-12-28 02:51:46 +00:00
|
|
|
time.Sleep(time.Millisecond * 2000)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("two clusters do not see each other: tunnels are not working")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// connection and client should recover and work again
|
|
|
|
output = &bytes.Buffer{}
|
|
|
|
tc.Stdout = output
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
time.Sleep(time.Millisecond * 50)
|
2020-06-15 21:24:34 +00:00
|
|
|
err = tc.SSH(ctx, cmd, false)
|
2017-12-28 02:51:46 +00:00
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output.String(), check.Equals, "hello world\n")
|
|
|
|
|
|
|
|
// stop clusters and remaining nodes
|
2020-04-17 16:27:34 +00:00
|
|
|
c.Assert(main.StopAll(), check.IsNil)
|
|
|
|
c.Assert(aux.StopAll(), check.IsNil)
|
2017-12-28 02:51:46 +00:00
|
|
|
}
|
|
|
|
|
2020-09-14 02:48:13 +00:00
|
|
|
func checkGetClusters(c *check.C, tun reversetunnel.Server) []reversetunnel.RemoteSite {
|
|
|
|
clusters, err := tun.GetSites()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
return clusters
|
|
|
|
}
|
|
|
|
|
2019-04-30 01:19:22 +00:00
|
|
|
func (s *IntSuite) TestTrustedTunnelNode(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2020-06-15 21:24:34 +00:00
|
|
|
ctx := context.Background()
|
2019-04-30 01:19:22 +00:00
|
|
|
username := s.me.Username
|
|
|
|
|
|
|
|
clusterMain := "cluster-main"
|
|
|
|
clusterAux := "cluster-aux"
|
2020-12-07 14:35:15 +00:00
|
|
|
main := s.newNamedTeleportInstance(c, clusterMain)
|
|
|
|
aux := s.newNamedTeleportInstance(c, clusterAux)
|
2019-04-30 01:19:22 +00:00
|
|
|
|
|
|
|
// main cluster has a local user and belongs to role "main-devs"
|
|
|
|
mainDevs := "main-devs"
|
|
|
|
role, err := services.NewRole(mainDevs, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{username},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
main.AddUserWithRole(username, role)
|
|
|
|
|
|
|
|
// for role mapping test we turn on Web API on the main cluster
|
|
|
|
// as it's used
|
|
|
|
makeConfig := func(enableSSH bool) ([]*InstanceSecrets, *service.Config) {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2019-04-30 01:19:22 +00:00
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
tconf.SSH.Enabled = enableSSH
|
|
|
|
return nil, tconf
|
|
|
|
}
|
|
|
|
lib.SetInsecureDevMode(true)
|
|
|
|
defer lib.SetInsecureDevMode(false)
|
|
|
|
|
|
|
|
c.Assert(main.CreateEx(makeConfig(false)), check.IsNil)
|
|
|
|
c.Assert(aux.CreateEx(makeConfig(true)), check.IsNil)
|
|
|
|
|
|
|
|
// auxiliary cluster has a role aux-devs
|
|
|
|
// connect aux cluster to main cluster
|
|
|
|
// using trusted clusters, so remote user will be allowed to assume
|
|
|
|
// role specified by mapping remote role "devs" to local role "local-devs"
|
|
|
|
auxDevs := "aux-devs"
|
|
|
|
role, err = services.NewRole(auxDevs, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{username},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-06-15 21:24:34 +00:00
|
|
|
err = aux.Process.GetAuthServer().UpsertRole(ctx, role)
|
2019-04-30 01:19:22 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
trustedClusterToken := "trusted-cluster-token"
|
|
|
|
err = main.Process.GetAuthServer().UpsertToken(
|
|
|
|
services.MustCreateProvisionToken(trustedClusterToken, []teleport.Role{teleport.RoleTrustedCluster}, time.Time{}))
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
trustedCluster := main.Secrets.AsTrustedCluster(trustedClusterToken, services.RoleMap{
|
|
|
|
{Remote: mainDevs, Local: []string{auxDevs}},
|
|
|
|
})
|
|
|
|
|
|
|
|
// modify trusted cluster resource name so it would not
|
|
|
|
// match the cluster name to check that it does not matter
|
|
|
|
trustedCluster.SetName(main.Secrets.SiteName + "-cluster")
|
|
|
|
|
|
|
|
c.Assert(main.Start(), check.IsNil)
|
|
|
|
c.Assert(aux.Start(), check.IsNil)
|
|
|
|
|
|
|
|
err = trustedCluster.CheckAndSetDefaults()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// try and upsert a trusted cluster
|
|
|
|
tryCreateTrustedCluster(c, aux.Process.GetAuthServer(), trustedCluster)
|
|
|
|
|
|
|
|
// Create a Teleport instance with a node that dials back to the aux cluster.
|
|
|
|
tunnelNodeHostname := "cluster-aux-node"
|
|
|
|
nodeConfig := func() *service.Config {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2019-04-30 01:19:22 +00:00
|
|
|
tconf.Hostname = tunnelNodeHostname
|
|
|
|
tconf.Token = "token"
|
|
|
|
tconf.AuthServers = []utils.NetAddr{
|
2020-12-07 14:35:15 +00:00
|
|
|
{
|
2019-04-30 01:19:22 +00:00
|
|
|
AddrNetwork: "tcp",
|
|
|
|
Addr: net.JoinHostPort(Loopback, aux.GetPortWeb()),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
tconf.Auth.Enabled = false
|
|
|
|
tconf.Proxy.Enabled = false
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
_, err = aux.StartNode(nodeConfig())
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime := time.Now().Add(time.Second * 10)
|
2020-09-14 02:48:13 +00:00
|
|
|
for len(checkGetClusters(c, main.Tunnel)) < 2 && len(checkGetClusters(c, aux.Tunnel)) < 2 {
|
2019-04-30 01:19:22 +00:00
|
|
|
time.Sleep(time.Millisecond * 2000)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("two clusters do not see each other: tunnels are not working")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for both nodes to show up before attempting to dial to them.
|
|
|
|
err = waitForNodeCount(main, clusterAux, 2)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
cmd := []string{"echo", "hello world"}
|
|
|
|
|
|
|
|
// Try and connect to a node in the Aux cluster from the Main cluster using
|
|
|
|
// direct dialing.
|
|
|
|
tc, err := main.NewClient(ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: clusterAux,
|
|
|
|
Host: Loopback,
|
|
|
|
Port: aux.GetPortSSHInt(),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
output := &bytes.Buffer{}
|
|
|
|
tc.Stdout = output
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
time.Sleep(time.Millisecond * 50)
|
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output.String(), check.Equals, "hello world\n")
|
|
|
|
|
|
|
|
// Try and connect to a node in the Aux cluster from the Main cluster using
|
|
|
|
// tunnel dialing.
|
|
|
|
tunnelClient, err := main.NewClient(ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: clusterAux,
|
|
|
|
Host: tunnelNodeHostname,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
tunnelOutput := &bytes.Buffer{}
|
|
|
|
tunnelClient.Stdout = tunnelOutput
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
time.Sleep(time.Millisecond * 50)
|
|
|
|
err = tunnelClient.SSH(context.Background(), cmd, false)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(tunnelOutput.String(), check.Equals, "hello world\n")
|
|
|
|
|
|
|
|
// Stop clusters and remaining nodes.
|
2020-04-17 16:27:34 +00:00
|
|
|
c.Assert(main.StopAll(), check.IsNil)
|
|
|
|
c.Assert(aux.StopAll(), check.IsNil)
|
2019-04-30 01:19:22 +00:00
|
|
|
}
|
|
|
|
|
2019-09-06 17:37:12 +00:00
|
|
|
// TestDiscoveryRecovers ensures that discovery protocol recovers from a bad discovery
|
|
|
|
// state (all known proxies are offline).
|
|
|
|
func (s *IntSuite) TestDiscoveryRecovers(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2019-09-06 17:37:12 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
username := s.me.Username
|
|
|
|
|
|
|
|
// create load balancer for main cluster proxies
|
|
|
|
frontend := *utils.MustParseAddr(net.JoinHostPort(Loopback, strconv.Itoa(s.getPorts(1)[0])))
|
|
|
|
lb, err := utils.NewLoadBalancer(context.TODO(), frontend)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(lb.Listen(), check.IsNil)
|
|
|
|
go lb.Serve()
|
|
|
|
defer lb.Close()
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
remote := s.newNamedTeleportInstance(c, "cluster-remote")
|
|
|
|
main := s.newNamedTeleportInstance(c, "cluster-main")
|
2019-09-06 17:37:12 +00:00
|
|
|
|
|
|
|
remote.AddUser(username, []string{username})
|
|
|
|
main.AddUser(username, []string{username})
|
|
|
|
|
|
|
|
c.Assert(main.Create(remote.Secrets.AsSlice(), false, nil), check.IsNil)
|
|
|
|
mainSecrets := main.Secrets
|
|
|
|
// switch listen address of the main cluster to load balancer
|
|
|
|
mainProxyAddr := *utils.MustParseAddr(mainSecrets.ListenAddr)
|
|
|
|
lb.AddBackend(mainProxyAddr)
|
|
|
|
mainSecrets.ListenAddr = frontend.String()
|
|
|
|
c.Assert(remote.Create(mainSecrets.AsSlice(), true, nil), check.IsNil)
|
|
|
|
|
|
|
|
c.Assert(main.Start(), check.IsNil)
|
|
|
|
c.Assert(remote.Start(), check.IsNil)
|
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime := time.Now().Add(time.Second * 10)
|
2020-09-14 02:48:13 +00:00
|
|
|
for len(checkGetClusters(c, main.Tunnel)) < 2 && len(checkGetClusters(c, remote.Tunnel)) < 2 {
|
2019-09-06 17:37:12 +00:00
|
|
|
time.Sleep(time.Millisecond * 2000)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("two clusters do not see each other: tunnels are not working")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper function for adding a new proxy to "main".
|
|
|
|
addNewMainProxy := func(name string) (reversetunnel.Server, ProxyConfig) {
|
|
|
|
c.Logf("adding main proxy %q...", name)
|
|
|
|
nodePorts := s.getPorts(3)
|
|
|
|
proxyReverseTunnelPort, proxyWebPort, proxySSHPort := nodePorts[0], nodePorts[1], nodePorts[2]
|
|
|
|
newConfig := ProxyConfig{
|
|
|
|
Name: name,
|
|
|
|
SSHPort: proxySSHPort,
|
|
|
|
WebPort: proxyWebPort,
|
|
|
|
ReverseTunnelPort: proxyReverseTunnelPort,
|
|
|
|
}
|
|
|
|
newProxy, err := main.StartProxy(newConfig)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// add proxy as a backend to the load balancer
|
|
|
|
lb.AddBackend(*utils.MustParseAddr(net.JoinHostPort(Loopback, strconv.Itoa(proxyReverseTunnelPort))))
|
|
|
|
return newProxy, newConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
killMainProxy := func(name string) {
|
|
|
|
c.Logf("killing main proxy %q...", name)
|
|
|
|
for _, p := range main.Nodes {
|
|
|
|
if !p.Config.Proxy.Enabled {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if p.Config.Hostname == name {
|
2020-04-07 19:05:19 +00:00
|
|
|
reverseTunnelPort := utils.MustParseAddr(p.Config.Proxy.ReverseTunnelListenAddr.Addr).Port(0)
|
|
|
|
c.Assert(lb.RemoveBackend(*utils.MustParseAddr(net.JoinHostPort(Loopback, strconv.Itoa(reverseTunnelPort)))), check.IsNil)
|
2019-09-06 17:37:12 +00:00
|
|
|
c.Assert(p.Close(), check.IsNil)
|
|
|
|
c.Assert(p.Wait(), check.IsNil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Errorf("cannot close proxy %q (not found)", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper function for testing that a proxy in main has been discovered by
|
|
|
|
// (and is able to use reverse tunnel into) remote. If conf is nil, main's
|
|
|
|
// first/default proxy will be called.
|
|
|
|
testProxyConn := func(conf *ProxyConfig, shouldFail bool) {
|
|
|
|
clientConf := ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: "cluster-remote",
|
|
|
|
Host: Loopback,
|
|
|
|
Port: remote.GetPortSSHInt(),
|
|
|
|
Proxy: conf,
|
|
|
|
}
|
|
|
|
output, err := runCommand(main, []string{"echo", "hello world"}, clientConf, 10)
|
|
|
|
cmt := check.Commentf("testProxyConn(conf=%+v,shouldFail=%v)", conf, shouldFail)
|
|
|
|
if shouldFail {
|
|
|
|
c.Assert(err, check.NotNil, cmt)
|
|
|
|
} else {
|
|
|
|
c.Assert(err, check.IsNil, cmt)
|
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure that initial proxy's tunnel has been established
|
|
|
|
waitForActiveTunnelConnections(c, main.Tunnel, "cluster-remote", 1)
|
|
|
|
// execute the connection via initial proxy; should not fail
|
|
|
|
testProxyConn(nil, false)
|
|
|
|
|
|
|
|
// helper funcion for making numbered proxy names
|
|
|
|
pname := func(n int) string {
|
|
|
|
return fmt.Sprintf("cluster-main-proxy-%d", n)
|
|
|
|
}
|
|
|
|
|
|
|
|
// create first numbered proxy
|
|
|
|
_, c0 := addNewMainProxy(pname(0))
|
|
|
|
// check that we now have two tunnel connections
|
2020-04-07 19:05:19 +00:00
|
|
|
c.Assert(waitForProxyCount(remote, "cluster-main", 2), check.IsNil)
|
2019-09-06 17:37:12 +00:00
|
|
|
// check that first numbered proxy is OK.
|
|
|
|
testProxyConn(&c0, false)
|
|
|
|
// remove the initial proxy.
|
2020-04-07 19:05:19 +00:00
|
|
|
c.Assert(lb.RemoveBackend(mainProxyAddr), check.IsNil)
|
|
|
|
c.Assert(waitForProxyCount(remote, "cluster-main", 1), check.IsNil)
|
2019-09-06 17:37:12 +00:00
|
|
|
|
|
|
|
// force bad state by iteratively removing previous proxy before
|
|
|
|
// adding next proxy; this ensures that discovery protocol's list of
|
|
|
|
// known proxies is all invalid.
|
|
|
|
for i := 0; i < 6; i++ {
|
|
|
|
prev, next := pname(i), pname(i+1)
|
|
|
|
killMainProxy(prev)
|
2020-04-07 19:05:19 +00:00
|
|
|
c.Assert(waitForProxyCount(remote, "cluster-main", 0), check.IsNil)
|
2019-09-06 17:37:12 +00:00
|
|
|
_, cn := addNewMainProxy(next)
|
2020-04-07 19:05:19 +00:00
|
|
|
c.Assert(waitForProxyCount(remote, "cluster-main", 1), check.IsNil)
|
2019-09-06 17:37:12 +00:00
|
|
|
testProxyConn(&cn, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop both clusters and remaining nodes.
|
2020-04-17 16:27:34 +00:00
|
|
|
c.Assert(remote.StopAll(), check.IsNil)
|
|
|
|
c.Assert(main.StopAll(), check.IsNil)
|
2019-09-06 17:37:12 +00:00
|
|
|
}
|
|
|
|
|
2017-10-11 23:36:25 +00:00
|
|
|
// TestDiscovery tests case for multiple proxies and a reverse tunnel
|
|
|
|
// agent that eventually connnects to the the right proxy
|
|
|
|
func (s *IntSuite) TestDiscovery(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-10-11 23:36:25 +00:00
|
|
|
username := s.me.Username
|
|
|
|
|
2017-10-12 17:35:46 +00:00
|
|
|
// create load balancer for main cluster proxies
|
2018-11-20 01:36:19 +00:00
|
|
|
frontend := *utils.MustParseAddr(net.JoinHostPort(Loopback, strconv.Itoa(s.getPorts(1)[0])))
|
2017-10-12 17:35:46 +00:00
|
|
|
lb, err := utils.NewLoadBalancer(context.TODO(), frontend)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(lb.Listen(), check.IsNil)
|
2017-10-12 23:51:18 +00:00
|
|
|
go lb.Serve()
|
2017-10-12 17:35:46 +00:00
|
|
|
defer lb.Close()
|
2017-10-11 23:36:25 +00:00
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
remote := s.newNamedTeleportInstance(c, "cluster-remote")
|
|
|
|
main := s.newNamedTeleportInstance(c, "cluster-main")
|
2017-10-11 23:36:25 +00:00
|
|
|
|
2017-10-12 17:35:46 +00:00
|
|
|
remote.AddUser(username, []string{username})
|
|
|
|
main.AddUser(username, []string{username})
|
2017-10-11 23:36:25 +00:00
|
|
|
|
2017-10-12 17:35:46 +00:00
|
|
|
c.Assert(main.Create(remote.Secrets.AsSlice(), false, nil), check.IsNil)
|
|
|
|
mainSecrets := main.Secrets
|
|
|
|
// switch listen address of the main cluster to load balancer
|
2017-10-12 23:51:18 +00:00
|
|
|
mainProxyAddr := *utils.MustParseAddr(mainSecrets.ListenAddr)
|
|
|
|
lb.AddBackend(mainProxyAddr)
|
2017-10-12 17:35:46 +00:00
|
|
|
mainSecrets.ListenAddr = frontend.String()
|
|
|
|
c.Assert(remote.Create(mainSecrets.AsSlice(), true, nil), check.IsNil)
|
|
|
|
|
|
|
|
c.Assert(main.Start(), check.IsNil)
|
|
|
|
c.Assert(remote.Start(), check.IsNil)
|
2017-10-11 23:36:25 +00:00
|
|
|
|
|
|
|
// wait for both sites to see each other via their reverse tunnels (for up to 10 seconds)
|
|
|
|
abortTime := time.Now().Add(time.Second * 10)
|
2020-09-14 02:48:13 +00:00
|
|
|
for len(checkGetClusters(c, main.Tunnel)) < 2 && len(checkGetClusters(c, remote.Tunnel)) < 2 {
|
2017-10-11 23:36:25 +00:00
|
|
|
time.Sleep(time.Millisecond * 2000)
|
|
|
|
if time.Now().After(abortTime) {
|
|
|
|
c.Fatalf("two clusters do not see each other: tunnels are not working")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-12 17:35:46 +00:00
|
|
|
// start second proxy
|
|
|
|
nodePorts := s.getPorts(3)
|
|
|
|
proxyReverseTunnelPort, proxyWebPort, proxySSHPort := nodePorts[0], nodePorts[1], nodePorts[2]
|
2017-10-12 23:51:18 +00:00
|
|
|
proxyConfig := ProxyConfig{
|
2017-10-12 17:35:46 +00:00
|
|
|
Name: "cluster-main-proxy",
|
|
|
|
SSHPort: proxySSHPort,
|
|
|
|
WebPort: proxyWebPort,
|
|
|
|
ReverseTunnelPort: proxyReverseTunnelPort,
|
2017-10-12 23:51:18 +00:00
|
|
|
}
|
2019-05-09 00:41:52 +00:00
|
|
|
secondProxy, err := main.StartProxy(proxyConfig)
|
2017-10-12 17:35:46 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// add second proxy as a backend to the load balancer
|
2018-11-20 01:36:19 +00:00
|
|
|
lb.AddBackend(*utils.MustParseAddr(net.JoinHostPort(Loopback, strconv.Itoa(proxyReverseTunnelPort))))
|
2017-10-12 17:35:46 +00:00
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
// At this point the main cluster should observe two tunnels
|
|
|
|
// connected to it from remote cluster
|
2019-05-09 00:41:52 +00:00
|
|
|
waitForActiveTunnelConnections(c, main.Tunnel, "cluster-remote", 1)
|
|
|
|
waitForActiveTunnelConnections(c, secondProxy, "cluster-remote", 1)
|
2018-06-15 22:35:02 +00:00
|
|
|
|
2017-10-12 17:35:46 +00:00
|
|
|
// execute the connection via first proxy
|
2018-02-13 00:28:17 +00:00
|
|
|
cfg := ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: "cluster-remote",
|
2018-11-20 01:36:19 +00:00
|
|
|
Host: Loopback,
|
2018-02-13 00:28:17 +00:00
|
|
|
Port: remote.GetPortSSHInt(),
|
|
|
|
}
|
2017-10-12 23:51:18 +00:00
|
|
|
output, err := runCommand(main, []string{"echo", "hello world"}, cfg, 1)
|
2017-10-11 23:36:25 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2017-10-12 23:51:18 +00:00
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
|
|
|
|
2018-02-13 00:28:17 +00:00
|
|
|
// Execute the connection via second proxy, should work. This command is
|
|
|
|
// tried 10 times with 250 millisecond delay between each attempt to allow
|
|
|
|
// the discovery request to be received and the connection added to the agent
|
|
|
|
// pool.
|
2017-10-12 23:51:18 +00:00
|
|
|
cfgProxy := ClientConfig{
|
|
|
|
Login: username,
|
|
|
|
Cluster: "cluster-remote",
|
2018-11-20 01:36:19 +00:00
|
|
|
Host: Loopback,
|
2017-10-12 23:51:18 +00:00
|
|
|
Port: remote.GetPortSSHInt(),
|
|
|
|
Proxy: &proxyConfig,
|
|
|
|
}
|
|
|
|
output, err = runCommand(main, []string{"echo", "hello world"}, cfgProxy, 10)
|
2017-10-11 23:36:25 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2017-10-12 23:51:18 +00:00
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Now disconnect the main proxy and make sure it will reconnect eventually.
|
2020-04-07 19:05:19 +00:00
|
|
|
c.Assert(lb.RemoveBackend(mainProxyAddr), check.IsNil)
|
2019-05-09 00:41:52 +00:00
|
|
|
waitForActiveTunnelConnections(c, secondProxy, "cluster-remote", 1)
|
2017-10-12 23:51:18 +00:00
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Requests going via main proxy should fail.
|
Fix staticcheck findings in integration/ and tool/
Original finding list:
```
tool/tctl/common/node_command.go:163:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
tool/tctl/common/status_command.go:110:2: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(view())
^
tool/tctl/common/status_command.go:126:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(view())
^
tool/tctl/common/token_command.go:201:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(tokensView())
^
tool/tctl/common/token_command.go:207:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(data))
^
tool/tctl/common/user_command.go:248:2: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
tool/tctl/common/user_command.go:294:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
integration/helpers.go:200:2: SA4006: this value of `err` is never used (staticcheck)
cryptoPubKey, err := sshutils.CryptoPublicKey(cfg.Pub)
^
integration/helpers.go:399:3: SA4006: this value of `roles` is never used (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:597:4: SA4006: this value of `roles` is never used (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:599:4: SA4006: this value of `roles` is never used (staticcheck)
roles = user.Roles
^
integration/integration_test.go:1625:2: SA4006: this value of `err` is never used (staticcheck)
adminsRole, err := services.NewRole(mainAdmins, services.RoleSpecV3{
^
integration/integration_test.go:2185:2: SA4006: this value of `output` is never used (staticcheck)
output, err = runCommand(main, []string{"echo", "hello world"}, cfg, 1)
^
integration/integration_test.go:2340:2: SA4006: this value of `output` is never used (staticcheck)
output, err = runCommand(main, []string{"echo", "hello world"}, cfgProxy, 1)
^
integration/kube_integration_test.go:154:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:321:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:366:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:386:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := s.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:465:2: SA4006: this value of `err` is never used (staticcheck)
mainRole, err := services.NewRole("main-kube", services.RoleSpecV3{
^
integration/kube_integration_test.go:579:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := proxyClient.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:727:2: SA4006: this value of `err` is never used (staticcheck)
mainRole, err := services.NewRole("main-kube", services.RoleSpecV3{
^
integration/kube_integration_test.go:840:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := proxyClient.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:1008:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
tool/teleport/common/teleport_test.go:83:2: SA4006: this value of `cmd` is never used (staticcheck)
cmd, conf := Run(Options{
^
tool/teleport/common/teleport_test.go:91:2: SA4006: this value of `cmd` is never used (staticcheck)
cmd, conf = Run(Options{
^
tool/tsh/tsh.go:170:2: SA4006: this value of `cmdLine` is never used (staticcheck)
cmdLine := []string{}
^
integration/helpers.go:399:11: SA4010: this result of append is never used, except maybe in other appends (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:597:12: SA4010: this result of append is never used, except maybe in other appends (staticcheck)
roles = append(roles, role)
^
integration/integration_test.go:1092:7: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(b.Tunnel.GetSites()) < 2 && len(b.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1426:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1691:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1895:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/kube_integration_test.go:548:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/kube_integration_test.go:814:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
```
2020-04-27 17:39:58 +00:00
|
|
|
_, err = runCommand(main, []string{"echo", "hello world"}, cfg, 1)
|
2017-10-12 23:51:18 +00:00
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Requests going via second proxy should succeed.
|
2017-10-12 23:51:18 +00:00
|
|
|
output, err = runCommand(main, []string{"echo", "hello world"}, cfgProxy, 1)
|
2017-10-11 23:36:25 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2017-10-12 23:51:18 +00:00
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
|
|
|
|
2018-02-13 00:28:17 +00:00
|
|
|
// Connect the main proxy back and make sure agents have reconnected over time.
|
|
|
|
// This command is tried 10 times with 250 millisecond delay between each
|
|
|
|
// attempt to allow the discovery request to be received and the connection
|
|
|
|
// added to the agent pool.
|
2017-10-12 23:51:18 +00:00
|
|
|
lb.AddBackend(mainProxyAddr)
|
2019-05-09 00:41:52 +00:00
|
|
|
|
|
|
|
// Once the proxy is added a matching tunnel connection should be created.
|
|
|
|
waitForActiveTunnelConnections(c, main.Tunnel, "cluster-remote", 1)
|
|
|
|
waitForActiveTunnelConnections(c, secondProxy, "cluster-remote", 1)
|
|
|
|
|
|
|
|
// Requests going via main proxy should succeed.
|
Events and GRPC API
This commit introduces several key changes to
Teleport backend and API infrastructure
in order to achieve scalability improvements
on 10K+ node deployments.
Events and plain keyspace
--------------------------
New backend interface supports events,
pagination and range queries
and moves away from buckets to
plain keyspace, what better aligns
with DynamoDB and Etcd featuring similar
interfaces.
All backend implementations are
exposing Events API, allowing
multiple subscribers to consume the same
event stream and avoid polling database.
Replacing BoltDB, Dir with SQLite
-------------------------------
BoltDB backend does not support
having two processes access the database at the
same time. This prevented Teleport
using BoltDB backend to be live reloaded.
SQLite supports reads/writes by multiple
processes and makes Dir backend obsolete
as SQLite is more efficient on larger collections,
supports transactions and can detect data
corruption.
Teleport automatically migrates data from
Bolt and Dir backends into SQLite.
GRPC API and protobuf resources
-------------------------------
GRPC API has been introduced for
the auth server. The auth server now serves both GRPC
and JSON-HTTP API on the same TLS socket and uses
the same client certificate authentication.
All future API methods should use GRPC and HTTP-JSON
API is considered obsolete.
In addition to that some resources like
Server and CertificateAuthority are now
generated from protobuf service specifications in
a way that is fully backward compatible with
original JSON spec and schema, so the same resource
can be encoded and decoded from JSON, YAML
and protobuf.
All models should be refactored
into new proto specification over time.
Streaming presence service
--------------------------
In order to cut bandwidth, nodes
are sending full updates only when changes
to labels or spec have occured, otherwise
new light-weight GRPC keep alive updates are sent
over to the presence service, reducing
bandwidth usage on multi-node deployments.
In addition to that nodes are no longer polling
auth server for certificate authority rotation
updates, instead they subscribe to event updates
to detect updates as soon as they happen.
This is a new API, so the errors are inevitable,
that's why polling is still done, but
on a way slower rate.
2018-11-07 23:33:38 +00:00
|
|
|
output, err = runCommand(main, []string{"echo", "hello world"}, cfg, 40)
|
2017-10-12 23:51:18 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
2017-10-11 23:36:25 +00:00
|
|
|
|
2018-06-15 22:35:02 +00:00
|
|
|
// Stop one of proxies on the main cluster.
|
|
|
|
err = main.StopProxy()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Wait for the remote cluster to detect the outbound connection is gone.
|
2020-04-07 19:05:19 +00:00
|
|
|
c.Assert(waitForProxyCount(remote, "cluster-main", 1), check.IsNil)
|
2018-06-15 22:35:02 +00:00
|
|
|
|
|
|
|
// Stop both clusters and remaining nodes.
|
2020-04-17 16:27:34 +00:00
|
|
|
c.Assert(remote.StopAll(), check.IsNil)
|
|
|
|
c.Assert(main.StopAll(), check.IsNil)
|
2017-10-11 23:36:25 +00:00
|
|
|
}
|
|
|
|
|
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 20:51:59 +00:00
|
|
|
// TestDiscoveryNode makes sure the discovery protocol works with nodes.
|
|
|
|
func (s *IntSuite) TestDiscoveryNode(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
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 20:51:59 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
lib.SetInsecureDevMode(true)
|
|
|
|
defer lib.SetInsecureDevMode(false)
|
|
|
|
|
|
|
|
// Create and start load balancer for proxies.
|
|
|
|
frontend := *utils.MustParseAddr(net.JoinHostPort(Loopback, strconv.Itoa(s.getPorts(1)[0])))
|
|
|
|
lb, err := utils.NewLoadBalancer(context.TODO(), frontend)
|
|
|
|
c.Assert(err, check.IsNil)
|
2019-05-09 00:41:52 +00:00
|
|
|
err = lb.Listen()
|
|
|
|
c.Assert(err, check.IsNil)
|
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 20:51:59 +00:00
|
|
|
go lb.Serve()
|
|
|
|
defer lb.Close()
|
|
|
|
|
|
|
|
// Create a Teleport instance with Auth/Proxy.
|
|
|
|
mainConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
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 20:51:59 +00:00
|
|
|
|
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.TunnelPublicAddrs = []utils.NetAddr{
|
2020-12-07 14:35:15 +00:00
|
|
|
{
|
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 20:51:59 +00:00
|
|
|
AddrNetwork: "tcp",
|
|
|
|
Addr: frontend.String(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = false
|
|
|
|
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
main := s.newTeleportWithConfig(mainConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer main.StopAll()
|
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 20:51:59 +00:00
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Create a Teleport instance with a Proxy.
|
|
|
|
nodePorts := s.getPorts(3)
|
|
|
|
proxyReverseTunnelPort, proxyWebPort, proxySSHPort := nodePorts[0], nodePorts[1], nodePorts[2]
|
|
|
|
proxyConfig := ProxyConfig{
|
|
|
|
Name: "cluster-main-proxy",
|
|
|
|
SSHPort: proxySSHPort,
|
|
|
|
WebPort: proxyWebPort,
|
|
|
|
ReverseTunnelPort: proxyReverseTunnelPort,
|
|
|
|
}
|
|
|
|
proxyTunnel, err := main.StartProxy(proxyConfig)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
proxyOneBackend := utils.MustParseAddr(net.JoinHostPort(Loopback, main.GetPortReverseTunnel()))
|
|
|
|
lb.AddBackend(*proxyOneBackend)
|
|
|
|
proxyTwoBackend := utils.MustParseAddr(net.JoinHostPort(Loopback, strconv.Itoa(proxyReverseTunnelPort)))
|
|
|
|
lb.AddBackend(*proxyTwoBackend)
|
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 20:51:59 +00:00
|
|
|
|
|
|
|
// Create a Teleport instance with a Node.
|
|
|
|
nodeConfig := func() *service.Config {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
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 20:51:59 +00:00
|
|
|
tconf.Hostname = "cluster-main-node"
|
|
|
|
tconf.Token = "token"
|
|
|
|
tconf.AuthServers = []utils.NetAddr{
|
2020-12-07 14:35:15 +00:00
|
|
|
{
|
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 20:51:59 +00:00
|
|
|
AddrNetwork: "tcp",
|
|
|
|
Addr: net.JoinHostPort(Loopback, main.GetPortWeb()),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
tconf.Auth.Enabled = false
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = false
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
_, err = main.StartNode(nodeConfig())
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Wait for active tunnel connections to be established.
|
2019-05-03 04:34:15 +00:00
|
|
|
waitForActiveTunnelConnections(c, main.Tunnel, Site, 1)
|
|
|
|
waitForActiveTunnelConnections(c, proxyTunnel, Site, 1)
|
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 20:51:59 +00:00
|
|
|
|
|
|
|
// Execute the connection via first proxy.
|
|
|
|
cfg := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: "cluster-main-node",
|
|
|
|
}
|
|
|
|
output, err := runCommand(main, []string{"echo", "hello world"}, cfg, 1)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
|
|
|
|
|
|
|
// Execute the connection via second proxy, should work. This command is
|
|
|
|
// tried 10 times with 250 millisecond delay between each attempt to allow
|
|
|
|
// the discovery request to be received and the connection added to the agent
|
|
|
|
// pool.
|
|
|
|
cfgProxy := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: "cluster-main-node",
|
2019-04-30 01:19:22 +00:00
|
|
|
Proxy: &proxyConfig,
|
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 20:51:59 +00:00
|
|
|
}
|
2019-05-14 22:16:27 +00:00
|
|
|
|
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 20:51:59 +00:00
|
|
|
output, err = runCommand(main, []string{"echo", "hello world"}, cfgProxy, 10)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Remove second proxy from LB.
|
2020-04-07 19:05:19 +00:00
|
|
|
c.Assert(lb.RemoveBackend(*proxyTwoBackend), check.IsNil)
|
2019-05-09 21:34:32 +00:00
|
|
|
waitForActiveTunnelConnections(c, main.Tunnel, Site, 1)
|
2019-04-30 01:19:22 +00:00
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Requests going via main proxy will succeed. Requests going via second
|
|
|
|
// proxy will fail.
|
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 20:51:59 +00:00
|
|
|
output, err = runCommand(main, []string{"echo", "hello world"}, cfg, 1)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
Fix staticcheck findings in integration/ and tool/
Original finding list:
```
tool/tctl/common/node_command.go:163:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
tool/tctl/common/status_command.go:110:2: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(view())
^
tool/tctl/common/status_command.go:126:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(view())
^
tool/tctl/common/token_command.go:201:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(tokensView())
^
tool/tctl/common/token_command.go:207:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(data))
^
tool/tctl/common/user_command.go:248:2: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
tool/tctl/common/user_command.go:294:3: SA1006: printf-style function with dynamic format string and no further arguments should use print-style function instead (staticcheck)
fmt.Printf(string(out))
^
integration/helpers.go:200:2: SA4006: this value of `err` is never used (staticcheck)
cryptoPubKey, err := sshutils.CryptoPublicKey(cfg.Pub)
^
integration/helpers.go:399:3: SA4006: this value of `roles` is never used (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:597:4: SA4006: this value of `roles` is never used (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:599:4: SA4006: this value of `roles` is never used (staticcheck)
roles = user.Roles
^
integration/integration_test.go:1625:2: SA4006: this value of `err` is never used (staticcheck)
adminsRole, err := services.NewRole(mainAdmins, services.RoleSpecV3{
^
integration/integration_test.go:2185:2: SA4006: this value of `output` is never used (staticcheck)
output, err = runCommand(main, []string{"echo", "hello world"}, cfg, 1)
^
integration/integration_test.go:2340:2: SA4006: this value of `output` is never used (staticcheck)
output, err = runCommand(main, []string{"echo", "hello world"}, cfgProxy, 1)
^
integration/kube_integration_test.go:154:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:321:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:366:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
integration/kube_integration_test.go:386:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := s.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:465:2: SA4006: this value of `err` is never used (staticcheck)
mainRole, err := services.NewRole("main-kube", services.RoleSpecV3{
^
integration/kube_integration_test.go:579:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := proxyClient.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:727:2: SA4006: this value of `err` is never used (staticcheck)
mainRole, err := services.NewRole("main-kube", services.RoleSpecV3{
^
integration/kube_integration_test.go:840:2: SA4006: this value of `err` is never used (staticcheck)
pods, err := proxyClient.CoreV1().Pods(kubeSystemNamespace).List(metav1.ListOptions{
^
integration/kube_integration_test.go:1008:2: SA4006: this value of `err` is never used (staticcheck)
role, err := services.NewRole("kubemaster", services.RoleSpecV3{
^
tool/teleport/common/teleport_test.go:83:2: SA4006: this value of `cmd` is never used (staticcheck)
cmd, conf := Run(Options{
^
tool/teleport/common/teleport_test.go:91:2: SA4006: this value of `cmd` is never used (staticcheck)
cmd, conf = Run(Options{
^
tool/tsh/tsh.go:170:2: SA4006: this value of `cmdLine` is never used (staticcheck)
cmdLine := []string{}
^
integration/helpers.go:399:11: SA4010: this result of append is never used, except maybe in other appends (staticcheck)
roles = append(roles, role)
^
integration/helpers.go:597:12: SA4010: this result of append is never used, except maybe in other appends (staticcheck)
roles = append(roles, role)
^
integration/integration_test.go:1092:7: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(b.Tunnel.GetSites()) < 2 && len(b.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1426:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1691:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/integration_test.go:1895:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/kube_integration_test.go:548:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
integration/kube_integration_test.go:814:6: SA4000: identical expressions on the left and right side of the '&&' operator (staticcheck)
for len(main.Tunnel.GetSites()) < 2 && len(main.Tunnel.GetSites()) < 2 {
^
```
2020-04-27 17:39:58 +00:00
|
|
|
_, err = runCommand(main, []string{"echo", "hello world"}, cfgProxy, 1)
|
2019-05-09 00:41:52 +00:00
|
|
|
c.Assert(err, check.NotNil)
|
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 20:51:59 +00:00
|
|
|
|
2019-05-14 22:16:27 +00:00
|
|
|
// Add second proxy to LB, both should have a connection.
|
2019-05-09 00:41:52 +00:00
|
|
|
lb.AddBackend(*proxyTwoBackend)
|
|
|
|
waitForActiveTunnelConnections(c, main.Tunnel, Site, 1)
|
2019-05-09 21:34:32 +00:00
|
|
|
waitForActiveTunnelConnections(c, proxyTunnel, Site, 1)
|
2019-04-30 01:19:22 +00:00
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Requests going via both proxies will succeed.
|
|
|
|
output, err = runCommand(main, []string{"echo", "hello world"}, cfg, 1)
|
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 20:51:59 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
2019-05-09 00:41:52 +00:00
|
|
|
output, err = runCommand(main, []string{"echo", "hello world"}, cfgProxy, 40)
|
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 20:51:59 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2019-05-09 00:41:52 +00:00
|
|
|
c.Assert(output, check.Equals, "hello world\n")
|
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 20:51:59 +00:00
|
|
|
|
2019-05-09 00:41:52 +00:00
|
|
|
// Stop everything.
|
|
|
|
err = proxyTunnel.Shutdown(context.Background())
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-04-17 16:27:34 +00:00
|
|
|
err = main.StopAll()
|
2019-05-09 00:41:52 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
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 20:51:59 +00:00
|
|
|
}
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
// waitForActiveTunnelConnections waits for remote cluster to report a minimum number of active connections
|
2019-05-03 04:34:15 +00:00
|
|
|
func waitForActiveTunnelConnections(c *check.C, tunnel reversetunnel.Server, clusterName string, expectedCount int) {
|
2018-12-12 00:22:44 +00:00
|
|
|
var lastCount int
|
|
|
|
var lastErr error
|
2019-05-09 00:41:52 +00:00
|
|
|
for i := 0; i < 30; i++ {
|
2019-05-03 04:34:15 +00:00
|
|
|
cluster, err := tunnel.GetSite(clusterName)
|
2018-12-12 00:22:44 +00:00
|
|
|
if err != nil {
|
|
|
|
lastErr = err
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
lastCount = cluster.GetTunnelsCount()
|
|
|
|
if lastCount >= expectedCount {
|
|
|
|
return
|
|
|
|
}
|
2019-05-09 00:41:52 +00:00
|
|
|
time.Sleep(1 * time.Second)
|
2018-12-12 00:22:44 +00:00
|
|
|
}
|
|
|
|
c.Fatalf("Connections count on %v: %v, expected %v, last error: %v", clusterName, lastCount, expectedCount, lastErr)
|
|
|
|
}
|
|
|
|
|
2018-06-15 22:35:02 +00:00
|
|
|
// waitForProxyCount waits a set time for the proxy count in clusterName to
|
|
|
|
// reach some value.
|
|
|
|
func waitForProxyCount(t *TeleInstance, clusterName string, count int) error {
|
|
|
|
var counts map[string]int
|
2020-04-07 19:05:19 +00:00
|
|
|
start := time.Now()
|
|
|
|
for time.Since(start) < 17*time.Second {
|
2020-09-17 15:07:44 +00:00
|
|
|
counts = t.RemoteClusterWatcher.Counts()
|
2018-06-15 22:35:02 +00:00
|
|
|
if counts[clusterName] == count {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-07 19:05:19 +00:00
|
|
|
time.Sleep(500 * time.Millisecond)
|
2018-06-15 22:35:02 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 19:05:19 +00:00
|
|
|
return trace.BadParameter("proxy count on %v: %v (wanted %v)", clusterName, counts[clusterName], count)
|
2018-06-15 22:35:02 +00:00
|
|
|
}
|
|
|
|
|
2019-04-30 01:19:22 +00:00
|
|
|
// waitForNodeCount waits for a certain number of nodes to show up in the remote site.
|
|
|
|
func waitForNodeCount(t *TeleInstance, clusterName string, count int) error {
|
|
|
|
for i := 0; i < 30; i++ {
|
|
|
|
remoteSite, err := t.Tunnel.GetSite(clusterName)
|
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
accessPoint, err := remoteSite.CachingAccessPoint()
|
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
nodes, err := accessPoint.GetNodes(defaults.Namespace)
|
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
if len(nodes) == count {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
return trace.BadParameter("did not find %v nodes", count)
|
|
|
|
}
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
// waitForTunnelConnections waits for remote tunnels connections
|
2020-10-05 22:12:25 +00:00
|
|
|
func waitForTunnelConnections(c *check.C, authServer *auth.Server, clusterName string, expectedCount int) {
|
2018-12-12 00:22:44 +00:00
|
|
|
var conns []services.TunnelConnection
|
2019-04-30 01:19:22 +00:00
|
|
|
for i := 0; i < 30; i++ {
|
2018-12-12 00:22:44 +00:00
|
|
|
conns, err := authServer.Presence.GetTunnelConnections(clusterName)
|
|
|
|
if err != nil {
|
|
|
|
c.Fatal(err)
|
|
|
|
}
|
2019-04-30 01:19:22 +00:00
|
|
|
if len(conns) == expectedCount {
|
2018-12-12 00:22:44 +00:00
|
|
|
return
|
|
|
|
}
|
2019-04-30 01:19:22 +00:00
|
|
|
time.Sleep(1 * time.Second)
|
2018-12-12 00:22:44 +00:00
|
|
|
}
|
|
|
|
c.Fatalf("proxy count on %v: %v, expected %v", clusterName, len(conns), expectedCount)
|
|
|
|
}
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// TestExternalClient tests if we can connect to a node in a Teleport
|
|
|
|
// cluster. Both normal and recording proxies are tested.
|
|
|
|
func (s *IntSuite) TestExternalClient(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2018-03-30 21:42:08 +00:00
|
|
|
// Only run this test if we have access to the external SSH binary.
|
2017-12-13 02:17:18 +00:00
|
|
|
_, err := exec.LookPath("ssh")
|
|
|
|
if err != nil {
|
|
|
|
c.Skip("Skipping TestExternalClient, no external SSH binary found.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
inRecordLocation string
|
|
|
|
inForwardAgent bool
|
|
|
|
inCommand string
|
|
|
|
outError bool
|
|
|
|
outExecOutput string
|
|
|
|
}{
|
2018-03-30 21:42:08 +00:00
|
|
|
// Record at the node, forward agent. Will still work even though the agent
|
2017-12-13 02:17:18 +00:00
|
|
|
// will be rejected by the proxy (agent forwarding request rejection is a
|
2018-03-30 21:42:08 +00:00
|
|
|
// soft failure).
|
2017-12-13 02:17:18 +00:00
|
|
|
{
|
2018-03-30 21:42:08 +00:00
|
|
|
inRecordLocation: services.RecordAtNode,
|
|
|
|
inForwardAgent: true,
|
|
|
|
inCommand: "echo hello",
|
|
|
|
outError: false,
|
|
|
|
outExecOutput: "hello",
|
2017-12-13 02:17:18 +00:00
|
|
|
},
|
2018-03-30 21:42:08 +00:00
|
|
|
// Record at the node, don't forward agent, will work. This is the normal
|
|
|
|
// Teleport mode of operation.
|
2017-12-13 02:17:18 +00:00
|
|
|
{
|
2018-03-30 21:42:08 +00:00
|
|
|
inRecordLocation: services.RecordAtNode,
|
|
|
|
inForwardAgent: false,
|
|
|
|
inCommand: "echo hello",
|
|
|
|
outError: false,
|
|
|
|
outExecOutput: "hello",
|
2017-12-13 02:17:18 +00:00
|
|
|
},
|
2018-03-30 21:42:08 +00:00
|
|
|
// Record at the proxy, forward agent. Will work.
|
2017-12-13 02:17:18 +00:00
|
|
|
{
|
2018-03-30 21:42:08 +00:00
|
|
|
inRecordLocation: services.RecordAtProxy,
|
|
|
|
inForwardAgent: true,
|
|
|
|
inCommand: "echo hello",
|
|
|
|
outError: false,
|
|
|
|
outExecOutput: "hello",
|
2017-12-13 02:17:18 +00:00
|
|
|
},
|
2018-03-30 21:42:08 +00:00
|
|
|
// Record at the proxy, don't forward agent, request will fail because
|
2017-12-13 02:17:18 +00:00
|
|
|
// recording proxy requires an agent.
|
|
|
|
{
|
2018-03-30 21:42:08 +00:00
|
|
|
inRecordLocation: services.RecordAtProxy,
|
|
|
|
inForwardAgent: false,
|
|
|
|
inCommand: "echo hello",
|
|
|
|
outError: true,
|
|
|
|
outExecOutput: "",
|
2017-12-13 02:17:18 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
2018-03-30 21:42:08 +00:00
|
|
|
// Create a Teleport instance with auth, proxy, and node.
|
2017-12-13 02:17:18 +00:00
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: tt.inRecordLocation,
|
2019-02-21 01:47:50 +00:00
|
|
|
LocalAuth: services.NewBool(true),
|
2017-12-13 02:17:18 +00:00
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2017-12-13 02:17:18 +00:00
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = true
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
t := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2017-12-13 02:17:18 +00:00
|
|
|
|
2018-03-30 21:42:08 +00:00
|
|
|
// Start (and defer close) a agent that runs during this integration test.
|
2017-12-13 02:17:18 +00:00
|
|
|
teleAgent, socketDirPath, socketPath, err := createAgent(
|
|
|
|
s.me,
|
|
|
|
t.Secrets.Users[s.me.Username].Key.Priv,
|
|
|
|
t.Secrets.Users[s.me.Username].Key.Cert)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
defer closeAgent(teleAgent, socketDirPath)
|
|
|
|
|
2018-03-30 21:42:08 +00:00
|
|
|
// Create a *exec.Cmd that will execute the external SSH command.
|
|
|
|
execCmd, err := externalSSHCommand(commandOptions{
|
|
|
|
forwardAgent: tt.inForwardAgent,
|
|
|
|
socketPath: socketPath,
|
|
|
|
proxyPort: t.GetPortProxy(),
|
|
|
|
nodePort: t.GetPortSSH(),
|
|
|
|
command: tt.inCommand,
|
|
|
|
})
|
2017-12-13 02:17:18 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2018-03-30 21:42:08 +00:00
|
|
|
// Execute SSH command and check the output is what we expect.
|
2017-12-13 02:17:18 +00:00
|
|
|
output, err := execCmd.Output()
|
|
|
|
if tt.outError {
|
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
} else {
|
2018-03-30 21:42:08 +00:00
|
|
|
if err != nil {
|
|
|
|
// If an *exec.ExitError is returned, parse it and return stderr. If this
|
|
|
|
// is not done then c.Assert will just print a byte array for the error.
|
|
|
|
er, ok := err.(*exec.ExitError)
|
|
|
|
if ok {
|
|
|
|
c.Fatalf("Unexpected error: %v", string(er.Stderr))
|
|
|
|
}
|
|
|
|
}
|
2017-12-13 02:17:18 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(strings.TrimSpace(string(output)), check.Equals, tt.outExecOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-30 21:42:08 +00:00
|
|
|
// TestControlMaster checks if multiple SSH channels can be created over the
|
|
|
|
// same connection. This is frequently used by tools like Ansible.
|
|
|
|
func (s *IntSuite) TestControlMaster(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2018-03-30 21:42:08 +00:00
|
|
|
// Only run this test if we have access to the external SSH binary.
|
|
|
|
_, err := exec.LookPath("ssh")
|
|
|
|
if err != nil {
|
|
|
|
c.Skip("Skipping TestControlMaster, no external SSH binary found.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
inRecordLocation string
|
|
|
|
}{
|
|
|
|
// Run tests when Teleport is recording sessions at the node.
|
|
|
|
{
|
|
|
|
inRecordLocation: services.RecordAtNode,
|
|
|
|
},
|
|
|
|
// Run tests when Teleport is recording sessions at the proxy.
|
|
|
|
{
|
|
|
|
inRecordLocation: services.RecordAtProxy,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
controlDir, err := ioutil.TempDir("", "teleport-")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
defer os.RemoveAll(controlDir)
|
|
|
|
controlPath := filepath.Join(controlDir, "control-path")
|
|
|
|
|
|
|
|
// Create a Teleport instance with auth, proxy, and node.
|
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: tt.inRecordLocation,
|
2019-02-21 01:47:50 +00:00
|
|
|
LocalAuth: services.NewBool(true),
|
2018-03-30 21:42:08 +00:00
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2018-03-30 21:42:08 +00:00
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = true
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
t := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2018-03-30 21:42:08 +00:00
|
|
|
|
|
|
|
// Start (and defer close) a agent that runs during this integration test.
|
|
|
|
teleAgent, socketDirPath, socketPath, err := createAgent(
|
|
|
|
s.me,
|
|
|
|
t.Secrets.Users[s.me.Username].Key.Priv,
|
|
|
|
t.Secrets.Users[s.me.Username].Key.Cert)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
defer closeAgent(teleAgent, socketDirPath)
|
|
|
|
|
|
|
|
// Create and run an exec command twice with the passed in ControlPath. This
|
|
|
|
// will cause re-use of the connection and creation of two sessions within
|
|
|
|
// the connection.
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
execCmd, err := externalSSHCommand(commandOptions{
|
|
|
|
forcePTY: true,
|
|
|
|
forwardAgent: true,
|
|
|
|
controlPath: controlPath,
|
|
|
|
socketPath: socketPath,
|
|
|
|
proxyPort: t.GetPortProxy(),
|
|
|
|
nodePort: t.GetPortSSH(),
|
|
|
|
command: "echo hello",
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Execute SSH command and check the output is what we expect.
|
|
|
|
output, err := execCmd.Output()
|
|
|
|
if err != nil {
|
|
|
|
// If an *exec.ExitError is returned, parse it and return stderr. If this
|
|
|
|
// is not done then c.Assert will just print a byte array for the error.
|
|
|
|
er, ok := err.(*exec.ExitError)
|
|
|
|
if ok {
|
|
|
|
c.Fatalf("Unexpected error: %v", string(er.Stderr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(strings.TrimSpace(string(output)), check.Equals, "hello")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
// TestProxyHostKeyCheck uses the forwarding proxy to connect to a server that
|
|
|
|
// presents a host key instead of a certificate in different configurations
|
|
|
|
// for the host key checking parameter in services.ClusterConfig.
|
|
|
|
func (s *IntSuite) TestProxyHostKeyCheck(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
var tests = []struct {
|
|
|
|
inHostKeyCheck string
|
|
|
|
outError bool
|
|
|
|
}{
|
|
|
|
// disable host key checking, should be able to connect
|
|
|
|
{
|
|
|
|
services.HostKeyCheckNo,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
// enable host key checking, should NOT be able to connect
|
|
|
|
{
|
|
|
|
services.HostKeyCheckYes,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
hostSigner, err := ssh.ParsePrivateKey(s.priv)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// start a ssh server that presents a host key instead of a certificate
|
|
|
|
nodePort := s.getPorts(1)[0]
|
|
|
|
sshNode, err := newDiscardServer(Host, nodePort, hostSigner)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = sshNode.Start()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
defer sshNode.Stop()
|
|
|
|
|
|
|
|
// create a teleport instance with auth, proxy, and node
|
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: services.RecordAtProxy,
|
|
|
|
ProxyChecksHostKeys: tt.inHostKeyCheck,
|
2019-02-21 01:47:50 +00:00
|
|
|
LocalAuth: services.NewBool(true),
|
2017-12-13 02:17:18 +00:00
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2017-12-13 02:17:18 +00:00
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = true
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
t := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2017-12-13 02:17:18 +00:00
|
|
|
|
|
|
|
// create a teleport client and exec a command
|
|
|
|
clientConfig := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: nodePort,
|
|
|
|
ForwardAgent: true,
|
|
|
|
}
|
|
|
|
_, err = runCommand(t, []string{"echo hello"}, clientConfig, 1)
|
|
|
|
|
|
|
|
// check if we were able to exec the command or not
|
|
|
|
if tt.outError {
|
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
} else {
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestAuditOff checks that when session recording has been turned off,
|
|
|
|
// sessions are not recorded.
|
|
|
|
func (s *IntSuite) TestAuditOff(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2017-12-13 02:17:18 +00:00
|
|
|
var err error
|
|
|
|
|
|
|
|
// create a teleport instance with auth, proxy, and node
|
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: services.RecordOff,
|
2019-02-21 01:47:50 +00:00
|
|
|
LocalAuth: services.NewBool(true),
|
2017-12-13 02:17:18 +00:00
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2017-12-13 02:17:18 +00:00
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = true
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
t := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2017-12-13 02:17:18 +00:00
|
|
|
|
|
|
|
// get access to a authClient for the cluster
|
|
|
|
site := t.GetSiteAPI(Site)
|
|
|
|
c.Assert(site, check.NotNil)
|
|
|
|
|
|
|
|
// should have no sessions in it to start with
|
|
|
|
sessions, _ := site.GetSessions(defaults.Namespace)
|
|
|
|
c.Assert(len(sessions), check.Equals, 0)
|
|
|
|
|
|
|
|
// create interactive session (this goroutine is this user's terminal time)
|
|
|
|
endCh := make(chan error, 1)
|
|
|
|
|
|
|
|
myTerm := NewTerminal(250)
|
|
|
|
go func() {
|
|
|
|
cl, err := t.NewClient(ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: t.GetPortSSHInt(),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-04-17 15:55:48 +00:00
|
|
|
cl.Stdout = myTerm
|
|
|
|
cl.Stdin = myTerm
|
2017-12-13 02:17:18 +00:00
|
|
|
err = cl.SSH(context.TODO(), []string{}, false)
|
|
|
|
endCh <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
// wait until there's a session in there:
|
|
|
|
for i := 0; len(sessions) == 0; i++ {
|
|
|
|
time.Sleep(time.Millisecond * 20)
|
|
|
|
sessions, _ = site.GetSessions(defaults.Namespace)
|
|
|
|
if i > 100 {
|
|
|
|
c.Fatalf("Waited %v, but no sessions found", 100*20*time.Millisecond)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
session := &sessions[0]
|
|
|
|
|
|
|
|
// wait for the user to join this session
|
|
|
|
for len(session.Parties) == 0 {
|
|
|
|
time.Sleep(time.Millisecond * 5)
|
|
|
|
session, err = site.GetSession(defaults.Namespace, sessions[0].ID)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
// make sure it's us who joined! :)
|
|
|
|
c.Assert(session.Parties[0].User, check.Equals, s.me.Username)
|
|
|
|
|
|
|
|
// lets type "echo hi" followed by "enter" and then "exit" + "enter":
|
|
|
|
myTerm.Type("\aecho hi\n\r\aexit\n\r\a")
|
|
|
|
|
|
|
|
// wait for session to end
|
|
|
|
select {
|
|
|
|
case <-time.After(1 * time.Minute):
|
|
|
|
c.Fatalf("Timed out waiting for session to end.")
|
|
|
|
case <-endCh:
|
|
|
|
}
|
|
|
|
|
2018-11-15 19:14:20 +00:00
|
|
|
// audit log should have the fact that the session occurred recorded in it
|
2017-12-21 01:04:46 +00:00
|
|
|
sessions, err = site.GetSessions(defaults.Namespace)
|
|
|
|
c.Assert(err, check.IsNil)
|
2017-12-13 02:17:18 +00:00
|
|
|
c.Assert(len(sessions), check.Equals, 1)
|
|
|
|
|
|
|
|
// however, attempts to read the actual sessions should fail because it was
|
|
|
|
// not actually recorded
|
|
|
|
_, err = site.GetSessionChunk(defaults.Namespace, session.ID, 0, events.MaxChunkBytes)
|
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
}
|
|
|
|
|
2018-02-24 01:23:09 +00:00
|
|
|
// TestPAM checks that Teleport PAM integration works correctly. In this case
|
|
|
|
// that means if the account and session modules return success, the user
|
|
|
|
// should be allowed to log in. If either the account or session module does
|
|
|
|
// not return success, the user should not be able to log in.
|
|
|
|
func (s *IntSuite) TestPAM(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2020-07-10 20:28:33 +00:00
|
|
|
// Check if TestPAM can run. For PAM tests to run, the binary must have
|
|
|
|
// been built with PAM support and the system running the tests must have
|
|
|
|
// libpam installed, and have the policy files installed. This test is
|
|
|
|
// always run in a container as part of the CI/CD pipeline. To run this
|
|
|
|
// test locally, install the pam_teleport.so module by running 'sudo make
|
|
|
|
// install' from the build.assets/pam/ directory. This will install the PAM
|
|
|
|
// module as well as the policy files.
|
2018-02-24 01:23:09 +00:00
|
|
|
if !pam.BuildHasPAM() || !pam.SystemHasPAM() || !hasPAMPolicy() {
|
|
|
|
skipMessage := "Skipping TestPAM: no policy found. To run PAM tests run " +
|
2020-07-10 20:28:33 +00:00
|
|
|
"'sudo make install' from the build.assets/pam/ directory."
|
2018-02-24 01:23:09 +00:00
|
|
|
c.Skip(skipMessage)
|
|
|
|
}
|
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
inEnabled bool
|
|
|
|
inServiceName string
|
2020-10-16 20:54:51 +00:00
|
|
|
inUsePAMAuth bool
|
2018-02-24 01:23:09 +00:00
|
|
|
outContains []string
|
|
|
|
}{
|
|
|
|
// 0 - No PAM support, session should work but no PAM related output.
|
|
|
|
{
|
|
|
|
inEnabled: false,
|
|
|
|
inServiceName: "",
|
2020-10-16 20:54:51 +00:00
|
|
|
inUsePAMAuth: true,
|
2018-02-24 01:23:09 +00:00
|
|
|
outContains: []string{},
|
|
|
|
},
|
|
|
|
// 1 - PAM enabled, module account and session functions return success.
|
|
|
|
{
|
|
|
|
inEnabled: true,
|
|
|
|
inServiceName: "teleport-success",
|
2020-10-16 20:54:51 +00:00
|
|
|
inUsePAMAuth: true,
|
2018-02-24 01:23:09 +00:00
|
|
|
outContains: []string{
|
2020-07-10 20:28:33 +00:00
|
|
|
"pam_sm_acct_mgmt OK",
|
|
|
|
"pam_sm_authenticate OK",
|
|
|
|
"pam_sm_open_session OK",
|
|
|
|
"pam_sm_close_session OK",
|
2018-02-24 01:23:09 +00:00
|
|
|
},
|
|
|
|
},
|
2020-10-16 20:54:51 +00:00
|
|
|
// 2 - PAM enabled, module account and session functions return success.
|
|
|
|
{
|
|
|
|
inEnabled: true,
|
|
|
|
inServiceName: "teleport-success",
|
|
|
|
inUsePAMAuth: false,
|
|
|
|
outContains: []string{
|
|
|
|
"pam_sm_acct_mgmt OK",
|
|
|
|
"pam_sm_open_session OK",
|
|
|
|
"pam_sm_close_session OK",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// 3 - PAM enabled, module account functions fail.
|
2018-02-24 01:23:09 +00:00
|
|
|
{
|
|
|
|
inEnabled: true,
|
|
|
|
inServiceName: "teleport-acct-failure",
|
2020-10-16 20:54:51 +00:00
|
|
|
inUsePAMAuth: true,
|
2018-02-24 01:23:09 +00:00
|
|
|
outContains: []string{},
|
|
|
|
},
|
2020-10-16 20:54:51 +00:00
|
|
|
// 4 - PAM enabled, module session functions fail.
|
2018-02-24 01:23:09 +00:00
|
|
|
{
|
|
|
|
inEnabled: true,
|
|
|
|
inServiceName: "teleport-session-failure",
|
2020-10-16 20:54:51 +00:00
|
|
|
inUsePAMAuth: true,
|
2018-02-24 01:23:09 +00:00
|
|
|
outContains: []string{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
// Create a teleport instance with auth, proxy, and node.
|
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2018-02-24 01:23:09 +00:00
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = true
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.SSH.PAM.Enabled = tt.inEnabled
|
|
|
|
tconf.SSH.PAM.ServiceName = tt.inServiceName
|
2020-10-16 20:54:51 +00:00
|
|
|
tconf.SSH.PAM.UsePAMAuth = tt.inUsePAMAuth
|
2018-02-24 01:23:09 +00:00
|
|
|
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
t := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2018-02-24 01:23:09 +00:00
|
|
|
|
|
|
|
termSession := NewTerminal(250)
|
|
|
|
|
|
|
|
// Create an interactive session and write something to the terminal.
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
go func() {
|
|
|
|
cl, err := t.NewClient(ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: t.GetPortSSHInt(),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-04-17 15:55:48 +00:00
|
|
|
cl.Stdout = termSession
|
|
|
|
cl.Stdin = termSession
|
2018-02-24 01:23:09 +00:00
|
|
|
|
|
|
|
termSession.Type("\aecho hi\n\r\aexit\n\r\a")
|
|
|
|
err = cl.SSH(context.TODO(), []string{}, false)
|
2019-11-16 00:39:40 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2018-02-24 01:23:09 +00:00
|
|
|
|
|
|
|
cancel()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait for the session to end or timeout after 10 seconds.
|
|
|
|
select {
|
|
|
|
case <-time.After(10 * time.Second):
|
2020-12-07 14:35:15 +00:00
|
|
|
dumpGoroutineProfile()
|
2018-02-24 01:23:09 +00:00
|
|
|
c.Fatalf("Timeout exceeded waiting for session to complete.")
|
|
|
|
case <-ctx.Done():
|
|
|
|
}
|
|
|
|
|
|
|
|
// If any output is expected, check to make sure it was output.
|
2019-11-16 00:39:40 +00:00
|
|
|
if len(tt.outContains) > 0 {
|
|
|
|
for _, expectedOutput := range tt.outContains {
|
2020-07-10 20:28:33 +00:00
|
|
|
output := termSession.Output(1024)
|
|
|
|
c.Logf("got output: %q; want output to contain: %q", output, expectedOutput)
|
2019-11-16 00:39:40 +00:00
|
|
|
c.Assert(strings.Contains(output, expectedOutput), check.Equals, true)
|
|
|
|
}
|
2018-02-24 01:23:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
// TestRotateSuccess tests full cycle cert authority rotation
|
|
|
|
func (s *IntSuite) TestRotateSuccess(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
2018-05-07 21:44:46 +00:00
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
t := s.newTeleportInstance(c)
|
|
|
|
defer t.StopAll()
|
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
logins := []string{s.me.Username}
|
|
|
|
for _, login := range logins {
|
|
|
|
t.AddUser(login, []string{login})
|
|
|
|
}
|
2020-12-07 14:35:15 +00:00
|
|
|
|
|
|
|
tconf := s.rotationConfig(true)
|
2018-04-08 21:37:33 +00:00
|
|
|
config, err := t.GenerateConfig(nil, tconf)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
serviceC := make(chan *service.TeleportProcess, 20)
|
|
|
|
|
2020-06-02 00:06:46 +00:00
|
|
|
runErrCh := make(chan error, 1)
|
2018-04-08 21:37:33 +00:00
|
|
|
go func() {
|
2020-06-02 00:06:46 +00:00
|
|
|
runErrCh <- service.Run(ctx, *config, func(cfg *service.Config) (service.Process, error) {
|
2018-04-08 21:37:33 +00:00
|
|
|
svc, err := service.NewTeleport(cfg)
|
|
|
|
if err == nil {
|
|
|
|
serviceC <- svc
|
|
|
|
}
|
|
|
|
return svc, err
|
|
|
|
})
|
|
|
|
}()
|
|
|
|
|
2018-05-07 21:44:46 +00:00
|
|
|
svc, err := waitForProcessStart(serviceC)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
defer svc.Shutdown(context.TODO())
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// Setup user in the cluster
|
|
|
|
err = SetupUser(svc, s.me.Username, nil)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// capture credentials before reload started to simulate old client
|
2019-07-22 23:58:38 +00:00
|
|
|
initialCreds, err := GenerateUserCreds(UserCredsRequest{Process: svc, Username: s.me.Username})
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service started. Setting rotation state to %v", services.RotationPhaseUpdateClients)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// start rotation
|
2018-05-07 21:44:46 +00:00
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseInit,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
hostCA, err := svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.HostCA, DomainName: Site}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Cert authority: %v", auth.CertAuthorityInfo(hostCA))
|
2018-12-12 00:22:44 +00:00
|
|
|
|
2018-05-07 21:44:46 +00:00
|
|
|
// wait until service phase update to be broadcasted (init phase does not trigger reload)
|
|
|
|
err = waitForProcessEvent(svc, service.TeleportPhaseChangeEvent, 10*time.Second)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// update clients
|
2018-04-08 21:37:33 +00:00
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseUpdateClients,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reload
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
defer svc.Shutdown(context.TODO())
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
cfg := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
2018-11-20 01:36:19 +00:00
|
|
|
Host: Loopback,
|
2018-04-08 21:37:33 +00:00
|
|
|
Port: t.GetPortSSHInt(),
|
|
|
|
}
|
|
|
|
clt, err := t.NewClientWithCreds(cfg, *initialCreds)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// client works as is before servers have been rotated
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service reloaded. Setting rotation state to %v", services.RotationPhaseUpdateServers)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// move to the next phase
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseUpdateServers,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
hostCA, err = svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.HostCA, DomainName: Site}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Cert authority: %v", auth.CertAuthorityInfo(hostCA))
|
2018-12-12 00:22:44 +00:00
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
defer svc.Shutdown(context.TODO())
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// new credentials will work from this phase to others
|
2019-07-22 23:58:38 +00:00
|
|
|
newCreds, err := GenerateUserCreds(UserCredsRequest{Process: svc, Username: s.me.Username})
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
clt, err = t.NewClientWithCreds(cfg, *newCreds)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// new client works
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service reloaded. Setting rotation state to %v.", services.RotationPhaseStandby)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// complete rotation
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseStandby,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
hostCA, err = svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.HostCA, DomainName: Site}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Cert authority: %v", auth.CertAuthorityInfo(hostCA))
|
2018-12-12 00:22:44 +00:00
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
defer svc.Shutdown(context.TODO())
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// new client still works
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service reloaded. Rotation has completed. Shuttting down service.")
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// shut down the service
|
|
|
|
cancel()
|
|
|
|
// close the service without waiting for the connections to drain
|
|
|
|
svc.Close()
|
|
|
|
|
|
|
|
select {
|
2020-06-02 00:06:46 +00:00
|
|
|
case err := <-runErrCh:
|
|
|
|
c.Assert(err, check.IsNil)
|
2018-04-08 21:37:33 +00:00
|
|
|
case <-time.After(20 * time.Second):
|
|
|
|
c.Fatalf("failed to shut down the server")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestRotateRollback tests cert authority rollback
|
|
|
|
func (s *IntSuite) TestRotateRollback(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
2018-05-07 21:44:46 +00:00
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.rotationConfig(true)
|
|
|
|
t := s.newTeleportInstance(c)
|
2018-04-08 21:37:33 +00:00
|
|
|
logins := []string{s.me.Username}
|
|
|
|
for _, login := range logins {
|
|
|
|
t.AddUser(login, []string{login})
|
|
|
|
}
|
|
|
|
config, err := t.GenerateConfig(nil, tconf)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
serviceC := make(chan *service.TeleportProcess, 20)
|
|
|
|
|
2020-06-02 00:06:46 +00:00
|
|
|
runErrCh := make(chan error, 1)
|
2018-04-08 21:37:33 +00:00
|
|
|
go func() {
|
2020-06-02 00:06:46 +00:00
|
|
|
runErrCh <- service.Run(ctx, *config, func(cfg *service.Config) (service.Process, error) {
|
2018-04-08 21:37:33 +00:00
|
|
|
svc, err := service.NewTeleport(cfg)
|
|
|
|
if err == nil {
|
|
|
|
serviceC <- svc
|
|
|
|
}
|
|
|
|
return svc, err
|
|
|
|
})
|
|
|
|
}()
|
|
|
|
|
2018-05-07 21:44:46 +00:00
|
|
|
svc, err := waitForProcessStart(serviceC)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Setup user in the cluster
|
|
|
|
err = SetupUser(svc, s.me.Username, nil)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// capture credentials before reload started to simulate old client
|
2019-07-22 23:58:38 +00:00
|
|
|
initialCreds, err := GenerateUserCreds(UserCredsRequest{Process: svc, Username: s.me.Username})
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service started. Setting rotation state to %q.", services.RotationPhaseInit)
|
2018-05-07 21:44:46 +00:00
|
|
|
|
|
|
|
// start rotation
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseInit,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
err = waitForProcessEvent(svc, service.TeleportPhaseChangeEvent, 10*time.Second)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Setting rotation state to %q.", services.RotationPhaseUpdateClients)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// start rotation
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseUpdateClients,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reload
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
cfg := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
2018-11-20 01:36:19 +00:00
|
|
|
Host: Loopback,
|
2018-04-08 21:37:33 +00:00
|
|
|
Port: t.GetPortSSHInt(),
|
|
|
|
}
|
|
|
|
clt, err := t.NewClientWithCreds(cfg, *initialCreds)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// client works as is before servers have been rotated
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service reloaded. Setting rotation state to %q.", services.RotationPhaseUpdateServers)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// move to the next phase
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseUpdateServers,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service reloaded. Setting rotation state to %q.", services.RotationPhaseRollback)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// complete rotation
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseRollback,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// old client works
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Log("Service reloaded. Rotation has completed. Shuttting down service.")
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// shut down the service
|
|
|
|
cancel()
|
|
|
|
// close the service without waiting for the connections to drain
|
|
|
|
svc.Close()
|
|
|
|
|
|
|
|
select {
|
2020-06-02 00:06:46 +00:00
|
|
|
case err := <-runErrCh:
|
|
|
|
c.Assert(err, check.IsNil)
|
2018-04-08 21:37:33 +00:00
|
|
|
case <-time.After(20 * time.Second):
|
|
|
|
c.Fatalf("failed to shut down the server")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestRotateTrustedClusters tests CA rotation support for trusted clusters
|
|
|
|
func (s *IntSuite) TestRotateTrustedClusters(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
2018-05-07 21:44:46 +00:00
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
clusterMain := "rotate-main"
|
|
|
|
clusterAux := "rotate-aux"
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.rotationConfig(false)
|
|
|
|
main := s.newNamedTeleportInstance(c, clusterMain)
|
|
|
|
aux := s.newNamedTeleportInstance(c, clusterAux)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
logins := []string{s.me.Username}
|
|
|
|
for _, login := range logins {
|
|
|
|
main.AddUser(login, []string{login})
|
|
|
|
}
|
|
|
|
config, err := main.GenerateConfig(nil, tconf)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
serviceC := make(chan *service.TeleportProcess, 20)
|
2020-06-02 00:06:46 +00:00
|
|
|
runErrCh := make(chan error, 1)
|
2018-04-08 21:37:33 +00:00
|
|
|
go func() {
|
2020-06-02 00:06:46 +00:00
|
|
|
runErrCh <- service.Run(ctx, *config, func(cfg *service.Config) (service.Process, error) {
|
2018-04-08 21:37:33 +00:00
|
|
|
svc, err := service.NewTeleport(cfg)
|
|
|
|
if err == nil {
|
|
|
|
serviceC <- svc
|
|
|
|
}
|
|
|
|
return svc, err
|
|
|
|
})
|
|
|
|
}()
|
|
|
|
|
2018-05-07 21:44:46 +00:00
|
|
|
svc, err := waitForProcessStart(serviceC)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// main cluster has a local user and belongs to role "main-devs"
|
|
|
|
mainDevs := "main-devs"
|
|
|
|
role, err := services.NewRole(mainDevs, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{s.me.Username},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
err = SetupUser(svc, s.me.Username, []services.Role{role})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2018-11-15 19:14:20 +00:00
|
|
|
// create auxiliary cluster and setup trust
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Assert(aux.CreateEx(nil, s.rotationConfig(false)), check.IsNil)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// auxiliary cluster has a role aux-devs
|
|
|
|
// connect aux cluster to main cluster
|
|
|
|
// using trusted clusters, so remote user will be allowed to assume
|
|
|
|
// role specified by mapping remote role "devs" to local role "local-devs"
|
|
|
|
auxDevs := "aux-devs"
|
|
|
|
role, err = services.NewRole(auxDevs, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{s.me.Username},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-06-15 21:24:34 +00:00
|
|
|
err = aux.Process.GetAuthServer().UpsertRole(ctx, role)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
trustedClusterToken := "trusted-clsuter-token"
|
2018-12-12 00:22:44 +00:00
|
|
|
err = svc.GetAuthServer().UpsertToken(
|
|
|
|
services.MustCreateProvisionToken(trustedClusterToken, []teleport.Role{teleport.RoleTrustedCluster}, time.Time{}))
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
trustedCluster := main.Secrets.AsTrustedCluster(trustedClusterToken, services.RoleMap{
|
|
|
|
{Remote: mainDevs, Local: []string{auxDevs}},
|
|
|
|
})
|
|
|
|
c.Assert(aux.Start(), check.IsNil)
|
|
|
|
|
|
|
|
// try and upsert a trusted cluster
|
|
|
|
lib.SetInsecureDevMode(true)
|
|
|
|
defer lib.SetInsecureDevMode(false)
|
2018-12-12 00:22:44 +00:00
|
|
|
|
|
|
|
tryCreateTrustedCluster(c, aux.Process.GetAuthServer(), trustedCluster)
|
|
|
|
waitForTunnelConnections(c, svc.GetAuthServer(), aux.Secrets.SiteName, 1)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// capture credentials before has reload started to simulate old client
|
2019-07-22 23:58:38 +00:00
|
|
|
initialCreds, err := GenerateUserCreds(UserCredsRequest{
|
|
|
|
Process: svc,
|
|
|
|
Username: s.me.Username,
|
|
|
|
})
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// credentials should work
|
|
|
|
cfg := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
2018-11-20 01:36:19 +00:00
|
|
|
Host: Loopback,
|
2018-04-08 21:37:33 +00:00
|
|
|
Cluster: clusterAux,
|
|
|
|
Port: aux.GetPortSSHInt(),
|
|
|
|
}
|
|
|
|
clt, err := main.NewClientWithCreds(cfg, *initialCreds)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Setting rotation state to %v", services.RotationPhaseInit)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// start rotation
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
2018-05-07 21:44:46 +00:00
|
|
|
TargetPhase: services.RotationPhaseInit,
|
2018-04-08 21:37:33 +00:00
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2018-05-07 21:44:46 +00:00
|
|
|
// wait until service phase update to be broadcasted (init phase does not trigger reload)
|
|
|
|
err = waitForProcessEvent(svc, service.TeleportPhaseChangeEvent, 10*time.Second)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// waitForPhase waits until aux cluster detects the rotation
|
|
|
|
waitForPhase := func(phase string) error {
|
|
|
|
var lastPhase string
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
ca, err := aux.Process.GetAuthServer().GetCertAuthority(services.CertAuthID{
|
|
|
|
Type: services.HostCA,
|
|
|
|
DomainName: clusterMain,
|
|
|
|
}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
if ca.GetRotation().Phase == phase {
|
|
|
|
return nil
|
|
|
|
}
|
2018-05-04 19:13:45 +00:00
|
|
|
lastPhase = ca.GetRotation().Phase
|
2018-04-08 21:37:33 +00:00
|
|
|
time.Sleep(tconf.PollingPeriod / 2)
|
|
|
|
}
|
|
|
|
return trace.CompareFailed("failed to converge to phase %q, last phase %q", phase, lastPhase)
|
|
|
|
}
|
|
|
|
|
2018-05-07 21:44:46 +00:00
|
|
|
err = waitForPhase(services.RotationPhaseInit)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// update clients
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseUpdateClients,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-05-07 21:44:46 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
err = waitForPhase(services.RotationPhaseUpdateClients)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// old client should work as is
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service reloaded. Setting rotation state to %v", services.RotationPhaseUpdateServers)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// move to the next phase
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseUpdateServers,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
err = waitForPhase(services.RotationPhaseUpdateServers)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// new credentials will work from this phase to others
|
2019-07-22 23:58:38 +00:00
|
|
|
newCreds, err := GenerateUserCreds(UserCredsRequest{Process: svc, Username: s.me.Username})
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
clt, err = main.NewClientWithCreds(cfg, *newCreds)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// new client works
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Service reloaded. Setting rotation state to %v.", services.RotationPhaseStandby)
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// complete rotation
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseStandby,
|
|
|
|
Mode: services.RotationModeManual,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Log("Waiting for service reload.")
|
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Log("Service reload completed, waiting for phase.")
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
err = waitForPhase(services.RotationPhaseStandby)
|
|
|
|
c.Assert(err, check.IsNil)
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Log("Phase completed.")
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// new client still works
|
2020-04-07 19:05:19 +00:00
|
|
|
err = runAndMatch(clt, 8, []string{"echo", "hello world"}, ".*hello world.*")
|
2018-04-08 21:37:33 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Log("Service reloaded. Rotation has completed. Shuttting down service.")
|
2018-04-08 21:37:33 +00:00
|
|
|
|
|
|
|
// shut down the service
|
|
|
|
cancel()
|
|
|
|
// close the service without waiting for the connections to drain
|
|
|
|
svc.Close()
|
|
|
|
|
|
|
|
select {
|
2020-06-02 00:06:46 +00:00
|
|
|
case err := <-runErrCh:
|
|
|
|
c.Assert(err, check.IsNil)
|
2018-04-08 21:37:33 +00:00
|
|
|
case <-time.After(20 * time.Second):
|
|
|
|
c.Fatalf("failed to shut down the server")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-12 23:10:50 +00:00
|
|
|
// TestRotateChangeSigningAlg tests the change of CA signing algorithm on
|
|
|
|
// manual rotation.
|
|
|
|
func (s *IntSuite) TestRotateChangeSigningAlg(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2020-06-12 23:10:50 +00:00
|
|
|
// Start with an instance using default signing alg.
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.rotationConfig(true)
|
|
|
|
t := s.newTeleportInstance(c)
|
2020-06-12 23:10:50 +00:00
|
|
|
logins := []string{s.me.Username}
|
|
|
|
for _, login := range logins {
|
|
|
|
t.AddUser(login, []string{login})
|
|
|
|
}
|
|
|
|
config, err := t.GenerateConfig(nil, tconf)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
serviceC := make(chan *service.TeleportProcess, 20)
|
|
|
|
runErrCh := make(chan error, 1)
|
|
|
|
|
|
|
|
restart := func(svc *service.TeleportProcess, cancel func()) (*service.TeleportProcess, func()) {
|
|
|
|
if svc != nil && cancel != nil {
|
|
|
|
// shut down the service
|
|
|
|
cancel()
|
|
|
|
// close the service without waiting for the connections to drain
|
|
|
|
err := svc.Close()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
err = svc.Wait()
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case err := <-runErrCh:
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
case <-time.After(20 * time.Second):
|
|
|
|
c.Fatalf("failed to shut down the server")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
go func() {
|
|
|
|
runErrCh <- service.Run(ctx, *config, func(cfg *service.Config) (service.Process, error) {
|
|
|
|
svc, err := service.NewTeleport(cfg)
|
|
|
|
if err == nil {
|
|
|
|
serviceC <- svc
|
|
|
|
}
|
|
|
|
return svc, err
|
|
|
|
})
|
|
|
|
}()
|
|
|
|
|
|
|
|
svc, err = waitForProcessStart(serviceC)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
return svc, cancel
|
|
|
|
}
|
|
|
|
|
|
|
|
assertSigningAlg := func(svc *service.TeleportProcess, alg string) {
|
|
|
|
hostCA, err := svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.HostCA, DomainName: Site}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
2021-01-29 18:28:24 +00:00
|
|
|
c.Assert(sshutils.GetSigningAlgName(hostCA), check.Equals, alg)
|
2020-06-12 23:10:50 +00:00
|
|
|
|
|
|
|
userCA, err := svc.GetAuthServer().GetCertAuthority(services.CertAuthID{Type: services.UserCA, DomainName: Site}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
2021-01-29 18:28:24 +00:00
|
|
|
c.Assert(sshutils.GetSigningAlgName(userCA), check.Equals, alg)
|
2020-06-12 23:10:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rotate := func(svc *service.TeleportProcess, mode string) *service.TeleportProcess {
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Rotation phase: %q.", services.RotationPhaseInit)
|
2020-06-12 23:10:50 +00:00
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseInit,
|
|
|
|
Mode: mode,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service phase update to be broadcasted (init phase does not trigger reload)
|
|
|
|
err = waitForProcessEvent(svc, service.TeleportPhaseChangeEvent, 10*time.Second)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Rotation phase: %q.", services.RotationPhaseUpdateClients)
|
2020-06-12 23:10:50 +00:00
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseUpdateClients,
|
|
|
|
Mode: mode,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reload
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2020-06-12 23:10:50 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
c.Logf("Rotation phase: %q.", services.RotationPhaseUpdateServers)
|
2020-06-12 23:10:50 +00:00
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseUpdateServers,
|
|
|
|
Mode: mode,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2020-06-12 23:10:50 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
c.Logf("rotation phase: %q", services.RotationPhaseStandby)
|
|
|
|
err = svc.GetAuthServer().RotateCertAuthority(auth.RotateRequest{
|
|
|
|
TargetPhase: services.RotationPhaseStandby,
|
|
|
|
Mode: mode,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// wait until service reloaded
|
2020-12-07 14:35:15 +00:00
|
|
|
svc, err = s.waitForReload(serviceC, svc)
|
2020-06-12 23:10:50 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
return svc
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the instance.
|
|
|
|
svc, cancel := restart(nil, nil)
|
|
|
|
|
|
|
|
c.Log("default signature algorithm due to empty config value")
|
|
|
|
// Verify the default signing algorithm with config value empty.
|
|
|
|
assertSigningAlg(svc, defaults.CASignatureAlgorithm)
|
|
|
|
|
|
|
|
c.Log("change signature algorithm with custom config value and manual rotation")
|
|
|
|
// Change the signing algorithm in config file.
|
|
|
|
signingAlg := ssh.SigAlgoRSA
|
|
|
|
config.CASignatureAlgorithm = &signingAlg
|
|
|
|
svc, cancel = restart(svc, cancel)
|
|
|
|
// Do a manual rotation - this should change the signing algorithm.
|
|
|
|
svc = rotate(svc, services.RotationModeManual)
|
|
|
|
assertSigningAlg(svc, ssh.SigAlgoRSA)
|
|
|
|
|
|
|
|
c.Log("preserve signature algorithm with empty config value and manual rotation")
|
|
|
|
// Unset the config value.
|
|
|
|
config.CASignatureAlgorithm = nil
|
|
|
|
svc, cancel = restart(svc, cancel)
|
|
|
|
|
|
|
|
// Do a manual rotation - this should leave the signing algorithm
|
|
|
|
// unaffected because config value is not set.
|
|
|
|
svc = rotate(svc, services.RotationModeManual)
|
|
|
|
assertSigningAlg(svc, ssh.SigAlgoRSA)
|
|
|
|
|
|
|
|
// shut down the service
|
|
|
|
cancel()
|
|
|
|
// close the service without waiting for the connections to drain
|
|
|
|
svc.Close()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case err := <-runErrCh:
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
case <-time.After(20 * time.Second):
|
|
|
|
c.Fatalf("failed to shut down the server")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
// rotationConfig sets up default config used for CA rotation tests
|
2020-12-07 14:35:15 +00:00
|
|
|
func (s *IntSuite) rotationConfig(disableWebService bool) *service.Config {
|
|
|
|
tconf := s.defaultServiceConfig()
|
2018-04-08 21:37:33 +00:00
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = disableWebService
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
tconf.PollingPeriod = 500 * time.Millisecond
|
|
|
|
tconf.ClientTimeout = time.Second
|
|
|
|
tconf.ShutdownTimeout = 2 * tconf.ClientTimeout
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
|
2018-05-07 21:44:46 +00:00
|
|
|
// waitForProcessEvent waits for process event to occur or timeout
|
|
|
|
func waitForProcessEvent(svc *service.TeleportProcess, event string, timeout time.Duration) error {
|
|
|
|
eventC := make(chan service.Event, 1)
|
|
|
|
svc.WaitForEvent(context.TODO(), event, eventC)
|
|
|
|
select {
|
|
|
|
case <-eventC:
|
|
|
|
return nil
|
|
|
|
case <-time.After(timeout):
|
|
|
|
return trace.BadParameter("timeout waiting for service to broadcast event %v", event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// waitForProcessStart is waiting for the process to start
|
|
|
|
func waitForProcessStart(serviceC chan *service.TeleportProcess) (*service.TeleportProcess, error) {
|
|
|
|
var svc *service.TeleportProcess
|
|
|
|
select {
|
|
|
|
case svc = <-serviceC:
|
2020-12-07 14:35:15 +00:00
|
|
|
case <-time.After(1 * time.Minute):
|
|
|
|
dumpGoroutineProfile()
|
2018-05-07 21:44:46 +00:00
|
|
|
return nil, trace.BadParameter("timeout waiting for service to start")
|
|
|
|
}
|
|
|
|
return svc, nil
|
|
|
|
}
|
|
|
|
|
2018-04-08 21:37:33 +00:00
|
|
|
// waitForReload waits for multiple events to happen:
|
|
|
|
//
|
|
|
|
// 1. new service to be created and started
|
|
|
|
// 2. old service, if present to shut down
|
|
|
|
//
|
|
|
|
// this helper function allows to serialize tests for reloads.
|
2020-12-07 14:35:15 +00:00
|
|
|
func (s *IntSuite) waitForReload(serviceC chan *service.TeleportProcess, old *service.TeleportProcess) (*service.TeleportProcess, error) {
|
2018-04-08 21:37:33 +00:00
|
|
|
var svc *service.TeleportProcess
|
|
|
|
select {
|
|
|
|
case svc = <-serviceC:
|
2020-12-07 14:35:15 +00:00
|
|
|
case <-time.After(1 * time.Minute):
|
|
|
|
dumpGoroutineProfile()
|
2018-04-08 21:37:33 +00:00
|
|
|
return nil, trace.BadParameter("timeout waiting for service to start")
|
|
|
|
}
|
|
|
|
|
|
|
|
eventC := make(chan service.Event, 1)
|
|
|
|
svc.WaitForEvent(context.TODO(), service.TeleportReadyEvent, eventC)
|
|
|
|
select {
|
|
|
|
case <-eventC:
|
|
|
|
|
|
|
|
case <-time.After(20 * time.Second):
|
2020-12-07 14:35:15 +00:00
|
|
|
dumpGoroutineProfile()
|
2018-04-08 21:37:33 +00:00
|
|
|
return nil, trace.BadParameter("timeout waiting for service to broadcast ready status")
|
|
|
|
}
|
|
|
|
|
|
|
|
// if old service is present, wait for it to complete shut down procedure
|
|
|
|
if old != nil {
|
|
|
|
ctx, cancel := context.WithCancel(context.TODO())
|
|
|
|
go func() {
|
|
|
|
defer cancel()
|
|
|
|
old.Supervisor.Wait()
|
|
|
|
}()
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2020-12-07 14:35:15 +00:00
|
|
|
case <-time.After(1 * time.Minute):
|
|
|
|
dumpGoroutineProfile()
|
2018-04-08 21:37:33 +00:00
|
|
|
return nil, trace.BadParameter("timeout waiting for old service to stop")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return svc, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// runAndMatch runs command and makes sure it matches the pattern
|
|
|
|
func runAndMatch(tc *client.TeleportClient, attempts int, command []string, pattern string) error {
|
|
|
|
output := &bytes.Buffer{}
|
|
|
|
tc.Stdout = output
|
|
|
|
var err error
|
|
|
|
for i := 0; i < attempts; i++ {
|
|
|
|
err = tc.SSH(context.TODO(), command, false)
|
|
|
|
if err != nil {
|
2020-04-07 19:05:19 +00:00
|
|
|
time.Sleep(500 * time.Millisecond)
|
2018-04-08 21:37:33 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
out := output.String()
|
2020-05-06 17:13:05 +00:00
|
|
|
out = replaceNewlines(out)
|
2018-04-08 21:37:33 +00:00
|
|
|
matched, _ := regexp.MatchString(pattern, out)
|
|
|
|
if matched {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
err = trace.CompareFailed("output %q did not match pattern %q", out, pattern)
|
2020-04-07 19:05:19 +00:00
|
|
|
time.Sleep(500 * time.Millisecond)
|
2018-04-08 21:37:33 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-05-04 00:36:08 +00:00
|
|
|
// TestWindowChange checks if custom Teleport window change requests are sent
|
|
|
|
// when the server side PTY changes its size.
|
|
|
|
func (s *IntSuite) TestWindowChange(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2018-05-04 00:36:08 +00:00
|
|
|
t := s.newTeleport(c, nil, true)
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2018-05-04 00:36:08 +00:00
|
|
|
|
|
|
|
site := t.GetSiteAPI(Site)
|
|
|
|
c.Assert(site, check.NotNil)
|
|
|
|
|
|
|
|
personA := NewTerminal(250)
|
|
|
|
personB := NewTerminal(250)
|
|
|
|
|
|
|
|
// openSession will open a new session on a server.
|
|
|
|
openSession := func() {
|
|
|
|
cl, err := t.NewClient(ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: t.GetPortSSHInt(),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-04-17 15:55:48 +00:00
|
|
|
cl.Stdout = personA
|
|
|
|
cl.Stdin = personA
|
2018-05-04 00:36:08 +00:00
|
|
|
|
|
|
|
err = cl.SSH(context.TODO(), []string{}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// joinSession will join the existing session on a server.
|
|
|
|
joinSession := func() {
|
|
|
|
// Find the existing session in the backend.
|
|
|
|
var sessionID string
|
|
|
|
for {
|
|
|
|
time.Sleep(time.Millisecond)
|
|
|
|
sessions, _ := site.GetSessions(defaults.Namespace)
|
|
|
|
if len(sessions) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
sessionID = string(sessions[0].ID)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
cl, err := t.NewClient(ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: t.GetPortSSHInt(),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-04-17 15:55:48 +00:00
|
|
|
cl.Stdout = personB
|
|
|
|
cl.Stdin = personB
|
2018-05-04 00:36:08 +00:00
|
|
|
|
|
|
|
// Change the size of the window immediately after it is created.
|
|
|
|
cl.OnShellCreated = func(s *ssh.Session, c *ssh.Client, terminal io.ReadWriteCloser) (exit bool, err error) {
|
|
|
|
err = s.WindowChange(48, 160)
|
|
|
|
if err != nil {
|
|
|
|
return true, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < 10; i++ {
|
2020-04-17 15:55:48 +00:00
|
|
|
err = cl.Join(context.TODO(), defaults.Namespace, session.ID(sessionID), personB)
|
2018-05-04 00:36:08 +00:00
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
}
|
|
|
|
|
2020-04-17 16:12:54 +00:00
|
|
|
// waitForOutput checks that the output of the passed in terminal contains
|
|
|
|
// one of the strings in `outputs` until some timeout has occurred.
|
|
|
|
waitForOutput := func(t *Terminal, outputs ...string) error {
|
2018-05-04 00:36:08 +00:00
|
|
|
tickerCh := time.Tick(500 * time.Millisecond)
|
|
|
|
timeoutCh := time.After(30 * time.Second)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-tickerCh:
|
2020-04-17 16:12:54 +00:00
|
|
|
out := t.Output(5000)
|
|
|
|
for _, s := range outputs {
|
|
|
|
if strings.Contains(out, s) {
|
|
|
|
return nil
|
|
|
|
}
|
2018-05-04 00:36:08 +00:00
|
|
|
}
|
|
|
|
case <-timeoutCh:
|
2020-12-07 14:35:15 +00:00
|
|
|
dumpGoroutineProfile()
|
2020-04-17 16:12:54 +00:00
|
|
|
return trace.BadParameter("timed out waiting for output, last output: %q doesn't contain any of the expected substrings: %q", t.Output(5000), outputs)
|
2018-05-04 00:36:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open session, the initial size will be 80x24.
|
|
|
|
go openSession()
|
|
|
|
|
|
|
|
// Use the "printf" command to print the terminal size on the screen and
|
|
|
|
// make sure it is 80x25.
|
2020-04-17 16:12:54 +00:00
|
|
|
personA.Type("\atput cols; tput lines\n\r\a")
|
|
|
|
err := waitForOutput(personA, "80\r\n25", "80\n\r25", "80\n25")
|
2018-05-04 00:36:08 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// As soon as person B joins the session, the terminal is resized to 160x48.
|
|
|
|
// Have another user join the session. As soon as the second shell is
|
|
|
|
// created, the window is resized to 160x48 (see joinSession implementation).
|
|
|
|
go joinSession()
|
|
|
|
|
|
|
|
// Use the "printf" command to print the window size again and make sure it's
|
|
|
|
// 160x48.
|
2020-04-17 16:12:54 +00:00
|
|
|
personA.Type("\atput cols; tput lines\n\r\a")
|
|
|
|
err = waitForOutput(personA, "160\r\n48", "160\n\r48", "160\n48")
|
2018-05-04 00:36:08 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Close the session.
|
|
|
|
personA.Type("\aexit\r\n\a")
|
|
|
|
}
|
|
|
|
|
2018-08-11 00:50:06 +00:00
|
|
|
// TestList checks that the list of servers returned is identity aware.
|
|
|
|
func (s *IntSuite) TestList(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2018-08-11 00:50:06 +00:00
|
|
|
// Create and start a Teleport cluster with auth, proxy, and node.
|
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: services.RecordOff,
|
2019-02-21 01:47:50 +00:00
|
|
|
LocalAuth: services.NewBool(true),
|
2018-08-11 00:50:06 +00:00
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2018-08-11 00:50:06 +00:00
|
|
|
tconf.Hostname = "server-01"
|
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = true
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.SSH.Labels = map[string]string{
|
|
|
|
"role": "worker",
|
|
|
|
}
|
|
|
|
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
t := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer t.StopAll()
|
2018-08-11 00:50:06 +00:00
|
|
|
|
|
|
|
// Create and start a Teleport node.
|
|
|
|
nodeSSHPort := s.getPorts(1)[0]
|
|
|
|
nodeConfig := func() *service.Config {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2018-08-11 00:50:06 +00:00
|
|
|
tconf.Hostname = "server-02"
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.SSH.Addr.Addr = net.JoinHostPort(t.Hostname, fmt.Sprintf("%v", nodeSSHPort))
|
|
|
|
tconf.SSH.Labels = map[string]string{
|
|
|
|
"role": "database",
|
|
|
|
}
|
|
|
|
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
_, err := t.StartNode(nodeConfig())
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Get an auth client to the cluster.
|
|
|
|
clt := t.GetSiteAPI(Site)
|
|
|
|
c.Assert(clt, check.NotNil)
|
|
|
|
|
|
|
|
// Wait 10 seconds for both nodes to show up to make sure they both have
|
|
|
|
// registered themselves.
|
|
|
|
waitForNodes := func(clt auth.ClientI, count int) error {
|
|
|
|
tickCh := time.Tick(500 * time.Millisecond)
|
|
|
|
stopCh := time.After(10 * time.Second)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-tickCh:
|
|
|
|
nodesInCluster, err := clt.GetNodes(defaults.Namespace, services.SkipValidation())
|
|
|
|
if err != nil && !trace.IsNotFound(err) {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
if got, want := len(nodesInCluster), count; got == want {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
case <-stopCh:
|
|
|
|
return trace.BadParameter("waited 10s, did find %v nodes", count)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = waitForNodes(clt, 2)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
inRoleName string
|
|
|
|
inLabels services.Labels
|
|
|
|
inLogin string
|
|
|
|
outNodes []string
|
|
|
|
}{
|
|
|
|
// 0 - Role has label "role:worker", only server-01 is returned.
|
|
|
|
{
|
|
|
|
inRoleName: "worker-only",
|
|
|
|
inLogin: "foo",
|
|
|
|
inLabels: services.Labels{"role": []string{"worker"}},
|
|
|
|
outNodes: []string{"server-01"},
|
|
|
|
},
|
|
|
|
// 1 - Role has label "role:database", only server-02 is returned.
|
|
|
|
{
|
|
|
|
inRoleName: "database-only",
|
|
|
|
inLogin: "bar",
|
|
|
|
inLabels: services.Labels{"role": []string{"database"}},
|
|
|
|
outNodes: []string{"server-02"},
|
|
|
|
},
|
|
|
|
// 2 - Role has wildcard label, all nodes are returned server-01 and server-2.
|
|
|
|
{
|
|
|
|
inRoleName: "worker-and-database",
|
|
|
|
inLogin: "baz",
|
|
|
|
inLabels: services.Labels{services.Wildcard: []string{services.Wildcard}},
|
|
|
|
outNodes: []string{"server-01", "server-02"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
// Create role with logins and labels for this test.
|
|
|
|
role, err := services.NewRole(tt.inRoleName, services.RoleSpecV3{
|
|
|
|
Allow: services.RoleConditions{
|
|
|
|
Logins: []string{tt.inLogin},
|
|
|
|
NodeLabels: tt.inLabels,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Create user, role, and generate credentials.
|
|
|
|
err = SetupUser(t.Process, tt.inLogin, []services.Role{role})
|
|
|
|
c.Assert(err, check.IsNil)
|
2019-07-22 23:58:38 +00:00
|
|
|
initialCreds, err := GenerateUserCreds(UserCredsRequest{Process: t.Process, Username: tt.inLogin})
|
2018-08-11 00:50:06 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Create a Teleport client.
|
|
|
|
cfg := ClientConfig{
|
|
|
|
Login: tt.inLogin,
|
|
|
|
Port: t.GetPortSSHInt(),
|
|
|
|
}
|
|
|
|
userClt, err := t.NewClientWithCreds(cfg, *initialCreds)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Get list of nodes and check that the returned nodes match the
|
|
|
|
// expected nodes.
|
|
|
|
nodes, err := userClt.ListNodes(context.Background())
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
for _, node := range nodes {
|
|
|
|
ok := utils.SliceContainsStr(tt.outNodes, node.GetHostname())
|
|
|
|
if !ok {
|
|
|
|
c.Fatalf("Got nodes: %v, want: %v.", nodes, tt.outNodes)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 20:58:40 +00:00
|
|
|
// TestCmdLabels verifies the behavior of running commands via labels
|
|
|
|
// with a mixture of regular and reversetunnel nodes.
|
|
|
|
func (s *IntSuite) TestCmdLabels(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2020-06-26 20:58:40 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
// InsecureDevMode needed for IoT node handshake
|
|
|
|
lib.SetInsecureDevMode(true)
|
|
|
|
defer lib.SetInsecureDevMode(false)
|
|
|
|
|
|
|
|
// Create and start a Teleport cluster with auth, proxy, and node.
|
|
|
|
makeConfig := func() *service.Config {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: services.RecordOff,
|
|
|
|
LocalAuth: services.NewBool(true),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2020-06-26 20:58:40 +00:00
|
|
|
tconf.Hostname = "server-01"
|
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.SSH.Labels = map[string]string{
|
|
|
|
"role": "worker",
|
|
|
|
"spam": "eggs",
|
|
|
|
}
|
|
|
|
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
t := s.newTeleportWithConfig(c, nil, nil, makeConfig())
|
|
|
|
defer t.StopAll()
|
|
|
|
|
|
|
|
// Create and start a reversetunnel node.
|
|
|
|
nodeConfig := func() *service.Config {
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2020-06-26 20:58:40 +00:00
|
|
|
tconf.Hostname = "server-02"
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.SSH.Labels = map[string]string{
|
|
|
|
"role": "database",
|
|
|
|
"spam": "eggs",
|
|
|
|
}
|
|
|
|
|
|
|
|
return tconf
|
|
|
|
}
|
|
|
|
_, err := t.StartReverseTunnelNode(nodeConfig())
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// test label patterns that match both nodes, and each
|
|
|
|
// node individually.
|
|
|
|
tts := []struct {
|
|
|
|
command []string
|
|
|
|
labels map[string]string
|
|
|
|
expect string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
command: []string{"echo", "two"},
|
|
|
|
labels: map[string]string{"spam": "eggs"},
|
|
|
|
expect: "two\ntwo\n",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
command: []string{"echo", "worker"},
|
|
|
|
labels: map[string]string{"role": "worker"},
|
|
|
|
expect: "worker\n",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
command: []string{"echo", "database"},
|
|
|
|
labels: map[string]string{"role": "database"},
|
|
|
|
expect: "database\n",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tts {
|
|
|
|
cfg := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Labels: tt.labels,
|
|
|
|
}
|
|
|
|
|
|
|
|
output, err := runCommand(t, tt.command, cfg, 1)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(output, check.Equals, tt.expect)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-15 02:15:45 +00:00
|
|
|
// TestDataTransfer makes sure that a "session.data" event is emitted at the
|
|
|
|
// end of a session that matches the amount of data that was transferred.
|
|
|
|
func (s *IntSuite) TestDataTransfer(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2018-12-12 00:22:44 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
2019-02-15 02:15:45 +00:00
|
|
|
KB := 1024
|
|
|
|
MB := 1048576
|
|
|
|
|
|
|
|
// Create a Teleport cluster.
|
|
|
|
main := s.newTeleport(c, nil, true)
|
2020-04-17 16:27:34 +00:00
|
|
|
defer main.StopAll()
|
2019-02-15 02:15:45 +00:00
|
|
|
|
|
|
|
// Create a client to the above Teleport cluster.
|
|
|
|
clientConfig := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: main.GetPortSSHInt(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write 1 MB to stdout.
|
|
|
|
command := []string{"dd", "if=/dev/zero", "bs=1024", "count=1024"}
|
|
|
|
output, err := runCommand(main, command, clientConfig, 1)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Make sure exactly 1 MB was written to output.
|
|
|
|
c.Assert(len(output) == MB, check.Equals, true)
|
|
|
|
|
|
|
|
// Make sure the session.data event was emitted to the audit log.
|
|
|
|
eventFields, err := findEventInLog(main, events.SessionDataEvent)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Make sure the audit event shows that 1 MB was written to the output.
|
|
|
|
c.Assert(eventFields.GetInt(events.DataReceived) > MB, check.Equals, true)
|
|
|
|
c.Assert(eventFields.GetInt(events.DataTransmitted) > KB, check.Equals, true)
|
|
|
|
}
|
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
func (s *IntSuite) TestBPFInteractive(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
// Check if BPF tests can be run on this host.
|
|
|
|
err := canTestBPF()
|
|
|
|
if err != nil {
|
|
|
|
c.Skip(fmt.Sprintf("Tests for BPF functionality can not be run: %v.", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lsPath, err := exec.LookPath("ls")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
inSessionRecording string
|
|
|
|
inBPFEnabled bool
|
|
|
|
outFound bool
|
|
|
|
}{
|
|
|
|
// For session recorded at the node, enhanced events should be found.
|
|
|
|
{
|
|
|
|
inSessionRecording: services.RecordAtNode,
|
|
|
|
inBPFEnabled: true,
|
|
|
|
outFound: true,
|
|
|
|
},
|
|
|
|
// For session recorded at the node, but BPF is turned off, no events
|
|
|
|
// should be found.
|
|
|
|
{
|
|
|
|
inSessionRecording: services.RecordAtNode,
|
|
|
|
inBPFEnabled: false,
|
|
|
|
outFound: false,
|
|
|
|
},
|
|
|
|
// For session recorded at the proxy, enhanced events should not be found.
|
|
|
|
// BPF turned off simulates an OpenSSH node.
|
|
|
|
{
|
|
|
|
inSessionRecording: services.RecordAtProxy,
|
|
|
|
inBPFEnabled: false,
|
|
|
|
outFound: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
// Create temporary directory where cgroup2 hierarchy will be mounted.
|
|
|
|
dir, err := ioutil.TempDir("", "cgroup-test")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
|
|
|
// Create and start a Teleport cluster.
|
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: tt.inSessionRecording,
|
|
|
|
LocalAuth: services.NewBool(true),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Create default config.
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2019-11-16 00:39:40 +00:00
|
|
|
|
|
|
|
// Configure Auth.
|
|
|
|
tconf.Auth.Preference.SetSecondFactor("off")
|
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
|
|
|
|
// Configure Proxy.
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
// Configure Node. If session are being recorded at the proxy, don't enable
|
|
|
|
// BPF to simulate an OpenSSH node.
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
if tt.inBPFEnabled {
|
|
|
|
tconf.SSH.BPF.Enabled = true
|
|
|
|
tconf.SSH.BPF.CgroupPath = dir
|
|
|
|
}
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
main := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer main.StopAll()
|
2019-11-16 00:39:40 +00:00
|
|
|
|
|
|
|
// Create a client terminal and context to signal when the client is done
|
|
|
|
// with the terminal.
|
|
|
|
term := NewTerminal(250)
|
|
|
|
doneContext, doneCancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
func() {
|
|
|
|
client, err := main.NewClient(ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: main.GetPortSSHInt(),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Connect terminal to std{in,out} of client.
|
2020-04-17 15:55:48 +00:00
|
|
|
client.Stdout = term
|
|
|
|
client.Stdin = term
|
2019-11-16 00:39:40 +00:00
|
|
|
|
|
|
|
// "Type" a command into the terminal.
|
|
|
|
term.Type(fmt.Sprintf("\a%v\n\r\aexit\n\r\a", lsPath))
|
|
|
|
err = client.SSH(context.TODO(), []string{}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Signal that the client has finished the interactive session.
|
|
|
|
doneCancel()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait 10 seconds for the client to finish up the interactive session.
|
|
|
|
select {
|
|
|
|
case <-time.After(10 * time.Second):
|
|
|
|
c.Fatalf("Timed out waiting for client to finish interactive session.")
|
|
|
|
case <-doneContext.Done():
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enhanced events should show up for session recorded at the node but not
|
|
|
|
// at the proxy.
|
|
|
|
if tt.outFound {
|
|
|
|
_, err = findCommandEventInLog(main, events.SessionCommandEvent, lsPath)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
} else {
|
|
|
|
_, err = findCommandEventInLog(main, events.SessionCommandEvent, lsPath)
|
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IntSuite) TestBPFExec(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
// Check if BPF tests can be run on this host.
|
|
|
|
err := canTestBPF()
|
|
|
|
if err != nil {
|
|
|
|
c.Skip(fmt.Sprintf("Tests for BPF functionality can not be run: %v.", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lsPath, err := exec.LookPath("ls")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
inSessionRecording string
|
|
|
|
inBPFEnabled bool
|
|
|
|
outFound bool
|
|
|
|
}{
|
|
|
|
// For session recorded at the node, enhanced events should be found.
|
|
|
|
{
|
|
|
|
inSessionRecording: services.RecordAtNode,
|
|
|
|
inBPFEnabled: true,
|
|
|
|
outFound: true,
|
|
|
|
},
|
|
|
|
// For session recorded at the node, but BPF is turned off, no events
|
|
|
|
// should be found.
|
|
|
|
{
|
|
|
|
inSessionRecording: services.RecordAtNode,
|
|
|
|
inBPFEnabled: false,
|
|
|
|
outFound: false,
|
|
|
|
},
|
|
|
|
// For session recorded at the proxy, enhanced events should not be found.
|
|
|
|
// BPF turned off simulates an OpenSSH node.
|
|
|
|
{
|
|
|
|
inSessionRecording: services.RecordAtProxy,
|
|
|
|
inBPFEnabled: false,
|
|
|
|
outFound: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
// Create temporary directory where cgroup2 hierarchy will be mounted.
|
|
|
|
dir, err := ioutil.TempDir("", "cgroup-test")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
|
|
|
// Create and start a Teleport cluster.
|
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: tt.inSessionRecording,
|
|
|
|
LocalAuth: services.NewBool(true),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Create default config.
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2019-11-16 00:39:40 +00:00
|
|
|
|
|
|
|
// Configure Auth.
|
|
|
|
tconf.Auth.Preference.SetSecondFactor("off")
|
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
|
|
|
|
// Configure Proxy.
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
// Configure Node. If session are being recorded at the proxy, don't enable
|
|
|
|
// BPF to simulate an OpenSSH node.
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
if tt.inBPFEnabled {
|
|
|
|
tconf.SSH.BPF.Enabled = true
|
|
|
|
tconf.SSH.BPF.CgroupPath = dir
|
|
|
|
}
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
main := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer main.StopAll()
|
2019-11-16 00:39:40 +00:00
|
|
|
|
|
|
|
// Create a client to the above Teleport cluster.
|
|
|
|
clientConfig := ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: main.GetPortSSHInt(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run exec command.
|
|
|
|
_, err = runCommand(main, []string{lsPath}, clientConfig, 1)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Enhanced events should show up for session recorded at the node but not
|
|
|
|
// at the proxy.
|
|
|
|
if tt.outFound {
|
|
|
|
_, err = findCommandEventInLog(main, events.SessionCommandEvent, lsPath)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
} else {
|
|
|
|
_, err = findCommandEventInLog(main, events.SessionCommandEvent, lsPath)
|
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-07 00:17:50 +00:00
|
|
|
// TestBPFSessionDifferentiation verifies that the bpf package can
|
|
|
|
// differentiate events from two different sessions. This test in turn also
|
|
|
|
// verifies the cgroup package.
|
|
|
|
func (s *IntSuite) TestBPFSessionDifferentiation(c *check.C) {
|
2020-12-07 14:35:15 +00:00
|
|
|
s.setUpTest(c)
|
|
|
|
defer s.tearDownTest(c)
|
|
|
|
|
2019-12-07 00:17:50 +00:00
|
|
|
tr := utils.NewTracer(utils.ThisFunction()).Start()
|
|
|
|
defer tr.Stop()
|
|
|
|
|
|
|
|
// Check if BPF tests can be run on this host.
|
|
|
|
err := canTestBPF()
|
|
|
|
if err != nil {
|
|
|
|
c.Skip(fmt.Sprintf("Tests for BPF functionality can not be run: %v.", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lsPath, err := exec.LookPath("ls")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Create temporary directory where cgroup2 hierarchy will be mounted.
|
|
|
|
dir, err := ioutil.TempDir("", "cgroup-test")
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
|
|
|
|
// Create and start a Teleport cluster.
|
|
|
|
makeConfig := func() (*check.C, []string, []*InstanceSecrets, *service.Config) {
|
|
|
|
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
|
|
|
SessionRecording: services.RecordAtNode,
|
|
|
|
LocalAuth: services.NewBool(true),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Create default config.
|
2020-12-07 14:35:15 +00:00
|
|
|
tconf := s.defaultServiceConfig()
|
2019-12-07 00:17:50 +00:00
|
|
|
|
|
|
|
// Configure Auth.
|
|
|
|
tconf.Auth.Preference.SetSecondFactor("off")
|
|
|
|
tconf.Auth.Enabled = true
|
|
|
|
tconf.Auth.ClusterConfig = clusterConfig
|
|
|
|
|
|
|
|
// Configure Proxy.
|
|
|
|
tconf.Proxy.Enabled = true
|
|
|
|
tconf.Proxy.DisableWebService = false
|
|
|
|
tconf.Proxy.DisableWebInterface = true
|
|
|
|
|
|
|
|
// Configure Node. If session are being recorded at the proxy, don't enable
|
|
|
|
// BPF to simulate an OpenSSH node.
|
|
|
|
tconf.SSH.Enabled = true
|
|
|
|
tconf.SSH.BPF.Enabled = true
|
|
|
|
tconf.SSH.BPF.CgroupPath = dir
|
|
|
|
return c, nil, nil, tconf
|
|
|
|
}
|
|
|
|
main := s.newTeleportWithConfig(makeConfig())
|
2020-04-17 16:27:34 +00:00
|
|
|
defer main.StopAll()
|
2019-12-07 00:17:50 +00:00
|
|
|
|
|
|
|
// Create two client terminals and channel to signal when the clients are
|
|
|
|
// done with the terminals.
|
|
|
|
termA := NewTerminal(250)
|
|
|
|
termB := NewTerminal(250)
|
|
|
|
doneCh := make(chan bool, 2)
|
|
|
|
|
|
|
|
// Open a terminal and type "ls" into both and exit.
|
2020-04-17 15:55:48 +00:00
|
|
|
writeTerm := func(term *Terminal) {
|
2019-12-07 00:17:50 +00:00
|
|
|
client, err := main.NewClient(ClientConfig{
|
|
|
|
Login: s.me.Username,
|
|
|
|
Cluster: Site,
|
|
|
|
Host: Host,
|
|
|
|
Port: main.GetPortSSHInt(),
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Connect terminal to std{in,out} of client.
|
2020-04-17 15:55:48 +00:00
|
|
|
client.Stdout = term
|
|
|
|
client.Stdin = term
|
2019-12-07 00:17:50 +00:00
|
|
|
|
|
|
|
// "Type" a command into the terminal.
|
|
|
|
term.Type(fmt.Sprintf("\a%v\n\r\aexit\n\r\a", lsPath))
|
|
|
|
err = client.SSH(context.Background(), []string{}, false)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Signal that the client has finished the interactive session.
|
|
|
|
doneCh <- true
|
|
|
|
}
|
|
|
|
writeTerm(termA)
|
|
|
|
writeTerm(termB)
|
|
|
|
|
|
|
|
// Wait 10 seconds for both events to arrive, otherwise timeout.
|
|
|
|
timeout := time.After(10 * time.Second)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
select {
|
|
|
|
case <-doneCh:
|
|
|
|
if i == 1 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case <-timeout:
|
2020-12-07 14:35:15 +00:00
|
|
|
dumpGoroutineProfile()
|
2019-12-07 00:17:50 +00:00
|
|
|
c.Fatalf("Timed out waiting for client to finish interactive session.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to find two command events from different sessions. Timeout after
|
|
|
|
// 10 seconds.
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
sessionIDs := map[string]bool{}
|
|
|
|
|
|
|
|
eventFields, err := eventsInLog(main.Config.DataDir+"/log/events.log", events.SessionCommandEvent)
|
|
|
|
if err != nil {
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fields := range eventFields {
|
|
|
|
if fields.GetString(events.EventType) == events.SessionCommandEvent &&
|
|
|
|
fields.GetString(events.Path) == lsPath {
|
|
|
|
sessionIDs[fields.GetString(events.SessionEventID)] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If two command events for "ls" from different sessions, return right
|
|
|
|
// away, test was successful.
|
|
|
|
if len(sessionIDs) == 2 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
}
|
|
|
|
c.Fatalf("Failed to find command events from two different sessions.")
|
|
|
|
}
|
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
// findEventInLog polls the event log looking for an event of a particular type.
|
2019-02-15 02:15:45 +00:00
|
|
|
func findEventInLog(t *TeleInstance, eventName string) (events.EventFields, error) {
|
|
|
|
for i := 0; i < 10; i++ {
|
2019-11-16 00:39:40 +00:00
|
|
|
eventFields, err := eventsInLog(t.Config.DataDir+"/log/events.log", eventName)
|
|
|
|
if err != nil {
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fields := range eventFields {
|
|
|
|
eventType, ok := fields[events.EventType]
|
|
|
|
if !ok {
|
|
|
|
return nil, trace.BadParameter("not found")
|
|
|
|
}
|
|
|
|
if eventType == eventName {
|
|
|
|
return fields, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
}
|
|
|
|
return nil, trace.NotFound("event not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
// findCommandEventInLog polls the event log looking for an event of a particular type.
|
|
|
|
func findCommandEventInLog(t *TeleInstance, eventName string, programName string) (events.EventFields, error) {
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
eventFields, err := eventsInLog(t.Config.DataDir+"/log/events.log", eventName)
|
2019-02-15 02:15:45 +00:00
|
|
|
if err != nil {
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
for _, fields := range eventFields {
|
|
|
|
eventType, ok := fields[events.EventType]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
eventPath, ok := fields[events.Path]
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if eventType == eventName && eventPath == programName {
|
|
|
|
return fields, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(1 * time.Second)
|
2019-02-15 02:15:45 +00:00
|
|
|
}
|
|
|
|
return nil, trace.NotFound("event not found")
|
|
|
|
}
|
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
// eventsInLog returns all events in a log file.
|
|
|
|
func eventsInLog(path string, eventName string) ([]events.EventFields, error) {
|
|
|
|
var ret []events.EventFields
|
|
|
|
|
2019-02-15 02:15:45 +00:00
|
|
|
file, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
|
|
|
var fields events.EventFields
|
|
|
|
err = json.Unmarshal(scanner.Bytes(), &fields)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2019-11-16 00:39:40 +00:00
|
|
|
ret = append(ret, fields)
|
2019-02-15 02:15:45 +00:00
|
|
|
}
|
|
|
|
|
2019-11-16 00:39:40 +00:00
|
|
|
if len(ret) == 0 {
|
|
|
|
return nil, trace.NotFound("event not found")
|
|
|
|
}
|
|
|
|
return ret, nil
|
2019-02-15 02:15:45 +00:00
|
|
|
}
|
|
|
|
|
2018-02-13 00:28:17 +00:00
|
|
|
// runCommand is a shortcut for running SSH command, it creates a client
|
|
|
|
// connected to proxy of the passed in instance, runs the command, and returns
|
|
|
|
// the result. If multiple attempts are requested, a 250 millisecond delay is
|
|
|
|
// added between them before giving up.
|
2017-10-12 23:51:18 +00:00
|
|
|
func runCommand(instance *TeleInstance, cmd []string, cfg ClientConfig, attempts int) (string, error) {
|
|
|
|
tc, err := instance.NewClient(cfg)
|
|
|
|
if err != nil {
|
|
|
|
return "", trace.Wrap(err)
|
|
|
|
}
|
2020-06-26 20:58:40 +00:00
|
|
|
// since this helper is sometimes used for running commands on
|
|
|
|
// multiple nodes concurrently, we use io.Pipe to protect our
|
|
|
|
// output buffer from concurrent writes.
|
|
|
|
read, write := io.Pipe()
|
2017-10-12 23:51:18 +00:00
|
|
|
output := &bytes.Buffer{}
|
2020-06-26 20:58:40 +00:00
|
|
|
doneC := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
io.Copy(output, read)
|
|
|
|
close(doneC)
|
|
|
|
}()
|
|
|
|
tc.Stdout = write
|
2017-10-12 23:51:18 +00:00
|
|
|
for i := 0; i < attempts; i++ {
|
|
|
|
err = tc.SSH(context.TODO(), cmd, false)
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
2019-05-14 22:16:27 +00:00
|
|
|
time.Sleep(1 * time.Second)
|
2017-10-12 23:51:18 +00:00
|
|
|
}
|
2020-06-26 20:58:40 +00:00
|
|
|
write.Close()
|
|
|
|
if err != nil {
|
|
|
|
return "", trace.Wrap(err)
|
|
|
|
}
|
|
|
|
<-doneC
|
|
|
|
return output.String(), nil
|
2017-10-12 23:51:18 +00:00
|
|
|
}
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
// getPorts helper returns a range of unallocated ports available for listening on
|
2016-04-14 20:30:13 +00:00
|
|
|
func (s *IntSuite) getPorts(num int) []int {
|
|
|
|
if len(s.ports) < num {
|
|
|
|
panic("do not have enough ports! increase AllocatePortsNum constant")
|
|
|
|
}
|
|
|
|
ports := make([]int, num)
|
|
|
|
for i := range ports {
|
|
|
|
p, _ := strconv.Atoi(s.ports.Pop())
|
|
|
|
ports[i] = p
|
|
|
|
}
|
|
|
|
return ports
|
|
|
|
}
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
func (s *IntSuite) newTeleportInstance(c *check.C) *TeleInstance {
|
|
|
|
return NewInstance(InstanceConfig{
|
|
|
|
ClusterName: Site,
|
|
|
|
HostID: HostID,
|
|
|
|
NodeName: Host,
|
2021-02-12 23:09:26 +00:00
|
|
|
Ports: s.getPorts(6),
|
2020-12-07 14:35:15 +00:00
|
|
|
Priv: s.priv,
|
|
|
|
Pub: s.pub,
|
|
|
|
log: s.log,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IntSuite) newNamedTeleportInstance(c *check.C, clusterName string) *TeleInstance {
|
|
|
|
return NewInstance(InstanceConfig{
|
|
|
|
ClusterName: clusterName,
|
|
|
|
HostID: HostID,
|
|
|
|
NodeName: Host,
|
2021-02-12 23:09:26 +00:00
|
|
|
Ports: s.getPorts(6),
|
2020-12-07 14:35:15 +00:00
|
|
|
Priv: s.priv,
|
|
|
|
Pub: s.pub,
|
|
|
|
log: s.log,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *IntSuite) defaultServiceConfig() *service.Config {
|
|
|
|
cfg := service.MakeDefaultConfig()
|
|
|
|
cfg.Console = nil
|
|
|
|
cfg.Log = s.log
|
|
|
|
return cfg
|
|
|
|
}
|
|
|
|
|
2016-04-14 20:30:13 +00:00
|
|
|
// Terminal emulates stdin+stdout for integration testing
|
|
|
|
type Terminal struct {
|
|
|
|
typed chan byte
|
2020-04-17 15:55:48 +00:00
|
|
|
mu *sync.Mutex
|
|
|
|
written *bytes.Buffer
|
2016-04-14 20:30:13 +00:00
|
|
|
}
|
|
|
|
|
2020-04-17 15:55:48 +00:00
|
|
|
func NewTerminal(capacity int) *Terminal {
|
|
|
|
return &Terminal{
|
2016-04-14 20:30:13 +00:00
|
|
|
typed: make(chan byte, capacity),
|
2020-04-17 15:55:48 +00:00
|
|
|
mu: &sync.Mutex{},
|
|
|
|
written: bytes.NewBuffer(nil),
|
2016-04-14 20:30:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) Type(data string) {
|
|
|
|
for _, b := range []byte(data) {
|
|
|
|
t.typed <- b
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-06 23:10:50 +00:00
|
|
|
// Output returns a number of first 'limit' bytes printed into this fake terminal
|
|
|
|
func (t *Terminal) Output(limit int) string {
|
2020-04-17 15:55:48 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2016-05-06 23:10:50 +00:00
|
|
|
buff := t.written.Bytes()
|
|
|
|
if len(buff) > limit {
|
|
|
|
buff = buff[:limit]
|
2016-04-14 20:30:13 +00:00
|
|
|
}
|
2016-05-06 23:10:50 +00:00
|
|
|
// clean up white space for easier comparison:
|
|
|
|
return strings.TrimSpace(string(buff))
|
2016-04-14 20:30:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) Write(data []byte) (n int, err error) {
|
2020-04-17 15:55:48 +00:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2016-04-14 20:30:13 +00:00
|
|
|
return t.written.Write(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) Read(p []byte) (n int, err error) {
|
|
|
|
for n = 0; n < len(p); n++ {
|
|
|
|
p[n] = <-t.typed
|
|
|
|
if p[n] == '\r' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if p[n] == '\a' { // 'alert' used for debugging, means 'pause for 1 second'
|
|
|
|
time.Sleep(time.Second)
|
2018-12-12 00:22:44 +00:00
|
|
|
n--
|
2016-04-14 20:30:13 +00:00
|
|
|
}
|
|
|
|
time.Sleep(time.Millisecond * 10)
|
|
|
|
}
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// waitFor helper waits on a challen for up to the given timeout
|
|
|
|
func waitFor(c chan interface{}, timeout time.Duration) error {
|
|
|
|
tick := time.Tick(timeout)
|
|
|
|
select {
|
|
|
|
case <-c:
|
|
|
|
return nil
|
|
|
|
case <-tick:
|
|
|
|
return fmt.Errorf("timeout waiting for event")
|
|
|
|
}
|
|
|
|
}
|
2018-02-24 01:23:09 +00:00
|
|
|
|
|
|
|
// hasPAMPolicy checks if the three policy files needed for tests exists. If
|
|
|
|
// they do it returns true, otherwise returns false.
|
|
|
|
func hasPAMPolicy() bool {
|
|
|
|
pamPolicyFiles := []string{
|
|
|
|
"/etc/pam.d/teleport-acct-failure",
|
|
|
|
"/etc/pam.d/teleport-session-failure",
|
|
|
|
"/etc/pam.d/teleport-success",
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fileName := range pamPolicyFiles {
|
|
|
|
_, err := os.Stat(fileName)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
2019-11-16 00:39:40 +00:00
|
|
|
|
2021-02-15 19:43:39 +00:00
|
|
|
// isRoot returns a boolean if the test is being run as root or not.
|
|
|
|
func isRoot() bool {
|
|
|
|
return os.Geteuid() == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// canTestBPF runs checks to determine whether BPF tests will run or not.
|
|
|
|
// Tests for this package must be run as root.
|
2019-11-16 00:39:40 +00:00
|
|
|
func canTestBPF() error {
|
2021-02-15 19:43:39 +00:00
|
|
|
if !isRoot() {
|
2019-11-16 00:39:40 +00:00
|
|
|
return trace.BadParameter("not root")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := bpf.IsHostCompatible()
|
|
|
|
if err != nil {
|
|
|
|
return trace.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-07 14:35:15 +00:00
|
|
|
|
|
|
|
func dumpGoroutineProfile() {
|
|
|
|
pprof.Lookup("goroutine").WriteTo(os.Stderr, 2)
|
|
|
|
}
|