diff --git a/api/client/credentials.go b/api/client/credentials.go index 03e9c5e6e8275..5a55b3d3f0894 100644 --- a/api/client/credentials.go +++ b/api/client/credentials.go @@ -576,16 +576,19 @@ func (d *DynamicIdentityFileCreds) SSHClientConfig() (*ssh.ClientConfig, error) return hostKeyCallback(hostname, remote, key) }, Timeout: defaults.DefaultIOTimeout, - User: userFromSSHCert(d.sshCert), + // We use this because we can't always guarantee that a user will have + // a principal other than this (they may not have access to SSH nodes) + // and the actual user here doesn't matter for auth server API + // authentication. All that matters is that the principal specified here + // is stable across all certificates issued to the user, since this + // value cannot be changed in a following rotation - + // SSHSessionJoinPrincipal is included on all user ssh certs. + // + // This is a bit of a hack - the ideal solution is a refactor of the + // API client in order to support the SSH config being generated at + // time of use, rather than a single SSH config being made dynamic. + // ~ noah + User: "-teleport-internal-join", } return cfg, nil } - -func userFromSSHCert(c *ssh.Certificate) string { - // The KeyId is not always a valid principal, so we prefer the first valid - // principal. - if len(c.ValidPrincipals) > 0 { - return c.ValidPrincipals[0] - } - return c.KeyId -} diff --git a/integrations/access/common/app.go b/integrations/access/common/app.go index d720a893087c8..4a6a4d3afb337 100644 --- a/integrations/access/common/app.go +++ b/integrations/access/common/app.go @@ -36,8 +36,6 @@ import ( const ( // minServerVersion is the minimal teleport version the plugin supports. minServerVersion = "6.1.0-beta.1" - // grpcBackoffMaxDelay is a maximum time gRPC client waits before reconnection attempt. - grpcBackoffMaxDelay = time.Second * 2 // InitTimeout is used to bound execution time of health check and teleport version check. initTimeout = time.Second * 10 // handlerTimeout is used to bound the execution time of watcher event handler. diff --git a/integrations/access/common/config.go b/integrations/access/common/config.go index 2d88fb100b448..887b0eefea8f0 100644 --- a/integrations/access/common/config.go +++ b/integrations/access/common/config.go @@ -19,16 +19,9 @@ package common import ( "context" - "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" - "google.golang.org/grpc" - grpcbackoff "google.golang.org/grpc/backoff" - - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/integrations/access/common/teleport" "github.com/gravitational/teleport/integrations/lib" - "github.com/gravitational/teleport/integrations/lib/credentials" "github.com/gravitational/teleport/integrations/lib/logger" ) @@ -51,32 +44,7 @@ func (c BaseConfig) GetRecipients() RawRecipientsMap { } func (c BaseConfig) GetTeleportClient(ctx context.Context) (teleport.Client, error) { - if validCred, err := credentials.CheckIfExpired(c.Teleport.Credentials()); err != nil { - log.Warn(err) - if !validCred { - return nil, trace.BadParameter( - "No valid credentials found, this likely means credentials are expired. In this case, please sign new credentials and increase their TTL if needed.", - ) - } - log.Info("At least one non-expired credential has been found, continuing startup") - } - - bk := grpcbackoff.DefaultConfig - bk.MaxDelay = grpcBackoffMaxDelay - - clt, err := client.New(ctx, client.Config{ - Addrs: c.Teleport.GetAddrs(), - Credentials: c.Teleport.Credentials(), - DialOpts: []grpc.DialOption{ - grpc.WithConnectParams(grpc.ConnectParams{Backoff: bk, MinConnectTimeout: initTimeout}), - grpc.WithReturnConnectionError(), - }, - }) - if err != nil { - return nil, trace.Wrap(err) - } - - return clt, nil + return c.Teleport.NewClient(ctx) } // GetPluginType returns the type of plugin this config is for. diff --git a/integrations/access/discord/config.go b/integrations/access/discord/config.go index 648b8a3ab0c87..3e38d85237384 100644 --- a/integrations/access/discord/config.go +++ b/integrations/access/discord/config.go @@ -82,7 +82,7 @@ func (c *Config) GetTeleportClient(ctx context.Context) (teleport.Client, error) if c.Client != nil { return c.Client, nil } - return c.BaseConfig.GetTeleportClient(ctx) + return c.BaseConfig.Teleport.NewClient(ctx) } // NewBot initializes the new Discord message generator (DiscordBot) diff --git a/integrations/access/jira/app.go b/integrations/access/jira/app.go index ca55caf693add..f6c61a6a12d68 100644 --- a/integrations/access/jira/app.go +++ b/integrations/access/jira/app.go @@ -26,10 +26,7 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "google.golang.org/grpc" - grpcbackoff "google.golang.org/grpc/backoff" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" apiutils "github.com/gravitational/teleport/api/utils" @@ -37,7 +34,6 @@ import ( "github.com/gravitational/teleport/integrations/access/common/teleport" "github.com/gravitational/teleport/integrations/lib" "github.com/gravitational/teleport/integrations/lib/backoff" - "github.com/gravitational/teleport/integrations/lib/credentials" "github.com/gravitational/teleport/integrations/lib/logger" "github.com/gravitational/teleport/integrations/lib/watcherjob" ) @@ -47,8 +43,6 @@ const ( minServerVersion = "6.1.0" // pluginName is used to tag PluginData and as a Delegator in Audit log. pluginName = "jira" - // grpcBackoffMaxDelay is a maximum time gRPC client waits before reconnection attempt. - grpcBackoffMaxDelay = time.Second * 2 // initTimeout is used to bound execution time of health check and teleport version check. initTimeout = time.Second * 10 // handlerTimeout is used to bound the execution time of watcher event handler. @@ -169,43 +163,14 @@ func (a *App) run(ctx context.Context) error { return trace.NewAggregate(httpErr, watcherJob.Err()) } -func (a *App) createTeleportClient(ctx context.Context) error { - log := logger.Get(ctx) - - if validCred, err := credentials.CheckIfExpired(a.conf.Teleport.Credentials()); err != nil { - log.Warn(err) - if !validCred { - return trace.BadParameter( - "No valid credentials found, this likely means credentials are expired. In this case, please sign new credentials and increase their TTL if needed.", - ) - } - log.Info("At least one non-expired credential has been found, continuing startup") - } - - var err error - bk := grpcbackoff.DefaultConfig - bk.MaxDelay = grpcBackoffMaxDelay - if a.teleport, err = client.New(ctx, client.Config{ - Addrs: a.conf.Teleport.GetAddrs(), - Credentials: a.conf.Teleport.Credentials(), - DialOpts: []grpc.DialOption{ - grpc.WithConnectParams(grpc.ConnectParams{Backoff: bk, MinConnectTimeout: initTimeout}), - grpc.WithReturnConnectionError(), - }, - }); err != nil { - return trace.Wrap(err) - } - - return nil -} - func (a *App) init(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() log := logger.Get(ctx) + var err error if a.teleport == nil { - if err := a.createTeleportClient(ctx); err != nil { + if a.teleport, err = a.conf.Teleport.NewClient(ctx); err != nil { return trace.Wrap(err) } } diff --git a/integrations/access/opsgenie/app.go b/integrations/access/opsgenie/app.go index 153fac9f56f4e..fcdb9f5baa9cf 100644 --- a/integrations/access/opsgenie/app.go +++ b/integrations/access/opsgenie/app.go @@ -25,17 +25,13 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "google.golang.org/grpc" - grpcbackoff "google.golang.org/grpc/backoff" tp "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/integrations/access/common/teleport" "github.com/gravitational/teleport/integrations/lib" "github.com/gravitational/teleport/integrations/lib/backoff" - "github.com/gravitational/teleport/integrations/lib/credentials" "github.com/gravitational/teleport/integrations/lib/logger" "github.com/gravitational/teleport/integrations/lib/watcherjob" ) @@ -45,8 +41,6 @@ const ( pluginName = "opsgenie" // minServerVersion is the minimal teleport version the plugin supports. minServerVersion = "6.1.0" - // grpcBackoffMaxDelay is a maximum time gRPC client waits before reconnection attempt. - grpcBackoffMaxDelay = time.Second * 2 // initTimeout is used to bound execution time of health check and teleport version check. initTimeout = time.Second * 10 // handlerTimeout is used to bound the execution time of watcher event handler. @@ -73,13 +67,8 @@ type App struct { // NewOpsgenieApp initializes a new teleport-opsgenie app and returns it. func NewOpsgenieApp(ctx context.Context, conf *Config) (*App, error) { - teleportClient, err := conf.GetTeleportClient(ctx) - if err != nil { - return nil, trace.Wrap(err) - } opsgenieApp := &App{ PluginName: pluginName, - teleport: teleportClient, conf: *conf, } opsgenieApp.mainJob = lib.NewServiceJob(opsgenieApp.run) @@ -147,31 +136,10 @@ func (a *App) run(ctx context.Context) error { func (a *App) init(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() - log := logger.Get(ctx) - - if validCred, err := credentials.CheckIfExpired(a.conf.Teleport.Credentials()); err != nil { - log.Warnf("Invalid Teleport credentials: %v", err) - if !validCred { - return trace.BadParameter( - "No valid credentials found, this likely means credentials are expired. In this case, please sign new credentials and increase their TTL if needed.", - ) - } - log.Info("At least one non-expired credential has been found, continuing startup") - } var err error - if a.teleport == nil { - bk := grpcbackoff.DefaultConfig - bk.MaxDelay = grpcBackoffMaxDelay - if a.teleport, err = client.New(ctx, client.Config{ - Addrs: a.conf.Teleport.GetAddrs(), - Credentials: a.conf.Teleport.Credentials(), - DialOpts: []grpc.DialOption{ - grpc.WithConnectParams(grpc.ConnectParams{Backoff: bk, MinConnectTimeout: initTimeout}), - grpc.WithReturnConnectionError(), - }, - }); err != nil { + if a.teleport, err = a.conf.Teleport.NewClient(ctx); err != nil { return trace.Wrap(err) } } diff --git a/integrations/access/pagerduty/app.go b/integrations/access/pagerduty/app.go index 2281ff63e632d..23723a57effce 100644 --- a/integrations/access/pagerduty/app.go +++ b/integrations/access/pagerduty/app.go @@ -25,17 +25,13 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "google.golang.org/grpc" - grpcbackoff "google.golang.org/grpc/backoff" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/integrations/access/common" "github.com/gravitational/teleport/integrations/access/common/teleport" "github.com/gravitational/teleport/integrations/lib" "github.com/gravitational/teleport/integrations/lib/backoff" - "github.com/gravitational/teleport/integrations/lib/credentials" "github.com/gravitational/teleport/integrations/lib/logger" "github.com/gravitational/teleport/integrations/lib/watcherjob" ) @@ -45,8 +41,6 @@ const ( minServerVersion = "6.1.0" // pluginName is used to tag PluginData and as a Delegator in Audit log. pluginName = "pagerduty" - // grpcBackoffMaxDelay is a maximum time gRPC client waits before reconnection attempt. - grpcBackoffMaxDelay = time.Second * 2 // initTimeout is used to bound execution time of health check and teleport version check. initTimeout = time.Second * 10 // handlerTimeout is used to bound the execution time of watcher event handler. @@ -147,32 +141,13 @@ func (a *App) init(ctx context.Context) error { defer cancel() log := logger.Get(ctx) - if validCred, err := credentials.CheckIfExpired(a.conf.Teleport.Credentials()); err != nil { - log.Warn(err) - if !validCred { - return trace.BadParameter( - "No valid credentials found, this likely means credentials are expired. In this case, please sign new credentials and increase their TTL if needed.", - ) - } - log.Info("At least one non-expired credential has been found, continuing startup") - } - var ( err error pong proto.PingResponse ) if a.teleport == nil { - bk := grpcbackoff.DefaultConfig - bk.MaxDelay = grpcBackoffMaxDelay - if a.teleport, err = client.New(ctx, client.Config{ - Addrs: a.conf.Teleport.GetAddrs(), - Credentials: a.conf.Teleport.Credentials(), - DialOpts: []grpc.DialOption{ - grpc.WithConnectParams(grpc.ConnectParams{Backoff: bk, MinConnectTimeout: initTimeout}), - grpc.WithReturnConnectionError(), - }, - }); err != nil { + if a.teleport, err = a.conf.Teleport.NewClient(ctx); err != nil { return trace.Wrap(err) } } diff --git a/integrations/access/servicenow/app.go b/integrations/access/servicenow/app.go index 3a370cad1d86a..029f0641d3779 100644 --- a/integrations/access/servicenow/app.go +++ b/integrations/access/servicenow/app.go @@ -27,17 +27,13 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "golang.org/x/exp/slices" - "google.golang.org/grpc" - grpcbackoff "google.golang.org/grpc/backoff" tp "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/integrations/access/common/teleport" "github.com/gravitational/teleport/integrations/lib" "github.com/gravitational/teleport/integrations/lib/backoff" - "github.com/gravitational/teleport/integrations/lib/credentials" "github.com/gravitational/teleport/integrations/lib/logger" "github.com/gravitational/teleport/integrations/lib/watcherjob" ) @@ -47,8 +43,6 @@ const ( pluginName = "servicenow" // minServerVersion is the minimal teleport version the plugin supports. minServerVersion = "13.0.0" - // grpcBackoffMaxDelay is a maximum time GRPC client waits before reconnection attempt. - grpcBackoffMaxDelay = time.Second * 2 // initTimeout is used to bound execution time of health check and teleport version check. initTimeout = time.Second * 10 // handlerTimeout is used to bound the execution time of watcher event handler. @@ -75,13 +69,8 @@ type App struct { // NewServicenowApp initializes a new teleport-servicenow app and returns it. func NewServiceNowApp(ctx context.Context, conf *Config) (*App, error) { - teleportClient, err := conf.GetTeleportClient(ctx) - if err != nil { - return nil, trace.Wrap(err) - } serviceNowApp := &App{ PluginName: pluginName, - teleport: teleportClient, conf: *conf, } serviceNowApp.mainJob = lib.NewServiceJob(serviceNowApp.run) @@ -147,31 +136,10 @@ func (a *App) run(ctx context.Context) error { func (a *App) init(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() - log := logger.Get(ctx) - - if validCred, err := credentials.CheckIfExpired(a.conf.Teleport.Credentials()); err != nil { - log.Warnf("Invalid Teleport credentials: %v", err) - if !validCred { - return trace.BadParameter( - "No valid credentials found, this likely means credentials are expired. In this case, please sign new credentials and increase their TTL if needed.", - ) - } - log.Info("At least one non-expired credential has been found, continuing startup") - } var err error - if a.teleport == nil { - bk := grpcbackoff.DefaultConfig - bk.MaxDelay = grpcBackoffMaxDelay - if a.teleport, err = client.New(ctx, client.Config{ - Addrs: a.conf.Teleport.GetAddrs(), - Credentials: a.conf.Teleport.Credentials(), - DialOpts: []grpc.DialOption{ - grpc.WithConnectParams(grpc.ConnectParams{Backoff: bk, MinConnectTimeout: initTimeout}), - grpc.WithReturnConnectionError(), - }, - }); err != nil { + if a.teleport, err = a.conf.Teleport.NewClient(ctx); err != nil { return trace.Wrap(err) } } diff --git a/integrations/lib/config.go b/integrations/lib/config.go index c13e3de8a165b..6e45d73fae836 100644 --- a/integrations/lib/config.go +++ b/integrations/lib/config.go @@ -15,45 +15,48 @@ package lib import ( + "context" "io" "os" "strings" + "time" "github.com/gravitational/trace" log "github.com/sirupsen/logrus" + "google.golang.org/grpc" + grpcbackoff "google.golang.org/grpc/backoff" "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/integrations/lib/credentials" "github.com/gravitational/teleport/integrations/lib/stringset" ) +const ( + // grpcBackoffMaxDelay is a maximum time gRPC client waits before reconnection attempt. + grpcBackoffMaxDelay = time.Second * 2 + // initTimeout is used to bound execution time of health check and teleport version check. + initTimeout = time.Second * 10 +) + // TeleportConfig stores config options for where // the Teleport's Auth server is listening, and what certificates to // use to authenticate in it. type TeleportConfig struct { + // AuthServer specifies the address that the client should connect to. + // Deprecated: replaced by Addr AuthServer string `toml:"auth_server"` Addr string `toml:"addr"` - Identity string `toml:"identity"` - ClientKey string `toml:"client_key"` - ClientCrt string `toml:"client_crt"` - RootCAs string `toml:"root_cas"` -} -func (cfg TeleportConfig) GetAddrs() []string { - if cfg.Addr != "" { - return []string{cfg.Addr} - } else if cfg.AuthServer != "" { - return []string{cfg.AuthServer} - } - return nil + ClientKey string `toml:"client_key"` + ClientCrt string `toml:"client_crt"` + RootCAs string `toml:"root_cas"` + + Identity string `toml:"identity"` + RefreshIdentity bool `toml:"refresh_identity"` + RefreshIdentityInterval time.Duration `toml:"refresh_identity_interval"` } func (cfg *TeleportConfig) CheckAndSetDefaults() error { - if cfg.Addr == "" && cfg.AuthServer == "" { - cfg.Addr = "localhost:3025" - } else if cfg.AuthServer != "" { - log.Warn("Configuration setting `auth_server` is deprecated, consider to change it to `addr`") - } - if err := cfg.CheckTLSConfig(); err != nil { return trace.Wrap(err) } @@ -62,6 +65,14 @@ func (cfg *TeleportConfig) CheckAndSetDefaults() error { return trace.BadParameter("configuration setting `identity` is mutually exclusive with all the `client_crt`, `client_key` and `root_cas` settings") } + // Default to refreshing identity minutely. + if cfg.RefreshIdentityInterval == 0 { + cfg.RefreshIdentityInterval = time.Minute + } + if cfg.RefreshIdentity && cfg.Identity == "" { + return trace.BadParameter("`refresh_identity` requires that `identity` be set") + } + return nil } @@ -98,31 +109,107 @@ func (cfg *TeleportConfig) CheckTLSConfig() error { return nil } -func (cfg TeleportConfig) Credentials() []client.Credentials { - switch true { - case cfg.Identity != "": - return []client.Credentials{client.LoadIdentityFile(cfg.Identity)} +// NewIdentityFileWatcher returns a credential compatible with the Teleport +// client. This credential will reload from the identity file at the specified +// path each time interval time passes. This function blocks until the initial +// credential has been loaded and then returns, creating a goroutine in the +// background to manage the reloading that will exit when ctx is canceled. +func NewIdentityFileWatcher(ctx context.Context, path string, interval time.Duration) (*client.DynamicIdentityFileCreds, error) { + dynamicCred, err := client.NewDynamicIdentityFileCreds(path) + if err != nil { + return nil, trace.Wrap(err, "creating dynamic identity file watcher") + } + + go func() { + timer := time.NewTimer(interval) + defer timer.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-timer.C: + } + + if err := dynamicCred.Reload(); err != nil { + log.WithError(err).Error("Failed to reload identity file from disk.") + } + timer.Reset(interval) + } + }() + + return dynamicCred, nil +} + +func (cfg TeleportConfig) NewClient(ctx context.Context) (*client.Client, error) { + addr := "localhost:3025" + switch { + case cfg.Addr != "": + addr = cfg.Addr + case cfg.AuthServer != "": + log.Warn("Configuration setting `auth_server` is deprecated, consider to change it to `addr`") + addr = cfg.AuthServer + } + + var creds []client.Credentials + switch { + case cfg.Identity != "" && !cfg.RefreshIdentity: + creds = []client.Credentials{client.LoadIdentityFile(cfg.Identity)} + case cfg.Identity != "" && cfg.RefreshIdentity: + cred, err := NewIdentityFileWatcher(ctx, cfg.Identity, cfg.RefreshIdentityInterval) + if err != nil { + return nil, trace.Wrap(err) + } + creds = []client.Credentials{cred} case cfg.ClientCrt != "" && cfg.ClientKey != "" && cfg.RootCAs != "": - return []client.Credentials{client.LoadKeyPair(cfg.ClientCrt, cfg.ClientKey, cfg.RootCAs)} + creds = []client.Credentials{client.LoadKeyPair(cfg.ClientCrt, cfg.ClientKey, cfg.RootCAs)} default: - return nil + return nil, trace.BadParameter("no credentials configured") + } + + if validCred, err := credentials.CheckIfExpired(creds); err != nil { + log.Warn(err) + if !validCred { + return nil, trace.BadParameter( + "No valid credentials found, this likely means credentials are expired. In this case, please sign new credentials and increase their TTL if needed.", + ) + } + log.Info("At least one non-expired credential has been found, continuing startup") } + + bk := grpcbackoff.DefaultConfig + bk.MaxDelay = grpcBackoffMaxDelay + clt, err := client.New(ctx, client.Config{ + Addrs: []string{addr}, + Credentials: creds, + DialOpts: []grpc.DialOption{ + grpc.WithConnectParams(grpc.ConnectParams{Backoff: bk, MinConnectTimeout: initTimeout}), + grpc.WithReturnConnectionError(), + }, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + return clt, nil } // ReadPassword reads password from file or env var, trims and returns func ReadPassword(filename string) (string, error) { f, err := os.Open(filename) - if os.IsNotExist(err) { - return "", trace.BadParameter("Error reading password from %v", filename) - } if err != nil { + if os.IsNotExist(err) { + return "", trace.BadParameter("Error reading password from %v", filename) + } return "", trace.Wrap(err) } + pass := make([]byte, 2000) l, err := f.Read(pass) if err != nil && err != io.EOF { return "", err } + pass = pass[:l] // truncate \0 return strings.TrimSpace(string(pass)), nil }