diff --git a/builtin/credential/okta/backend.go b/builtin/credential/okta/backend.go index 463f3277d..ca0fe9f4d 100644 --- a/builtin/credential/okta/backend.go +++ b/builtin/credential/okta/backend.go @@ -6,6 +6,7 @@ import ( "net/textproto" "time" + "github.com/hashicorp/go-secure-stdlib/strutil" "github.com/hashicorp/vault/helper/mfa" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/cidrutil" @@ -63,7 +64,7 @@ type backend struct { *framework.Backend } -func (b *backend) Login(ctx context.Context, req *logical.Request, username, password, totp string) ([]string, *logical.Response, []string, error) { +func (b *backend) Login(ctx context.Context, req *logical.Request, username, password, totp, preferredProvider string) ([]string, *logical.Response, []string, error) { cfg, err := b.Config(ctx, req.Storage) if err != nil { return nil, nil, nil, err @@ -179,7 +180,11 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username, pas for _, v := range result.Embedded.Factors { v := v // create a new copy since we'll be taking the address later - if v.Provider != "OKTA" { + if preferredProvider != "" && preferredProvider != v.Provider { + continue + } + + if !strutil.StrListContains(b.getSupportedProviders(), v.Provider) { continue } @@ -191,17 +196,18 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username, pas } } - // Okta push and totp are currently supported. If a totp passcode is provided during - // login and is supported, that will be the preferred method. + // Okta push and totp, and Google totp are currently supported. + // If a totp passcode is provided during login and is supported, + // that will be the preferred method. switch { case totpFactor != nil && totp != "": selectedFactor = totpFactor - case pushFactor != nil: + case pushFactor != nil && pushFactor.Provider == oktaProvider: selectedFactor = pushFactor case totpFactor != nil && totp == "": return nil, logical.ErrorResponse("'totp' passcode parameter is required to perform MFA"), nil, nil default: - return nil, logical.ErrorResponse("Okta Verify Push or TOTP factor is required in order to perform MFA"), nil, nil + return nil, logical.ErrorResponse("Okta Verify Push or TOTP or Google TOTP factor is required in order to perform MFA"), nil, nil } requestPath := fmt.Sprintf("authn/factors/%s/verify", selectedFactor.Id) diff --git a/builtin/credential/okta/cli.go b/builtin/credential/okta/cli.go index cc526f638..c1ec74cf0 100644 --- a/builtin/credential/okta/cli.go +++ b/builtin/credential/okta/cli.go @@ -38,11 +38,16 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro "password": password, } - // Okta totp code + // Okta or Google totp code if totp, ok := m["totp"]; ok { data["totp"] = totp } + // provider is an optional parameter + if provider, ok := m["provider"]; ok { + data["provider"] = provider + } + // Legacy MFA support mfa_method, ok := m["method"] if ok { diff --git a/builtin/credential/okta/path_login.go b/builtin/credential/okta/path_login.go index 768846a27..8402da4c9 100644 --- a/builtin/credential/okta/path_login.go +++ b/builtin/credential/okta/path_login.go @@ -8,9 +8,15 @@ import ( "github.com/go-errors/errors" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/policyutil" + "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/hashicorp/vault/sdk/logical" ) +const ( + googleProvider = "GOOGLE" + oktaProvider = "OKTA" +) + func pathLogin(b *backend) *framework.Path { return &framework.Path{ Pattern: `login/(?P.+)`, @@ -28,6 +34,10 @@ func pathLogin(b *backend) *framework.Path { Type: framework.TypeString, Description: "TOTP passcode.", }, + "provider": { + Type: framework.TypeString, + Description: "Preferred factor provider.", + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -40,6 +50,10 @@ func pathLogin(b *backend) *framework.Path { } } +func (b *backend) getSupportedProviders() []string { + return []string{googleProvider, oktaProvider} +} + func (b *backend) pathLoginAliasLookahead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { username := d.Get("username").(string) if username == "" { @@ -59,8 +73,12 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framew username := d.Get("username").(string) password := d.Get("password").(string) totp := d.Get("totp").(string) + preferredProvider := strings.ToUpper(d.Get("provider").(string)) + if preferredProvider != "" && !strutil.StrListContains(b.getSupportedProviders(), preferredProvider) { + return logical.ErrorResponse(fmt.Sprintf("provider %s is not among the supported ones %v", preferredProvider, b.getSupportedProviders())), nil + } - policies, resp, groupNames, err := b.Login(ctx, req, username, password, totp) + policies, resp, groupNames, err := b.Login(ctx, req, username, password, totp, preferredProvider) // Handle an internal error if err != nil { return nil, err @@ -124,7 +142,7 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *f // No TOTP entry is possible on renew. If push MFA is enabled it will still be triggered, however. // Sending "" as the totp will prompt the push action if it is configured. - loginPolicies, resp, groupNames, err := b.Login(ctx, req, username, password, "") + loginPolicies, resp, groupNames, err := b.Login(ctx, req, username, password, "", "") if err != nil || (resp != nil && resp.IsError()) { return resp, err } diff --git a/changelog/14985.txt b/changelog/14985.txt new file mode 100644 index 000000000..3d8f4280f --- /dev/null +++ b/changelog/14985.txt @@ -0,0 +1,3 @@ +```release-note:improvement +auth/okta: Add support for Google provider TOTP type in the Okta auth method +``` diff --git a/website/content/api-docs/auth/okta.mdx b/website/content/api-docs/auth/okta.mdx index 6bca62e6d..789cfd596 100644 --- a/website/content/api-docs/auth/okta.mdx +++ b/website/content/api-docs/auth/okta.mdx @@ -352,6 +352,7 @@ Login with the username and password. - `username` `(string: )` - Username for this user. - `password` `(string: )` - Password for the authenticating user. - `totp` `(string: )` - Okta Verify TOTP passcode. +- `provider` `(string: )` - MFA TOTP factor provider. `GOOGLE` and `OKTA` are currently supported. ### Sample Payload diff --git a/website/content/docs/auth/okta.mdx b/website/content/docs/auth/okta.mdx index 50440ee3d..5b5a1e568 100644 --- a/website/content/docs/auth/okta.mdx +++ b/website/content/docs/auth/okta.mdx @@ -54,13 +54,20 @@ The response will contain a token at `auth.client_token`: ### MFA -Okta Verify Push and TOTP MFA methods are supported during login. For TOTP, the current +Okta Verify Push and TOTP MFA methods, and Google TOTP are supported during login. For TOTP, the current passcode may be provided via the `totp` parameter: ```shell-session $ vault login -method=okta username=my-username totp=123456 ``` +If both Okta TOTP and Google TOTP are enabled in your Okta account, make sure to pass in +the `provider` name to which the `totp` code belong. + +```shell-session +$ vault login -method=okta username=my-username totp=123456 provider=GOOGLE +``` + If `totp` is not set and MFA Push is configured in Okta, a Push will be sent during login. The auth method uses the Okta [Authentication API](https://developer.okta.com/docs/reference/api/authn/).