diff --git a/api/profile/profile.go b/api/profile/profile.go index 58a2132659351..5746ac19bb61a 100644 --- a/api/profile/profile.go +++ b/api/profile/profile.go @@ -96,6 +96,9 @@ type Profile struct { // MFAMode ("auto", "platform", "cross-platform"). // Equivalent to the --mfa-mode tsh flag. MFAMode string `yaml:"mfa_mode,omitempty"` + + // PrivateKeyPolicy is a key policy enforced for this profile. + PrivateKeyPolicy keys.PrivateKeyPolicy `yaml:"private_key_policy"` } // Copy returns a shallow copy of p, or nil if p is nil. diff --git a/api/utils/keys/yubikey.go b/api/utils/keys/yubikey.go index ce350f18232c0..b7d2759e0beb4 100644 --- a/api/utils/keys/yubikey.go +++ b/api/utils/keys/yubikey.go @@ -288,7 +288,7 @@ func (y *yubiKey) generatePrivateKey(slot piv.Slot, touchPolicy piv.TouchPolicy) } // Create a self signed certificate and store it in the PIV slot so that other - // Teleport Clients know to reuse the stored key instead of genearting a new one. + // Teleport Clients know to reuse the stored key instead of generating a new one. priv, err := yk.PrivateKey(slot, pub, piv.KeyAuth{}) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/client/api.go b/lib/client/api.go index 4f55e339b4bd0..7ffe581b5c098 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -590,6 +590,7 @@ func (c *Config) LoadProfile(ps ProfileStore, proxyAddr string) error { c.KeysDir = profile.Dir c.AuthConnector = profile.AuthConnector c.LoadAllCAs = profile.LoadAllCAs + c.PrivateKeyPolicy = profile.PrivateKeyPolicy c.AuthenticatorAttachment, err = parseMFAMode(profile.MFAMode) if err != nil { return trace.BadParameter("unable to parse mfa mode in user profile: %v.", err) @@ -629,6 +630,7 @@ func (c *Config) SaveProfile(makeCurrent bool) error { AuthConnector: c.AuthConnector, MFAMode: c.AuthenticatorAttachment.String(), LoadAllCAs: c.LoadAllCAs, + PrivateKeyPolicy: c.PrivateKeyPolicy, } if err := c.ClientStore.SaveProfile(p, makeCurrent); err != nil { @@ -3016,11 +3018,6 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { return nil, trace.Wrap(err) } - // Set private key policy from ping response if not already set. - if tc.PrivateKeyPolicy == "" { - tc.PrivateKeyPolicy = pr.Auth.PrivateKeyPolicy - } - key, err := tc.SSHLogin(ctx, sshLoginFunc) if err != nil { return nil, trace.Wrap(err) @@ -3182,7 +3179,7 @@ type SSHLoginFunc func(context.Context, *keys.PrivateKey) (*auth.SSHLoginRespons // SSHLogin uses the given login function to login the client. This function handles // private key logic and parsing the resulting auth response. func (tc *TeleportClient) SSHLogin(ctx context.Context, sshLoginFunc SSHLoginFunc) (*Key, error) { - priv, err := tc.GetNewLoginKey(ctx, tc.PrivateKeyPolicy) + priv, err := tc.GetNewLoginKey(ctx) if err != nil { return nil, trace.Wrap(err) } @@ -3195,7 +3192,8 @@ func (tc *TeleportClient) SSHLogin(ctx context.Context, sshLoginFunc SSHLoginFun fmt.Fprintf(tc.Stderr, "Unmet private key policy %q.\n", privateKeyPolicy) // Set the private key policy to the expected value and re-login. - priv, err = tc.GetNewLoginKey(ctx, privateKeyPolicy) + tc.PrivateKeyPolicy = privateKeyPolicy + priv, err = tc.GetNewLoginKey(ctx) if err != nil { return nil, trace.Wrap(err) } @@ -3241,39 +3239,23 @@ func (tc *TeleportClient) SSHLogin(ctx context.Context, sshLoginFunc SSHLoginFun } // GetNewLoginKey gets a new private key for login. -func (tc *TeleportClient) GetNewLoginKey(ctx context.Context, keyPolicy keys.PrivateKeyPolicy) (*keys.PrivateKey, error) { - key, err := tc.LocalAgent().GetCoreKey() - if err == nil { - // If we find an existing key with a non-zero key polic and it meets - // the given keyPolicy requirement, then we should use the existing key. - if coreKeyPolicy := keys.GetPrivateKeyPolicy(key.PrivateKey); coreKeyPolicy != keys.PrivateKeyPolicyNone { - if err := keyPolicy.VerifyPolicy(coreKeyPolicy); err == nil { - return key.PrivateKey, nil - } - } - } else if !trace.IsNotFound(err) { - return nil, trace.Wrap(err) - } - - switch keyPolicy { - case keys.PrivateKeyPolicyHardwareKey, keys.PrivateKeyPolicyHardwareKeyTouch: +func (tc *TeleportClient) GetNewLoginKey(ctx context.Context) (priv *keys.PrivateKey, err error) { + switch tc.PrivateKeyPolicy { + case keys.PrivateKeyPolicyHardwareKey: log.Debugf("Attempting to login with YubiKey private key.") - - priv, err := keys.GetOrGenerateYubiKeyPrivateKey(keyPolicy == keys.PrivateKeyPolicyHardwareKeyTouch) - if err != nil { - return nil, trace.Wrap(err) - } - return priv, nil + priv, err = keys.GetOrGenerateYubiKeyPrivateKey(false) + case keys.PrivateKeyPolicyHardwareKeyTouch: + log.Debugf("Attempting to login with YubiKey private key with touch required.") + priv, err = keys.GetOrGenerateYubiKeyPrivateKey(true) default: log.Debugf("Attempting to login with a new RSA private key.") + priv, err = native.GeneratePrivateKey() + } - // Generate a new standard key. - priv, err := native.GeneratePrivateKey() - if err != nil { - return nil, trace.Wrap(err) - } - return priv, nil + if err != nil { + return nil, trace.Wrap(err) } + return priv, nil } // new SSHLogin generates a new SSHLogin using the given login key. @@ -3780,6 +3762,11 @@ func (tc *TeleportClient) applyProxySettings(proxySettings webclient.ProxySettin // authentication settings, overriding existing fields in tc. func (tc *TeleportClient) applyAuthSettings(authSettings webclient.AuthenticationSettings) { tc.LoadAllCAs = authSettings.LoadAllCAs + + // Update the private key policy from auth settings if it is stricter than the saved setting. + if authSettings.PrivateKeyPolicy != "" && authSettings.PrivateKeyPolicy.VerifyPolicy(tc.PrivateKeyPolicy) != nil { + tc.PrivateKeyPolicy = authSettings.PrivateKeyPolicy + } } // AddTrustedCA adds a new CA as trusted CA for this client, used in tests diff --git a/lib/client/client_store.go b/lib/client/client_store.go index 4278ec6ee2096..02ccb1be516df 100644 --- a/lib/client/client_store.go +++ b/lib/client/client_store.go @@ -148,9 +148,10 @@ func (s *Store) ReadProfileStatus(profileName string) (*ProfileStatus, error) { } key, err := s.GetKey(idx, WithAllCerts...) if err != nil { - if trace.IsNotFound(err) { - // If we can't find a key to match the profile, return a partial status. This - // is used for some superficial functions `tsh logout` and `tsh status`. + if trace.IsNotFound(err) || trace.IsConnectionProblem(err) { + // If we can't find a key to match the profile, or can't connect to + // the key (hardware key), return a partial status. This is used for + // some superficial functions `tsh logout` and `tsh status`. return &ProfileStatus{ Name: profileName, Dir: profile.Dir, diff --git a/lib/teleterm/clusters/cluster_auth.go b/lib/teleterm/clusters/cluster_auth.go index bb4a8fded683b..01dae78355841 100644 --- a/lib/teleterm/clusters/cluster_auth.go +++ b/lib/teleterm/clusters/cluster_auth.go @@ -161,8 +161,6 @@ func (c *Cluster) updateClientFromPingResponse(ctx context.Context) (*webclient. return nil, trace.Wrap(err) } - c.clusterClient.PrivateKeyPolicy = pingResp.Auth.PrivateKeyPolicy - return pingResp, nil } diff --git a/tool/common/common.go b/tool/common/common.go index 4222b765479ff..d72c9028cbae6 100644 --- a/tool/common/common.go +++ b/tool/common/common.go @@ -111,6 +111,7 @@ func ShowClusterAlerts(ctx context.Context, client ClusterAlertGetter, w io.Writ // get any "on login" alerts alertCtx, cancelAlertCtx := context.WithTimeout(ctx, constants.TimeoutGetClusterAlerts) defer cancelAlertCtx() + alerts, err := client.GetClusterAlerts(alertCtx, types.GetClusterAlertsRequest{ Labels: labels, Severity: minSeverity, diff --git a/tool/tsh/tsh.go b/tool/tsh/tsh.go index ea5f8bb63e373..f642ff7044ba7 100644 --- a/tool/tsh/tsh.go +++ b/tool/tsh/tsh.go @@ -55,6 +55,7 @@ import ( "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/wrappers" + "github.com/gravitational/teleport/api/utils/keys" "github.com/gravitational/teleport/lib/asciitable" "github.com/gravitational/teleport/lib/auth" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" @@ -1731,10 +1732,16 @@ func onLogin(cf *CLIConf) error { } // Show on-login alerts, all high severity alerts are shown by onStatus - // so can be excluded here. + // so can be excluded here, except when Hardware Key Touch is required + // which skips on-status alerts. + alertSeverityMax := types.AlertSeverity_MEDIUM + if tc.PrivateKeyPolicy == keys.PrivateKeyPolicyHardwareKeyTouch { + alertSeverityMax = types.AlertSeverity_HIGH + } + if err := common.ShowClusterAlerts(cf.Context, tc, os.Stderr, map[string]string{ types.AlertOnLogin: "yes", - }, types.AlertSeverity_LOW, types.AlertSeverity_MEDIUM); err != nil { + }, types.AlertSeverity_LOW, alertSeverityMax); err != nil { log.WithError(err).Warn("Failed to display cluster alerts.") } @@ -3242,7 +3249,7 @@ func makeClientForProxy(cf *CLIConf, proxy string, useProfileLogin bool) (*clien // be found, or the key isn't supported as an agent key. if profileSiteName != "" { if err := tc.LoadKeyForCluster(profileSiteName); err != nil { - if !trace.IsNotFound(err) { + if !trace.IsNotFound(err) && !trace.IsConnectionProblem(err) { return nil, trace.Wrap(err) } log.WithError(err).Infof("Could not load key for %s into the local agent.", profileSiteName) @@ -3584,9 +3591,13 @@ func onStatus(cf *CLIConf) error { return nil } - if err := common.ShowClusterAlerts(cf.Context, tc, os.Stderr, nil, - types.AlertSeverity_HIGH, types.AlertSeverity_HIGH); err != nil { - log.WithError(err).Warn("Failed to display cluster alerts.") + if tc.PrivateKeyPolicy == keys.PrivateKeyPolicyHardwareKeyTouch { + log.Debug("Skipping cluster alerts due to Hardware Key Touch requirement.") + } else { + if err := common.ShowClusterAlerts(cf.Context, tc, os.Stderr, nil, + types.AlertSeverity_HIGH, types.AlertSeverity_HIGH); err != nil { + log.WithError(err).Warn("Failed to display cluster alerts.") + } } return nil