From 46ff29f072311cd7caeb328f73f628d06adef651 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 30 Mar 2023 17:42:14 +0100 Subject: [PATCH] Machine ID FIPS support (#23563) * Machine ID `tbot` FIPS support * Add GoDoc for bot CLI --- integrations/operator/sidecar/tbot.go | 2 +- lib/tbot/config/config.go | 27 +++++++++++++++++++ lib/tbot/identity/identity.go | 17 +++++++++--- lib/tbot/renew.go | 6 +++-- lib/tbot/tbot.go | 11 ++++++++ tool/tbot/main.go | 1 + tool/tbot/main_test.go | 1 + .../all_parameters_provided/file.golden | 1 + .../all_parameters_provided/stdout.golden | 1 + .../no_parameters_provided/file.golden | 1 + .../no_parameters_provided/stdout.golden | 1 + 11 files changed, 63 insertions(+), 6 deletions(-) diff --git a/integrations/operator/sidecar/tbot.go b/integrations/operator/sidecar/tbot.go index 1250258922920..a0f956b5607c9 100644 --- a/integrations/operator/sidecar/tbot.go +++ b/integrations/operator/sidecar/tbot.go @@ -155,7 +155,7 @@ func (c clientCredentials) TLSConfig() (*tls.Config, error) { } func (c clientCredentials) SSHClientConfig() (*ssh.ClientConfig, error) { - return c.id.SSHClientConfig() + return c.id.SSHClientConfig(false) } func (b *Bot) NeedLeaderElection() bool { diff --git a/lib/tbot/config/config.go b/lib/tbot/config/config.go index 7a831719b3df3..9f0fdef837b6e 100644 --- a/lib/tbot/config/config.go +++ b/lib/tbot/config/config.go @@ -31,6 +31,7 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/utils" ) @@ -151,6 +152,14 @@ type CLIConf struct { // RemainingArgs is the remaining string arguments for commands that // require them. RemainingArgs []string + + // FIPS instructs `tbot` to run in a mode designed to comply with FIPS + // regulations. This means the bot should: + // - Refuse to run if not compiled with boringcrypto + // - Use FIPS relevant endpoints for cloud providers (e.g AWS) + // - Restrict TLS / SSH cipher suites and TLS version + // - RSA2048 should be used for private key generation + FIPS bool } // AzureOnboardingConfig holds configuration relevant to the "azure" join method. @@ -227,6 +236,20 @@ type BotConfig struct { CertificateTTL time.Duration `yaml:"certificate_ttl"` RenewalInterval time.Duration `yaml:"renewal_interval"` Oneshot bool `yaml:"oneshot"` + // FIPS instructs `tbot` to run in a mode designed to comply with FIPS + // regulations. This means the bot should: + // - Refuse to run if not compiled with boringcrypto + // - Use FIPS relevant endpoints for cloud providers (e.g AWS) + // - Restrict TLS / SSH cipher suites and TLS version + // - RSA2048 should be used for private key generation + FIPS bool `yaml:"fips"` +} + +func (conf *BotConfig) CipherSuites() []uint16 { + if conf.FIPS { + return defaults.FIPSCipherSuites + } + return utils.DefaultCipherSuites() } func (conf *BotConfig) CheckAndSetDefaults() error { @@ -438,6 +461,10 @@ func FromCLIConf(cf *CLIConf) (*BotConfig, error) { config.Onboarding.SetToken(cf.Token) } + if cf.FIPS { + config.FIPS = cf.FIPS + } + if err := config.CheckAndSetDefaults(); err != nil { return nil, trace.Wrap(err, "validating merged bot config") } diff --git a/lib/tbot/identity/identity.go b/lib/tbot/identity/identity.go index 54249cb7d4edd..ffac638e3ff13 100644 --- a/lib/tbot/identity/identity.go +++ b/lib/tbot/identity/identity.go @@ -33,6 +33,7 @@ import ( apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/api/utils/keys" apisshutils "github.com/gravitational/teleport/api/utils/sshutils" + "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/tbot/bot" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" @@ -228,10 +229,11 @@ func (i *Identity) getSSHCheckers() ([]ssh.PublicKey, error) { // SSHClientConfig returns a ssh.ClientConfig used by the bot to connect to // the reverse tunnel server. -func (i *Identity) SSHClientConfig() (*ssh.ClientConfig, error) { +func (i *Identity) SSHClientConfig(fips bool) (*ssh.ClientConfig, error) { callback, err := apisshutils.NewHostKeyCallback( apisshutils.HostKeyCallbackConfig{ GetHostCheckers: i.getSSHCheckers, + FIPS: fips, }) if err != nil { return nil, trace.Wrap(err) @@ -239,12 +241,21 @@ func (i *Identity) SSHClientConfig() (*ssh.ClientConfig, error) { if len(i.SSHCert.ValidPrincipals) < 1 { return nil, trace.BadParameter("user cert has no valid principals") } - return &ssh.ClientConfig{ + config := &ssh.ClientConfig{ User: i.SSHCert.ValidPrincipals[0], Auth: []ssh.AuthMethod{ssh.PublicKeys(i.KeySigner)}, HostKeyCallback: callback, Timeout: apidefaults.DefaultIOTimeout, - }, nil + } + if fips { + config.Config = ssh.Config{ + KeyExchanges: defaults.FIPSKEXAlgorithms, + MACs: defaults.FIPSMACAlgorithms, + Ciphers: defaults.FIPSCiphers, + } + } + + return config, nil } // ReadIdentityFromStore reads stored identity credentials diff --git a/lib/tbot/renew.go b/lib/tbot/renew.go index e146408c3ee68..98024c56796f9 100644 --- a/lib/tbot/renew.go +++ b/lib/tbot/renew.go @@ -75,12 +75,12 @@ func (b *Bot) AuthenticatedUserClientFromIdentity(ctx context.Context, id *ident return nil, trace.BadParameter("auth client requires a fully formed identity") } - tlsConfig, err := id.TLSConfig(nil /* cipherSuites */) + tlsConfig, err := id.TLSConfig(b.cfg.CipherSuites()) if err != nil { return nil, trace.Wrap(err) } - sshConfig, err := id.SSHClientConfig() + sshConfig, err := id.SSHClientConfig(b.cfg.FIPS) if err != nil { return nil, trace.Wrap(err) } @@ -473,6 +473,8 @@ func (b *Bot) getIdentityFromToken() (*identity.Identity, error) { GetHostCredentials: client.HostCredentials, JoinMethod: b.cfg.Onboarding.JoinMethod, Expires: &expires, + FIPS: b.cfg.FIPS, + CipherSuites: b.cfg.CipherSuites(), } if params.JoinMethod == types.JoinMethodAzure { params.AzureParams = auth.AzureParams{ diff --git a/lib/tbot/tbot.go b/lib/tbot/tbot.go index 7ea97aef7eeda..537f685ef1e2b 100644 --- a/lib/tbot/tbot.go +++ b/lib/tbot/tbot.go @@ -33,6 +33,7 @@ import ( "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/tbot/config" "github.com/gravitational/teleport/lib/tbot/identity" "github.com/gravitational/teleport/lib/utils" @@ -42,6 +43,7 @@ type Bot struct { cfg *config.BotConfig log logrus.FieldLogger reloadChan chan struct{} + modules modules.Modules // These are protected by getter/setters with mutex locks mu sync.Mutex @@ -62,6 +64,7 @@ func New(cfg *config.BotConfig, log logrus.FieldLogger, reloadChan chan struct{} cfg: cfg, log: log, reloadChan: reloadChan, + modules: modules.GetModules(), _cas: map[types.CertAuthType][]types.CertAuthority{}, } @@ -266,6 +269,14 @@ func (b *Bot) initialize(ctx context.Context) (func() error, error) { ) } + if b.cfg.FIPS { + if !b.modules.IsBoringBinary() { + b.log.Error("FIPS mode enabled but FIPS compatible binary not in use. Ensure you are using the Enterprise FIPS binary to use this flag.") + return nil, trace.BadParameter("fips mode enabled but binary was not compiled with boringcrypto") + } + b.log.Info("Bot is running in FIPS compliant mode.") + } + // First, try to make sure all destinations are usable. if err := checkDestinations(b.cfg); err != nil { return nil, trace.Wrap(err) diff --git a/tool/tbot/main.go b/tool/tbot/main.go index d35fc771ea28b..4a42d7eabd1d7 100644 --- a/tool/tbot/main.go +++ b/tool/tbot/main.go @@ -65,6 +65,7 @@ func Run(args []string, stdout io.Writer) error { app := utils.InitCLIParser("tbot", appHelp).Interspersed(false) app.Flag("debug", "Verbose logging to stdout.").Short('d').BoolVar(&cf.Debug) app.Flag("config", "Path to a configuration file.").Short('c').StringVar(&cf.ConfigPath) + app.Flag("fips", "Runs tbot in FIPS compliance mode. This requires the FIPS binary is in use.").BoolVar(&cf.FIPS) app.HelpFlag.Short('h') joinMethodList := fmt.Sprintf( diff --git a/tool/tbot/main_test.go b/tool/tbot/main_test.go index 5937c09190550..cd766066c1c24 100644 --- a/tool/tbot/main_test.go +++ b/tool/tbot/main_test.go @@ -65,6 +65,7 @@ func TestRun_Configure(t *testing.T) { "--oneshot", "--certificate-ttl", "42m", "--renewal-interval", "21m", + "--fips", }...), }, } diff --git a/tool/tbot/testdata/TestRun_Configure/all_parameters_provided/file.golden b/tool/tbot/testdata/TestRun_Configure/all_parameters_provided/file.golden index 430440116090a..2de37caa03207 100644 --- a/tool/tbot/testdata/TestRun_Configure/all_parameters_provided/file.golden +++ b/tool/tbot/testdata/TestRun_Configure/all_parameters_provided/file.golden @@ -15,3 +15,4 @@ auth_server: example.com certificate_ttl: 42m0s renewal_interval: 21m0s oneshot: true +fips: true diff --git a/tool/tbot/testdata/TestRun_Configure/all_parameters_provided/stdout.golden b/tool/tbot/testdata/TestRun_Configure/all_parameters_provided/stdout.golden index 430440116090a..2de37caa03207 100644 --- a/tool/tbot/testdata/TestRun_Configure/all_parameters_provided/stdout.golden +++ b/tool/tbot/testdata/TestRun_Configure/all_parameters_provided/stdout.golden @@ -15,3 +15,4 @@ auth_server: example.com certificate_ttl: 42m0s renewal_interval: 21m0s oneshot: true +fips: true diff --git a/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/file.golden b/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/file.golden index 453a0054bb551..a729b3488df36 100644 --- a/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/file.golden +++ b/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/file.golden @@ -9,3 +9,4 @@ auth_server: "" certificate_ttl: 1h0m0s renewal_interval: 20m0s oneshot: false +fips: false diff --git a/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/stdout.golden b/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/stdout.golden index 453a0054bb551..a729b3488df36 100644 --- a/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/stdout.golden +++ b/tool/tbot/testdata/TestRun_Configure/no_parameters_provided/stdout.golden @@ -9,3 +9,4 @@ auth_server: "" certificate_ttl: 1h0m0s renewal_interval: 20m0s oneshot: false +fips: false