diff --git a/api/types/authentication.go b/api/types/authentication.go index 05870b8ad17c2..bcc33051fd6a5 100644 --- a/api/types/authentication.go +++ b/api/types/authentication.go @@ -63,6 +63,8 @@ type AuthPreference interface { // IsSecondFactorWebauthnAllowed checks if users are allowed to register // Webauthn devices. IsSecondFactorWebauthnAllowed() bool + // IsAdminActionMFAEnforced checks if admin action MFA is enforced. + IsAdminActionMFAEnforced() bool // GetConnectorName gets the name of the OIDC or SAML connector to use. If // this value is empty, we fall back to the first connector in the backend. @@ -325,6 +327,12 @@ func (c *AuthPreferenceV2) IsSecondFactorWebauthnAllowed() bool { c.Spec.SecondFactor == constants.SecondFactorOn } +// IsAdminActionMFAEnforced checks if admin action MFA is enforced. Currently, the only +// prerequisite for admin action MFA enforcement is whether Webauthn is enabled. +func (c *AuthPreferenceV2) IsAdminActionMFAEnforced() bool { + return c.IsSecondFactorWebauthnAllowed() +} + // GetConnectorName gets the name of the OIDC or SAML connector to use. If // this value is empty, we fall back to the first connector in the backend. func (c *AuthPreferenceV2) GetConnectorName() string { diff --git a/lib/authz/permissions.go b/lib/authz/permissions.go index 2722d88f30e59..976f94415654c 100644 --- a/lib/authz/permissions.go +++ b/lib/authz/permissions.go @@ -34,7 +34,6 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" - "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/defaults" mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1" "github.com/gravitational/teleport/api/mfa" @@ -444,8 +443,8 @@ func (a *authorizer) isAdminActionAuthorizationRequired(ctx context.Context, aut return false, trace.Wrap(err) } - // Admin actions do not require MFA when Webauthn is not enabled. - if authpref.GetPreferredLocalMFA() != constants.SecondFactorWebauthn { + // Check if this cluster enforces MFA for admin actions. + if !authpref.IsAdminActionMFAEnforced() { return false, nil } diff --git a/tool/tctl/common/user_command.go b/tool/tctl/common/user_command.go index 050567f2f59d7..c6d545c5aa211 100644 --- a/tool/tctl/common/user_command.go +++ b/tool/tctl/common/user_command.go @@ -34,6 +34,7 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/mfa" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/asciitable" "github.com/gravitational/teleport/lib/auth" @@ -297,6 +298,16 @@ func (u *UserCommand) Add(ctx context.Context, client auth.ClientI) error { user.SetTraits(traits) user.SetRoles(u.allowedRoles) + // Prompt for admin action MFA if required, allowing reuse for CreateResetPasswordToken. + if u.config.Auth.Preference.IsAdminActionMFAEnforced() { + mfaResponse, err := mfa.PerformAdminActionMFACeremony(ctx, client, "CreateUser", true /*allowReuse*/) + if err != nil { + return trace.Wrap(err) + } else if mfaResponse != nil { + ctx = mfa.ContextWithMFAResponse(ctx, mfaResponse) + } + } + if _, err := client.CreateUser(ctx, user); err != nil { if trace.IsAlreadyExists(err) { fmt.Printf(`NOTE: To update an existing local user: