diff --git a/builtin/credential/okta/backend.go b/builtin/credential/okta/backend.go index fa275b6665..95829a9a12 100644 --- a/builtin/credential/okta/backend.go +++ b/builtin/credential/okta/backend.go @@ -115,6 +115,11 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri case "PASSWORD_WARN": oktaResponse.AddWarning("Your Okta password is in warning state and needs to be changed soon.") + case "MFA_REQUIRED", "MFA_ENROLL": + if !cfg.BypassOktaMFA { + return nil, logical.ErrorResponse("okta mfa required for this account but mfa bypass not set in config"), nil, nil + } + case "SUCCESS": // Do nothing here @@ -126,7 +131,13 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri } // Verify result status again in case a switch case above modifies result - if result.Status != "SUCCESS" && result.Status != "PASSWORD_WARN" { + switch { + case result.Status == "SUCCESS", + result.Status == "PASSWORD_WARN", + result.Status == "MFA_REQUIRED" && cfg.BypassOktaMFA, + result.Status == "MFA_ENROLL" && cfg.BypassOktaMFA: + // Allowed + default: if b.Logger().IsDebug() { b.Logger().Debug("auth/okta: authentication returned a non-success status", "status", result.Status) } diff --git a/builtin/credential/okta/path_config.go b/builtin/credential/okta/path_config.go index 8e8415bceb..365af74f4e 100644 --- a/builtin/credential/okta/path_config.go +++ b/builtin/credential/okta/path_config.go @@ -54,6 +54,10 @@ func pathConfig(b *backend) *framework.Path { Type: framework.TypeDurationSecond, Description: `Maximum duration after which authentication will be expired`, }, + "bypass_okta_mfa": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: `When set true, requests by Okta for a MFA check will be bypassed. This also disallows certain status checks on the account, such as whether the password is expired.`, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -99,10 +103,11 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *f resp := &logical.Response{ Data: map[string]interface{}{ - "organization": cfg.Org, - "org_name": cfg.Org, - "ttl": cfg.TTL.Seconds(), - "max_ttl": cfg.MaxTTL.Seconds(), + "organization": cfg.Org, + "org_name": cfg.Org, + "ttl": cfg.TTL.Seconds(), + "max_ttl": cfg.MaxTTL.Seconds(), + "bypass_okta_mfa": cfg.BypassOktaMFA, }, } if cfg.BaseURL != "" { @@ -112,6 +117,10 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *f resp.Data["production"] = *cfg.Production } + if cfg.BypassOktaMFA { + resp.AddWarning("Okta MFA bypass is configured. In addition to ignoring Okta MFA requests, certain other account statuses will not be seen, such as PASSWORD_EXPIRED. Authentication will succeed in these cases.") + } + return resp, nil } @@ -175,6 +184,11 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * cfg.Production = nil } + bypass, ok := d.GetOk("bypass_okta_mfa") + if ok { + cfg.BypassOktaMFA = bypass.(bool) + } + ttl, ok := d.GetOk("ttl") if ok { cfg.TTL = time.Duration(ttl.(int)) * time.Second @@ -197,7 +211,13 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d * return nil, err } - return nil, nil + var resp *logical.Response + if cfg.BypassOktaMFA { + resp = new(logical.Response) + resp.AddWarning("Okta MFA bypass is configured. In addition to ignoring Okta MFA requests, certain other account statuses will not be seen, such as PASSWORD_EXPIRED. Authentication will succeed in these cases.") + } + + return resp, nil } func (b *backend) pathConfigExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) { @@ -228,12 +248,13 @@ func (c *ConfigEntry) OktaClient() *okta.Client { // ConfigEntry for Okta type ConfigEntry struct { - Org string `json:"organization"` - Token string `json:"token"` - BaseURL string `json:"base_url"` - Production *bool `json:"is_production,omitempty"` - TTL time.Duration `json:"ttl"` - MaxTTL time.Duration `json:"max_ttl"` + Org string `json:"organization"` + Token string `json:"token"` + BaseURL string `json:"base_url"` + Production *bool `json:"is_production,omitempty"` + TTL time.Duration `json:"ttl"` + MaxTTL time.Duration `json:"max_ttl"` + BypassOktaMFA bool `json:"bypass_okta_mfa"` } const pathConfigHelp = ` diff --git a/website/source/api/auth/okta/index.html.md b/website/source/api/auth/okta/index.html.md index 6d3dfd5d00..408c87e436 100644 --- a/website/source/api/auth/okta/index.html.md +++ b/website/source/api/auth/okta/index.html.md @@ -37,6 +37,9 @@ distinction between the `create` and `update` capabilities inside ACL policies. - `ttl` `(string: "")` - Duration after which authentication will be expired. - `max_ttl` `(string: "")` - Maximum duration after which authentication will be expired. +- `bypass_okta_mfa` `(bool: false)` - Whether to bypass an Okta MFA request. + Useful if using one of Vault's built-in MFA mechanisms, but this will also + cause certain other statuses to be ignored, such as `PASSWORD_EXPIRED`. ### Sample Payload