diff --git a/integration/autoupdate/tools/updater_test.go b/integration/autoupdate/tools/updater_test.go index 0ef2eabb4aa41..0491a63ac272a 100644 --- a/integration/autoupdate/tools/updater_test.go +++ b/integration/autoupdate/tools/updater_test.go @@ -66,8 +66,10 @@ func TestUpdate(t *testing.T) { // Verify that the installed version is equal to requested one. cmd := exec.CommandContext(ctx, tctlPath, "version") + var stderr bytes.Buffer + cmd.Stderr = &stderr out, err := cmd.Output() - require.NoError(t, err) + require.NoError(t, err, stderr.String()) matches := pattern.FindStringSubmatch(string(out)) require.Len(t, matches, 2) diff --git a/tool/tctl/common/client/auth.go b/tool/tctl/common/client/auth.go index 1a5ea200c713b..b278ee43b3719 100644 --- a/tool/tctl/common/client/auth.go +++ b/tool/tctl/common/client/auth.go @@ -38,7 +38,7 @@ import ( "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/tool/common" - tctlcfg "github.com/gravitational/teleport/tool/tctl/common/config" + "github.com/gravitational/teleport/tool/tctl/common/config" ) // InitFunc initiates connection to auth service, makes ping request and return the client instance. @@ -47,9 +47,10 @@ import ( type InitFunc func(ctx context.Context) (client *authclient.Client, close func(context.Context), err error) // GetInitFunc wraps lazy loading auth init function for commands which requires the auth client. -func GetInitFunc(ccf tctlcfg.GlobalCLIFlags, cfg *servicecfg.Config) InitFunc { +func GetInitFunc(cfg *servicecfg.Config, getAuthFn config.LoadAuthFunc) InitFunc { return func(ctx context.Context) (*authclient.Client, func(context.Context), error) { - clientConfig, err := tctlcfg.ApplyConfig(&ccf, cfg) + // Lazy load auth configuration + clientConfig, err := getAuthFn(cfg) if err != nil { return nil, nil, trace.Wrap(err) } @@ -70,7 +71,7 @@ func GetInitFunc(ccf tctlcfg.GlobalCLIFlags, cfg *servicecfg.Config) InitFunc { dialer, err := reversetunnelclient.NewTunnelAuthDialer(reversetunnelclient.TunnelAuthDialerConfig{ Resolver: resolver, ClientConfig: clientConfig.SSH, - Log: cfg.Logger, + Log: clientConfig.Log, InsecureSkipTLSVerify: clientConfig.Insecure, GetClusterCAs: apiclient.ClusterCAsFromCertPool(clientConfig.TLS.RootCAs), }) @@ -87,7 +88,7 @@ func GetInitFunc(ccf tctlcfg.GlobalCLIFlags, cfg *servicecfg.Config) InitFunc { } fmt.Fprintf(os.Stderr, "ERROR: Cannot connect to the auth server. Is the auth server running on %q?\n", - cfg.AuthServerAddresses()[0].Addr) + clientConfig.AuthServers[0].Addr) return nil, nil, trace.NewAggregate(&common.ExitCodeError{Code: 1}, err) } diff --git a/tool/tctl/common/config/global.go b/tool/tctl/common/config/global.go index 0b357db8e848b..7b9a48f91d727 100644 --- a/tool/tctl/common/config/global.go +++ b/tool/tctl/common/config/global.go @@ -59,12 +59,14 @@ type GlobalCLIFlags struct { Insecure bool } +type LoadAuthFunc func(cfg *servicecfg.Config) (*authclient.Config, error) + // ApplyConfig takes configuration values from the config file and applies them // to 'servicecfg.Config' object. // -// The returned authclient.Config has the credentials needed to dial the auth +// The returned LoadAuthFunc can be used to create the credentials needed to dial the auth // server. -func ApplyConfig(ccf *GlobalCLIFlags, cfg *servicecfg.Config) (*authclient.Config, error) { +func ApplyConfig(ccf *GlobalCLIFlags, cfg *servicecfg.Config) (LoadAuthFunc, error) { ctx := context.TODO() // --debug flag if ccf.Debug { @@ -132,8 +134,11 @@ func ApplyConfig(ccf *GlobalCLIFlags, cfg *servicecfg.Config) (*authclient.Confi } authConfig, err := LoadConfigFromProfile(ccf, cfg) if err == nil { - return authConfig, nil + return func(_ *servicecfg.Config) (*authclient.Config, error) { + return authConfig, nil + }, nil } + if !trace.IsNotFound(err) { return nil, trace.Wrap(err) } else if runtime.GOOS == constants.WindowsOS { @@ -158,40 +163,41 @@ func ApplyConfig(ccf *GlobalCLIFlags, cfg *servicecfg.Config) (*authclient.Confi } } - authConfig := new(authclient.Config) - // read the host UUID only in case the identity was not provided, - // because it will be used for reading local auth server identity - cfg.HostUUID, err = hostid.ReadFile(cfg.DataDir) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return nil, trace.Wrap(err, "Could not load Teleport host UUID file at %s. "+ - "Please make sure that a Teleport Auth Service instance is running on this host prior to using tctl or provide credentials by logging in with tsh first.", - filepath.Join(cfg.DataDir, hostid.FileName)) - } else if errors.Is(err, fs.ErrPermission) { - return nil, trace.Wrap(err, "Teleport does not have permission to read Teleport host UUID file at %s. "+ - "Ensure that you are running as a user with appropriate permissions or provide credentials by logging in with tsh first.", - filepath.Join(cfg.DataDir, hostid.FileName)) + return func(cfg *servicecfg.Config) (*authclient.Config, error) { + authConfig := new(authclient.Config) + // read the host UUID only in case the identity was not provided, + // because it will be used for reading local auth server identity + cfg.HostUUID, err = hostid.ReadFile(cfg.DataDir) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil, trace.Wrap(err, "Could not load Teleport host UUID file at %s. "+ + "Please make sure that a Teleport Auth Service instance is running on this host prior to using tctl or provide credentials by logging in with tsh first.", + filepath.Join(cfg.DataDir, hostid.FileName)) + } else if errors.Is(err, fs.ErrPermission) { + return nil, trace.Wrap(err, "Teleport does not have permission to read Teleport host UUID file at %s. "+ + "Ensure that you are running as a user with appropriate permissions or provide credentials by logging in with tsh first.", + filepath.Join(cfg.DataDir, hostid.FileName)) + } + return nil, trace.Wrap(err) } - return nil, trace.Wrap(err) - } - identity, err := storage.ReadLocalIdentity(filepath.Join(cfg.DataDir, teleport.ComponentProcess), state.IdentityID{Role: types.RoleAdmin, HostUUID: cfg.HostUUID}) - if err != nil { - // The "admin" identity is not present? This means the tctl is running - // NOT on the auth server - if trace.IsNotFound(err) { - return nil, trace.AccessDenied("tctl must be used on an Auth Service host or provided with credentials by logging in with tsh first.") + identity, err := storage.ReadLocalIdentity(filepath.Join(cfg.DataDir, teleport.ComponentProcess), state.IdentityID{Role: types.RoleAdmin, HostUUID: cfg.HostUUID}) + if err != nil { + // The "admin" identity is not present? This means the tctl is running + // NOT on the auth server + if trace.IsNotFound(err) { + return nil, trace.AccessDenied("tctl must be used on an Auth Service host or provided with credentials by logging in with tsh first.") + } + return nil, trace.Wrap(err) } - return nil, trace.Wrap(err) - } - authConfig.TLS, err = identity.TLSConfig(cfg.CipherSuites) - if err != nil { - return nil, trace.Wrap(err) - } - authConfig.TLS.InsecureSkipVerify = ccf.Insecure - authConfig.Insecure = ccf.Insecure - authConfig.AuthServers = cfg.AuthServerAddresses() - authConfig.Log = cfg.Logger - authConfig.DialOpts = append(authConfig.DialOpts, metadata.WithUserAgentFromTeleportComponent(teleport.ComponentTCTL)) - - return authConfig, nil + authConfig.TLS, err = identity.TLSConfig(cfg.CipherSuites) + if err != nil { + return nil, trace.Wrap(err) + } + authConfig.TLS.InsecureSkipVerify = ccf.Insecure + authConfig.Insecure = ccf.Insecure + authConfig.AuthServers = cfg.AuthServerAddresses() + authConfig.Log = cfg.Logger + authConfig.DialOpts = append(authConfig.DialOpts, metadata.WithUserAgentFromTeleportComponent(teleport.ComponentTCTL)) + return authConfig, nil + }, nil } diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go index a8f671c5ad37e..1040394b22aec 100644 --- a/tool/tctl/common/tctl.go +++ b/tool/tctl/common/tctl.go @@ -181,7 +181,13 @@ func TryRun(ctx context.Context, commands []CLICommand, args []string) error { cfg.Debug = ccf.Debug - clientFunc := commonclient.GetInitFunc(ccf, cfg) + getAuthFn, err := tctlcfg.ApplyConfig(&ccf, cfg) + if err != nil { + return trace.Wrap(err) + } + + clientFunc := commonclient.GetInitFunc(cfg, getAuthFn) + // Execute whatever is selected. for _, c := range commands { match, err := c.TryRun(ctx, selectedCmd, clientFunc)