Add tsh e2e tests with various security features enabled (#26862)

* * Refactor tool/tsh to enable tsh e2e tests outside of the tsh package.

* Add tool/teleport/testenv to enable easier e2e tests from outside
  packages.

* Skip all flaky test checks when * is provided.
This commit is contained in:
Brian Joerger 2023-06-05 18:25:09 -07:00 committed by GitHub
parent 724f6a1f76
commit 70c5ce7e8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 486 additions and 158 deletions

View file

@ -169,7 +169,7 @@ func test(repoPath string, ref string, changedFiles []string) {
}
for _, n := range r.New {
if slices.Contains(testsToSkip, n.RefName) {
if slices.Contains(testsToSkip, n.RefName) || slices.Contains(testsToSkip, "*") {
log.Printf("-skipping %q (%s)\n", n.RefName, dir)
continue
}
@ -182,7 +182,7 @@ func test(repoPath string, ref string, changedFiles []string) {
}
for _, n := range r.Changed {
if slices.Contains(testsToSkip, n.RefName) {
if slices.Contains(testsToSkip, n.RefName) || slices.Contains(testsToSkip, "*") {
log.Printf("-skipping %q (%s)\n", n.RefName, dir)
continue
}

View file

@ -430,6 +430,21 @@ type Config struct {
// PROXYSigner is used to sign PROXY headers for securely propagating client IP address
PROXYSigner multiplexer.PROXYHeaderSigner
// DTAuthnRunCeremony allows tests to override the default device
// authentication function.
// Defaults to [dtauthn.NewCeremony().Run].
DTAuthnRunCeremony DTAuthnRunCeremonyFunc
// dtAttemptLoginIgnorePing and dtAutoEnrollIgnorePing allow Device Trust
// tests to ignore Ping responses.
// Useful to force flows that only typically happen on Teleport Enterprise.
dtAttemptLoginIgnorePing, dtAutoEnrollIgnorePing bool
// dtAutoEnroll allows tests to override the default device auto-enroll
// function.
// Defaults to [dtenroll.AutoEnroll].
dtAutoEnroll dtAutoEnrollFunc
}
// CachePolicy defines cache policy for local clients
@ -942,8 +957,8 @@ func (c *Config) ResourceFilter(kind string) *proto.ListResourcesRequest {
}
}
// dtAuthnRunCeremonyFunc matches the signature of [dtauthn.RunCeremony].
type dtAuthnRunCeremonyFunc func(context.Context, devicepb.DeviceTrustServiceClient, *devicepb.UserCertificates) (*devicepb.UserCertificates, error)
// DTAuthnRunCeremonyFunc matches the signature of [dtauthn.RunCeremony].
type DTAuthnRunCeremonyFunc func(context.Context, devicepb.DeviceTrustServiceClient, *devicepb.UserCertificates) (*devicepb.UserCertificates, error)
// dtAutoEnrollFunc matches the signature of [dtenroll.AutoEnroll].
type dtAutoEnrollFunc func(context.Context, devicepb.DeviceTrustServiceClient) (*devicepb.Device, error)
@ -966,21 +981,6 @@ type TeleportClient struct {
// Note: there's no mutex guarding this or localAgent, making
// TeleportClient NOT safe for concurrent use.
lastPing *webclient.PingResponse
// dtAttemptLoginIgnorePing and dtAutoEnrollIgnorePing allow Device Trust
// tests to ignore Ping responses.
// Useful to force flows that only typically happen on Teleport Enterprise.
dtAttemptLoginIgnorePing, dtAutoEnrollIgnorePing bool
// dtAuthnRunCeremony allows tests to override the default device
// authentication function.
// Defaults to [dtauthn.NewCeremony().Run].
dtAuthnRunCeremony dtAuthnRunCeremonyFunc
// dtAutoEnroll allows tests to override the default device auto-enroll
// function.
// Defaults to [dtenroll.AutoEnroll].
dtAutoEnroll dtAutoEnrollFunc
}
// ShellCreatedCallback can be supplied for every teleport client. It will
@ -3179,15 +3179,11 @@ func (g *proxyClusterGuesser) authMethod(ctx context.Context) ssh.AuthMethod {
// profile.
func (tc *TeleportClient) WithoutJumpHosts(fn func(tcNoJump *TeleportClient) error) error {
tcNoJump := &TeleportClient{
Config: tc.Config,
localAgent: tc.localAgent,
OnShellCreated: tc.OnShellCreated,
eventsCh: make(chan events.EventFields, 1024),
lastPing: tc.lastPing,
dtAttemptLoginIgnorePing: tc.dtAttemptLoginIgnorePing,
dtAutoEnrollIgnorePing: tc.dtAutoEnrollIgnorePing,
dtAuthnRunCeremony: tc.dtAuthnRunCeremony,
dtAutoEnroll: tc.dtAutoEnroll,
Config: tc.Config,
localAgent: tc.localAgent,
OnShellCreated: tc.OnShellCreated,
eventsCh: make(chan events.EventFields, 1024),
lastPing: tc.lastPing,
}
tcNoJump.JumpHosts = nil
@ -3410,7 +3406,7 @@ func (tc *TeleportClient) DeviceLogin(ctx context.Context, certs *devicepb.UserC
defer authClient.Close()
// Allow tests to override the default authn function.
runCeremony := tc.dtAuthnRunCeremony
runCeremony := tc.DTAuthnRunCeremony
if runCeremony == nil {
runCeremony = dtauthn.NewCeremony().Run
}

View file

@ -24,8 +24,8 @@ func (tc *TeleportClient) SetDTAutoEnrollIgnorePing(val bool) {
tc.dtAutoEnrollIgnorePing = val
}
func (tc *TeleportClient) SetDTAuthnRunCeremony(fn dtAuthnRunCeremonyFunc) {
tc.dtAuthnRunCeremony = fn
func (tc *TeleportClient) SetDTAuthnRunCeremony(fn DTAuthnRunCeremonyFunc) {
tc.DTAuthnRunCeremony = fn
}
func (tc *TeleportClient) SetDTAutoEnroll(fn dtAutoEnrollFunc) {

View file

@ -0,0 +1,299 @@
/*
Copyright 2023 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package testenv provides functions for creating test servers for testing.
package testenv
import (
"context"
"crypto"
"fmt"
"net"
"os"
"path/filepath"
"testing"
"time"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/breaker"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/cloud"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/srv"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/tool/teleport/common"
)
var ports utils.PortList
// used to easily join test services
const staticToken = "test-static-token"
func init() {
// If the test is re-executing itself, execute the command that comes over
// the pipe. Used to test tsh ssh and tsh scp commands.
if srv.IsReexec() {
common.Run(common.Options{Args: os.Args[1:]})
return
}
var err error
ports, err = utils.GetFreeTCPPorts(100)
if err != nil {
panic(fmt.Sprintf("Failed to allocate tcp ports for tests: %v", err))
}
modules.SetModules(&cliModules{})
}
// MakeTestServer creates a Teleport Server for testing.
func MakeTestServer(t *testing.T, opts ...TestServerOptFunc) (process *service.TeleportProcess) {
t.Helper()
var options TestServersOpts
for _, opt := range opts {
opt(&options)
}
// Set up a test auth server with default config.
cfg := servicecfg.MakeDefaultConfig()
cfg.CircuitBreakerConfig = breaker.NoopBreakerConfig()
cfg.CachePolicy.Enabled = false
// Disables cloud auto-imported labels when running tests in cloud envs
// such as Github Actions.
//
// This is required otherwise Teleport will import cloud instance
// labels, and use them for example as labels in Kubernetes Service and
// cause some tests to fail because the output includes unexpected
// labels.
//
// It is also found that Azure metadata client can throw "Too many
// requests" during CI which fails services.NewTeleport.
cfg.InstanceMetadataClient = cloud.NewDisabledIMDSClient()
cfg.Hostname = "server01"
cfg.DataDir = t.TempDir()
cfg.Log = utils.NewLoggerForTests()
authAddr := utils.NetAddr{AddrNetwork: "tcp", Addr: net.JoinHostPort("127.0.0.1", ports.Pop())}
cfg.SetToken(staticToken)
cfg.SetAuthServerAddress(authAddr)
cfg.Auth.ListenAddr = authAddr
cfg.Auth.BootstrapResources = options.Bootstrap
cfg.Auth.StorageConfig.Params = backend.Params{defaults.BackendPath: filepath.Join(cfg.DataDir, defaults.BackendDir)}
staticToken, err := types.NewStaticTokens(types.StaticTokensSpecV2{
StaticTokens: []types.ProvisionTokenV1{{
Roles: []types.SystemRole{types.RoleProxy, types.RoleDatabase, types.RoleTrustedCluster, types.RoleNode, types.RoleApp},
Expires: time.Now().Add(time.Minute),
Token: staticToken,
}},
})
require.NoError(t, err)
cfg.Auth.StaticTokens = staticToken
cfg.Proxy.WebAddr = utils.NetAddr{AddrNetwork: "tcp", Addr: net.JoinHostPort("127.0.0.1", ports.Pop())}
cfg.Proxy.SSHAddr = utils.NetAddr{AddrNetwork: "tcp", Addr: net.JoinHostPort("127.0.0.1", ports.Pop())}
cfg.Proxy.ReverseTunnelListenAddr = utils.NetAddr{AddrNetwork: "tcp", Addr: net.JoinHostPort("127.0.0.1", ports.Pop())}
cfg.Proxy.DisableWebInterface = true
cfg.SSH.Addr = utils.NetAddr{AddrNetwork: "tcp", Addr: net.JoinHostPort("127.0.0.1", ports.Pop())}
cfg.SSH.DisableCreateHostUser = true
// Apply options
for _, fn := range options.ConfigFuncs {
fn(cfg)
}
process, err = service.NewTeleport(cfg)
require.NoError(t, err, trace.DebugReport(err))
require.NoError(t, process.Start())
t.Cleanup(func() {
require.NoError(t, process.Close())
require.NoError(t, process.Wait())
})
waitForServices(t, process, cfg)
return process
}
func waitForServices(t *testing.T, auth *service.TeleportProcess, cfg *servicecfg.Config) {
var serviceReadyEvents []string
if cfg.Proxy.Enabled {
serviceReadyEvents = append(serviceReadyEvents, service.ProxyWebServerReady)
}
if cfg.SSH.Enabled {
serviceReadyEvents = append(serviceReadyEvents, service.NodeSSHReady)
}
if cfg.Databases.Enabled {
serviceReadyEvents = append(serviceReadyEvents, service.DatabasesReady)
}
if cfg.Apps.Enabled {
serviceReadyEvents = append(serviceReadyEvents, service.AppsReady)
}
if cfg.Auth.Enabled {
serviceReadyEvents = append(serviceReadyEvents, service.AuthTLSReady)
}
waitForEvents(t, auth, serviceReadyEvents...)
if cfg.Auth.Enabled && cfg.Databases.Enabled {
waitForDatabases(t, auth, cfg.Databases.Databases)
}
}
func waitForEvents(t *testing.T, svc service.Supervisor, events ...string) {
for _, event := range events {
_, err := svc.WaitForEventTimeout(10*time.Second, event)
require.NoError(t, err, "service server didn't receive %v event after 10s", event)
}
}
func waitForDatabases(t *testing.T, auth *service.TeleportProcess, dbs []servicecfg.Database) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
for {
select {
case <-time.After(500 * time.Millisecond):
all, err := auth.GetAuthServer().GetDatabaseServers(ctx, apidefaults.Namespace)
require.NoError(t, err)
// Count how many input "dbs" are registered.
var registered int
for _, db := range dbs {
for _, a := range all {
if a.GetName() == db.Name {
registered++
break
}
}
}
if registered == len(dbs) {
return
}
case <-ctx.Done():
t.Fatal("Databases not registered after 10s")
}
}
}
type TestServersOpts struct {
Bootstrap []types.Resource
ConfigFuncs []func(cfg *servicecfg.Config)
}
type TestServerOptFunc func(o *TestServersOpts)
func WithBootstrap(bootstrap ...types.Resource) TestServerOptFunc {
return func(o *TestServersOpts) {
o.Bootstrap = bootstrap
}
}
func WithConfig(fn func(cfg *servicecfg.Config)) TestServerOptFunc {
return func(o *TestServersOpts) {
o.ConfigFuncs = append(o.ConfigFuncs, fn)
}
}
func WithAuthConfig(fn func(*servicecfg.AuthConfig)) TestServerOptFunc {
return WithConfig(func(cfg *servicecfg.Config) {
fn(&cfg.Auth)
})
}
func WithClusterName(t *testing.T, n string) TestServerOptFunc {
return WithAuthConfig(func(cfg *servicecfg.AuthConfig) {
clusterName, err := services.NewClusterNameWithRandomID(
types.ClusterNameSpecV2{
ClusterName: n,
})
require.NoError(t, err)
cfg.ClusterName = clusterName
})
}
func WithHostname(hostname string) TestServerOptFunc {
return WithConfig(func(cfg *servicecfg.Config) {
cfg.Hostname = hostname
})
}
func WithSSHPublicAddrs(addrs ...string) TestServerOptFunc {
return WithConfig(func(cfg *servicecfg.Config) {
cfg.SSH.PublicAddrs = utils.MustParseAddrList(addrs...)
})
}
func WithSSHLabel(key, value string) TestServerOptFunc {
return WithConfig(func(cfg *servicecfg.Config) {
if cfg.SSH.Labels == nil {
cfg.SSH.Labels = make(map[string]string)
}
cfg.SSH.Labels[key] = value
})
}
type cliModules struct{}
// BuildType returns build type.
func (p *cliModules) BuildType() string {
return "CLI"
}
// PrintVersion prints the Teleport version.
func (p *cliModules) PrintVersion() {
fmt.Println("Teleport CLI")
}
// Features returns supported features
func (p *cliModules) Features() modules.Features {
return modules.Features{
Kubernetes: true,
DB: true,
App: true,
AdvancedAccessWorkflows: true,
AccessControls: true,
}
}
// IsBoringBinary checks if the binary was compiled with BoringCrypto.
func (p *cliModules) IsBoringBinary() bool {
return false
}
// AttestHardwareKey attests a hardware key.
func (p *cliModules) AttestHardwareKey(_ context.Context, _ interface{}, _ keys.PrivateKeyPolicy, _ *keys.AttestationStatement, _ crypto.PublicKey, _ time.Duration) (keys.PrivateKeyPolicy, error) {
return keys.PrivateKeyPolicyNone, nil
}
func (p *cliModules) EnableRecoveryCodes() {
}
func (p *cliModules) EnablePlugins() {
}
func (p *cliModules) SetFeatures(f modules.Features) {
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"fmt"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bytes"

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"context"
@ -41,7 +41,7 @@ type aliasRunner struct {
aliases map[string]string
// runTshMain is a function to run tsh; for example Run().
runTshMain func(ctx context.Context, args []string, opts ...cliOption) error
runTshMain func(ctx context.Context, args []string, opts ...CliOption) error
// runExternalCommand is a function to execute the command passed in as argument; outside of test, it should simply invoke the Run() method.
runExternalCommand func(cmd *exec.Cmd) error
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"context"
@ -34,7 +34,7 @@ func newTestAliasRunner(t *testing.T) *aliasRunner {
t.Fatalf("calling uninitialized function 'setEnv(key=%q,value=%q)'", key, value)
return nil
},
runTshMain: func(ctx context.Context, args []string, opts ...cliOption) error {
runTshMain: func(ctx context.Context, args []string, opts ...CliOption) error {
t.Fatalf("calling uninitialized function 'runTshMain(ctx=%v,args=%v,opts=%v)'", ctx, args, opts)
return nil
},
@ -353,7 +353,7 @@ func Test_runAliasCommand(t *testing.T) {
externalCalls := 0
ar := &aliasRunner{
runTshMain: func(ctx context.Context, args []string, opts ...cliOption) error {
runTshMain: func(ctx context.Context, args []string, opts ...CliOption) error {
mainCalls++
return nil
},

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"crypto/tls"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"fmt"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"
@ -58,8 +58,8 @@ func TestAWS(t *testing.T) {
// Log into Teleport cluster.
err = Run(context.Background(), []string{
"login", "--insecure", "--debug", "--auth", connector.GetName(), "--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, user)
}, setHomePath(tmpHomePath), CliOption(func(cf *CLIConf) error {
cf.MockSSOLogin = mockSSOLogin(t, authServer, user)
return nil
}))
require.NoError(t, err)

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"fmt"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"
@ -57,10 +57,10 @@ func TestAzure(t *testing.T) {
require.NoError(t, err)
// helper function
run := func(args []string, opts ...cliOption) {
run := func(args []string, opts ...CliOption) {
opts = append(opts, setHomePath(tmpHomePath))
opts = append(opts, func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, user)
cf.MockSSOLogin = mockSSOLogin(t, authServer, user)
return nil
})
err := Run(context.Background(), args, opts...)

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"strings"

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"testing"

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"fmt"

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"fmt"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bytes"
@ -147,7 +147,7 @@ func TestAppLoginLeaf(t *testing.T) {
getHelpers := func(t *testing.T) (func(cluster string) string, func(args ...string) string) {
tmpHomePath := t.TempDir()
run := func(args []string, opts ...cliOption) string {
run := func(args []string, opts ...CliOption) string {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
@ -169,7 +169,7 @@ func TestAppLoginLeaf(t *testing.T) {
cluster}
opt := func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, rootAuth.GetAuthServer(), alice)
cf.MockSSOLogin = mockSSOLogin(t, rootAuth.GetAuthServer(), alice)
return nil
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"fmt"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"strings"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -15,7 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
// onDaemonStop implements the "tsh daemon stop" command. It handles graceful shutdown of the daemon
// on Windows, so it's a noop on other platforms. See daemonstop_windows.go for more details.

View file

@ -15,7 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"syscall"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bytes"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bytes"
@ -115,8 +115,8 @@ func TestDatabaseLogin(t *testing.T) {
// Log into Teleport cluster.
err = Run(context.Background(), []string{
"login", "--insecure", "--debug", "--auth", connector.GetName(), "--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
}, setHomePath(tmpHomePath), CliOption(func(cf *CLIConf) error {
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
}))
require.NoError(t, err)
@ -251,8 +251,8 @@ func TestLocalProxyRequirement(t *testing.T) {
// Log into Teleport cluster.
err = Run(context.Background(), []string{
"login", "--insecure", "--debug", "--auth", connector.GetName(), "--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
}, setHomePath(tmpHomePath), CliOption(func(cf *CLIConf) error {
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
}))
require.NoError(t, err)
@ -372,6 +372,7 @@ func TestListDatabase(t *testing.T) {
"--insecure",
"--debug",
}, setCopyStdout(captureStdout))
require.NoError(t, err)
require.Contains(t, captureStdout.String(), "root-postgres")
@ -384,6 +385,7 @@ func TestListDatabase(t *testing.T) {
"--insecure",
"--debug",
}, setCopyStdout(captureStdout))
require.NoError(t, err)
require.Contains(t, captureStdout.String(), "leaf-postgres")
}

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"fmt"

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"crypto/x509"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
const (
// loginUsageFooter is printed at the bottom of `tsh help login` output

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"
@ -1398,8 +1398,8 @@ func updateKubeConfig(cf *CLIConf, tc *client.TeleportClient, path string, overr
func getKubeConfigPath(cf *CLIConf, path string) string {
// cf.kubeConfigPath is used in tests to allow Teleport to run tsh login commands
// in parallel. If defined, it should take precedence over kubeconfig.PathFromEnv().
if path == "" && cf.kubeConfigPath != "" {
path = cf.kubeConfigPath
if path == "" && cf.KubeConfigPath != "" {
path = cf.KubeConfigPath
} else if path == "" {
path = kubeconfig.PathFromEnv()
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bytes"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bufio"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"path"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"fmt"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"testing"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bytes"
@ -290,7 +290,7 @@ func TestProxySSH(t *testing.T) {
s.root.Config.Auth.ClusterName.GetClusterName(),
s.root.Config.SSH.Addr.Port(defaults.SSHServerListenPort))
runProxySSH := func(proxyRequest string, opts ...cliOption) error {
runProxySSH := func(proxyRequest string, opts ...CliOption) error {
return Run(ctx, []string{
"--insecure",
"--proxy", s.root.Config.Proxy.WebAddr.Addr,
@ -398,7 +398,7 @@ func TestProxySSHJumpHost(t *testing.T) {
s := newTestSuite(t, tc.opts...)
runProxySSHJump := func(opts ...cliOption) error {
runProxySSHJump := func(opts ...CliOption) error {
proxyRequest := fmt.Sprintf("%s:%d",
s.leaf.Config.Proxy.SSHAddr.Host(),
s.leaf.Config.SSH.Addr.Port(defaults.SSHServerListenPort))
@ -733,9 +733,9 @@ func disableAgent(t *testing.T) {
t.Setenv(teleport.SSHAuthSock, "")
}
func setMockSSOLogin(t *testing.T, s *suite) cliOption {
func setMockSSOLogin(t *testing.T, s *suite) CliOption {
return func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, s.root.GetAuthServer(), s.user)
cf.MockSSOLogin = mockSSOLogin(t, s.root.GetAuthServer(), s.user)
cf.AuthConnector = s.connector.GetName()
return nil
}
@ -776,7 +776,7 @@ func mustLoginSetEnv(t *testing.T, s *suite, args ...string) string {
return tshHome
}
func mustLoginIdentity(t *testing.T, s *suite, opts ...cliOption) string {
func mustLoginIdentity(t *testing.T, s *suite, opts ...CliOption) string {
identityFile := path.Join(t.TempDir(), "identity.pem")
mustLogin(t, s, "--out", identityFile)
return identityFile
@ -1132,7 +1132,7 @@ func TestPrintProxyAWSTemplate(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
buf := new(bytes.Buffer)
test.inputCLIConf.overrideStdout = buf
test.inputCLIConf.OverrideStdout = buf
require.NoError(t, printProxyAWSTemplate(test.inputCLIConf, test.inputAWSApp))
for _, wantSnippet := range test.wantSnippets {
require.Contains(t, buf.String(), wantSnippet)

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bytes"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"
@ -54,8 +54,8 @@ func TestLoadConfigFromProfile(t *testing.T) {
"--debug",
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
}, setHomePath(tmpHomePath), CliOption(func(cf *CLIConf) error {
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
}))
require.NoError(t, err)
@ -116,8 +116,8 @@ func TestRemoteTctlWithProfile(t *testing.T) {
"--debug",
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
}, setHomePath(tmpHomePath), CliOption(func(cf *CLIConf) error {
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
}))
require.NoError(t, err)
@ -182,8 +182,8 @@ func TestSetAuthServerFlagWhileLoggedIn(t *testing.T) {
"--debug",
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
}, setHomePath(tmpHomePath), CliOption(func(cf *CLIConf) error {
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
}))
require.NoError(t, err)

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"fmt"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bytes"
@ -322,18 +322,18 @@ type CLIConf struct {
// unsetEnvironment unsets Teleport related environment variables.
unsetEnvironment bool
// overrideStdout allows to switch standard output source for resource command. Used in tests.
overrideStdout io.Writer
// OverrideStdout allows to switch standard output source for resource command. Used in tests.
OverrideStdout io.Writer
// overrideStderr allows to switch standard error source for resource command. Used in tests.
overrideStderr io.Writer
// overrideStdin allows to switch standard in source for resource command. Used in tests.
overrideStdin io.Reader
// mockSSOLogin used in tests to override sso login handler in teleport client.
mockSSOLogin client.SSOLoginFunc
// MockSSOLogin used in tests to override sso login handler in teleport client.
MockSSOLogin client.SSOLoginFunc
// mockHeadlessLogin used in tests to override Headless login handler in teleport client.
mockHeadlessLogin client.SSHLoginFunc
// MockHeadlessLogin used in tests to override Headless login handler in teleport client.
MockHeadlessLogin client.SSHLoginFunc
// HomePath is where tsh stores profiles
HomePath string
@ -430,11 +430,11 @@ type CLIConf struct {
// kubeAllNamespaces allows users to search for pods in every namespace.
kubeAllNamespaces bool
// kubeConfigPath is the location of the Kubeconfig for the current test.
// KubeConfigPath is the location of the Kubeconfig for the current test.
// Setting this value allows Teleport tests to run `tsh login` commands in
// parallel.
// It shouldn't be used outside testing.
kubeConfigPath string
KubeConfigPath string
// Client only version display. Skips checking proxy version.
clientOnlyVersionCheck bool
@ -451,12 +451,17 @@ type CLIConf struct {
// HeadlessAuthenticationID is the ID of a headless authentication.
HeadlessAuthenticationID string
// DTAuthnRunCeremony allows tests to override the default device
// authentication function.
// Defaults to [dtauthn.NewCeremony().Run].
DTAuthnRunCeremony client.DTAuthnRunCeremonyFunc
}
// Stdout returns the stdout writer.
func (c *CLIConf) Stdout() io.Writer {
if c.overrideStdout != nil {
return c.overrideStdout
if c.OverrideStdout != nil {
return c.OverrideStdout
}
return os.Stdout
}
@ -490,7 +495,7 @@ func (c *CLIConf) RunCommand(cmd *exec.Cmd) error {
return trace.Wrap(cmd.Run())
}
func main() {
func Main() {
cmdLineOrig := os.Args[1:]
var cmdLine []string
@ -567,8 +572,8 @@ const (
// env vars that tsh status will check to provide hints about active env vars to a user.
var tshStatusEnvVars = [...]string{proxyEnvVar, clusterEnvVar, siteEnvVar, kubeClusterEnvVar, teleport.EnvKubeConfig}
// cliOption is used in tests to inject/override configuration within Run
type cliOption func(*CLIConf) error
// CliOption is used in tests to inject/override configuration within Run
type CliOption func(*CLIConf) error
// initLogger initializes the logger taking into account --debug and TELEPORT_DEBUG. If TELEPORT_DEBUG is set, it will also enable CLIConf.Debug.
func initLogger(cf *CLIConf) {
@ -582,7 +587,7 @@ func initLogger(cf *CLIConf) {
}
// Run executes TSH client. same as main() but easier to test
func Run(ctx context.Context, args []string, opts ...cliOption) error {
func Run(ctx context.Context, args []string, opts ...CliOption) error {
cf := CLIConf{
Context: ctx,
TracingProvider: tracing.NoopProvider(),
@ -3586,9 +3591,10 @@ func loadClientConfigFromCLIConf(cf *CLIConf, proxy string) (*client.Config, err
c.EnableEscapeSequences = cf.EnableEscapeSequences
// pass along mock sso login if provided (only used in tests)
c.MockSSOLogin = cf.mockSSOLogin
c.MockHeadlessLogin = cf.mockHeadlessLogin
// pass along mock functions if provided (only used in tests)
c.MockSSOLogin = cf.MockSSOLogin
c.MockHeadlessLogin = cf.MockHeadlessLogin
c.DTAuthnRunCeremony = cf.DTAuthnRunCeremony
// Set tsh home directory
c.HomePath = cf.HomePath

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"context"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"bufio"
@ -94,7 +94,7 @@ func init() {
// Needed for tests that generate OpenSSH config by tsh config command where
// tsh proxy ssh command is used as ProxyCommand.
if os.Getenv(tshBinMainTestEnv) != "" {
main()
Main()
// main will only exit if there is an error.
// since we are here, there was no error, so we must do so ourselves.
os.Exit(0)
@ -313,7 +313,7 @@ func TestFailedLogin(t *testing.T) {
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = ssoLogin
cf.MockSSOLogin = ssoLogin
return nil
})
require.ErrorIs(t, err, loginFailed)
@ -407,7 +407,7 @@ func TestOIDCLogin(t *testing.T) {
"--proxy", proxyAddr.String(),
"--user", "alice", // explicitly use wrong name
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.SiteName = "localhost"
cf.overrideStderr = buf
return nil
@ -523,7 +523,7 @@ func TestLoginIdentityOut(t *testing.T) {
"--proxy", proxyAddr.String(),
"--out", identPath,
}, tt.extraArgs...), setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
})
require.NoError(t, err)
@ -581,7 +581,7 @@ func TestRelogin(t *testing.T) {
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.overrideStderr = buf
return nil
})
@ -596,7 +596,7 @@ func TestRelogin(t *testing.T) {
"localhost",
}, setHomePath(tmpHomePath),
func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.overrideStderr = buf
return nil
})
@ -618,7 +618,7 @@ func TestRelogin(t *testing.T) {
"--proxy", proxyAddr.String(),
"localhost",
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.overrideStderr = buf
return nil
})
@ -667,7 +667,7 @@ func TestSwitchingProxies(t *testing.T) {
"--auth", connector.GetName(),
"--proxy", proxyAddr1.String(),
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer1, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer1, alice)
return nil
})
require.NoError(t, err)
@ -680,7 +680,7 @@ func TestSwitchingProxies(t *testing.T) {
"--proxy", proxyAddr2.String(),
}, setHomePath(tmpHomePath),
func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer2, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer2, alice)
return nil
})
@ -875,7 +875,7 @@ func TestMakeClient(t *testing.T) {
// makeClient should call Ping on the proxy to fetch SSHProxyAddr
conf = CLIConf{
Proxy: proxyWebAddr.String(),
IdentityFileIn: "../../fixtures/certs/identities/tls.pem",
IdentityFileIn: "../../../fixtures/certs/identities/tls.pem",
Context: context.Background(),
InsecureSkipVerify: true,
}
@ -1494,7 +1494,7 @@ func TestSSHOnMultipleNodes(t *testing.T) {
tt.cluster,
}, setHomePath(tmpHomePath),
func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, tt.auth, alice)
cf.MockSSOLogin = mockSSOLogin(t, tt.auth, alice)
return nil
},
)
@ -1516,8 +1516,8 @@ func TestSSHOnMultipleNodes(t *testing.T) {
setHomePath(tmpHomePath),
func(conf *CLIConf) error {
conf.overrideStdin = &bytes.Buffer{}
conf.overrideStdout = stdout
conf.mockHeadlessLogin = mockHeadlessLogin(t, tt.auth, alice)
conf.OverrideStdout = stdout
conf.MockHeadlessLogin = mockHeadlessLogin(t, tt.auth, alice)
return nil
},
)
@ -1632,8 +1632,8 @@ func TestSSHAccessRequest(t *testing.T) {
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
"--user", "alice",
}, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, rootAuth.GetAuthServer(), alice)
}, setHomePath(tmpHomePath), CliOption(func(cf *CLIConf) error {
cf.MockSSOLogin = mockSSOLogin(t, rootAuth.GetAuthServer(), alice)
return nil
}))
require.NoError(t, err)
@ -1729,8 +1729,8 @@ func TestSSHAccessRequest(t *testing.T) {
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
"--user", "alice",
}, setHomePath(tmpHomePath), cliOption(func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, rootAuth.GetAuthServer(), alice)
}, setHomePath(tmpHomePath), CliOption(func(cf *CLIConf) error {
cf.MockSSOLogin = mockSSOLogin(t, rootAuth.GetAuthServer(), alice)
return nil
}))
require.NoError(t, err)
@ -1847,7 +1847,7 @@ func TestAccessRequestOnLeaf(t *testing.T) {
"--auth", connector.GetName(),
"--proxy", rootProxyAddr.String(),
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, rootAuthServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, rootAuthServer, alice)
return nil
})
require.NoError(t, err)
@ -2006,7 +2006,7 @@ func TestKubeCredentialsLock(t *testing.T) {
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = ssoFunc
cf.MockSSOLogin = ssoFunc
return nil
})
require.NoError(t, err)
@ -2067,7 +2067,7 @@ iUK/veLmZ6XoouiWLCdU1VJz/1Fcwe/IEamg6ETfofvsqOCgcNYJ
"--teleport-cluster", teleportClusterName.GetClusterName(),
"--kube-cluster", kubeClusterName,
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = ssoFunc
cf.MockSSOLogin = ssoFunc
return nil
})
errChan <- credErr
@ -2209,9 +2209,9 @@ func TestSSHHeadless(t *testing.T) {
require.NoError(t, err)
bob.SetRoles([]string{"requester"})
sshHostName := "test-ssh-host"
sshHostname := "test-ssh-host"
rootAuth, rootProxy := makeTestServers(t, withBootstrap(nodeAccess, alice, requester, bob), withConfig(func(cfg *servicecfg.Config) {
cfg.Hostname = sshHostName
cfg.Hostname = sshHostname
cfg.SSH.Enabled = true
cfg.SSH.Addr = utils.NetAddr{AddrNetwork: "tcp", Addr: net.JoinHostPort("127.0.0.1", ports.Pop())}
}))
@ -2262,12 +2262,12 @@ func TestSSHHeadless(t *testing.T) {
"--proxy", proxyAddr.String(),
}, tc.args...)
args = append(args,
fmt.Sprintf("%s@%s", user.Username, sshHostName),
fmt.Sprintf("%s@%s", user.Username, sshHostname),
"echo", "test",
)
err := Run(ctx, args, cliOption(func(cf *CLIConf) error {
cf.mockHeadlessLogin = mockHeadlessLogin(t, rootAuth.GetAuthServer(), alice)
err := Run(ctx, args, CliOption(func(cf *CLIConf) error {
cf.MockHeadlessLogin = mockHeadlessLogin(t, rootAuth.GetAuthServer(), alice)
return nil
}))
tc.assertErr(t, err)
@ -2943,7 +2943,7 @@ func TestAuthClientFromTSHProfile(t *testing.T) {
"--auth", connector.GetName(),
"--proxy", proxyAddr.String(),
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
})
require.NoError(t, err)
@ -3189,39 +3189,39 @@ func mockHeadlessLogin(t *testing.T, authServer *auth.Server, user types.User) c
}
}
func setOverrideStdout(stdout io.Writer) cliOption {
func setOverrideStdout(stdout io.Writer) CliOption {
return func(cf *CLIConf) error {
cf.overrideStdout = stdout
cf.OverrideStdout = stdout
return nil
}
}
func setCopyStdout(stdout io.Writer) cliOption {
func setCopyStdout(stdout io.Writer) CliOption {
return setOverrideStdout(io.MultiWriter(os.Stdout, stdout))
}
func setHomePath(path string) cliOption {
func setHomePath(path string) CliOption {
return func(cf *CLIConf) error {
cf.HomePath = path
return nil
}
}
func setKubeConfigPath(path string) cliOption {
func setKubeConfigPath(path string) CliOption {
return func(cf *CLIConf) error {
cf.kubeConfigPath = path
cf.KubeConfigPath = path
return nil
}
}
func setIdentity(path string) cliOption {
func setIdentity(path string) CliOption {
return func(cf *CLIConf) error {
cf.IdentityFileIn = path
return nil
}
}
func setCmdRunner(cmdRunner func(*exec.Cmd) error) cliOption {
func setCmdRunner(cmdRunner func(*exec.Cmd) error) CliOption {
return func(cf *CLIConf) error {
cf.cmdRunner = cmdRunner
return nil
@ -4304,7 +4304,7 @@ func TestForwardingTraces(t *testing.T) {
"--proxy", proxyAddr.String(),
"--trace",
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
})
require.NoError(t, err)
@ -4423,7 +4423,7 @@ func TestExportingTraces(t *testing.T) {
"--trace",
"--trace-exporter", tshCollector.GRPCAddr(),
}, setHomePath(tmpHomePath), func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, authServer, alice)
cf.MockSSOLogin = mockSSOLogin(t, authServer, alice)
return nil
})
require.NoError(t, err)

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"errors"

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package main
package common
import (
"os"

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package common
import (
"fmt"

25
tool/tsh/main.go Normal file
View file

@ -0,0 +1,25 @@
/*
Copyright 2016-2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
tshcommon "github.com/gravitational/teleport/tool/tsh/common"
)
func main() {
tshcommon.Main()
}