Skip to content

Commit

Permalink
Access Plugins: Support dynamic credential reloading (#32974)
Browse files Browse the repository at this point in the history
* Refactor client creation in access plugins

* Remove unnecessary client creation

* Remove unused constant

* Fix imports

* Add dynamic watcher mode

* Export NewIdentityFileWatcher

* Add godoc for exported func

* Americanize Speling

* Change username used by dynamic ssh credentials
  • Loading branch information
strideynet authored Nov 1, 2023
1 parent f5f7644 commit 516e15d
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 202 deletions.
23 changes: 13 additions & 10 deletions api/client/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 0 additions & 2 deletions integrations/access/common/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
34 changes: 1 addition & 33 deletions integrations/access/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion integrations/access/discord/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
39 changes: 2 additions & 37 deletions integrations/access/jira/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,14 @@ 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"
"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"
)
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
}
Expand Down
34 changes: 1 addition & 33 deletions integrations/access/opsgenie/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
}
Expand Down
27 changes: 1 addition & 26 deletions integrations/access/pagerduty/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
}
Expand Down
34 changes: 1 addition & 33 deletions integrations/access/servicenow/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
}
Expand Down
Loading

0 comments on commit 516e15d

Please sign in to comment.