From 7e54bc15c2780e0d2c3815ac3bfc44cfbc3e6baf Mon Sep 17 00:00:00 2001 From: Jim Kalafut Date: Sun, 21 Feb 2021 21:18:17 -0800 Subject: [PATCH] Add TOTP support to Okta Auth (#10942) --- builtin/credential/okta/backend.go | 46 +++++++++++++++++++++----- builtin/credential/okta/cli.go | 6 ++++ builtin/credential/okta/path_login.go | 15 ++++++--- changelog/10942.txt | 3 ++ website/content/api-docs/auth/okta.mdx | 1 + website/content/docs/auth/okta.mdx | 15 +++++++++ 6 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 changelog/10942.txt diff --git a/builtin/credential/okta/backend.go b/builtin/credential/okta/backend.go index 380efac5e..6046e282c 100644 --- a/builtin/credential/okta/backend.go +++ b/builtin/credential/okta/backend.go @@ -12,6 +12,11 @@ import ( "github.com/okta/okta-sdk-golang/v2/okta" ) +const ( + mfaPushMethod = "push" + mfaTOTPMethod = "token:software:totp" +) + func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { b := Backend() if err := b.Setup(ctx, conf); err != nil { @@ -57,7 +62,7 @@ type backend struct { *framework.Backend } -func (b *backend) Login(ctx context.Context, req *logical.Request, username string, password string) ([]string, *logical.Response, []string, error) { +func (b *backend) Login(ctx context.Context, req *logical.Request, username, password, totp string) ([]string, *logical.Response, []string, error) { cfg, err := b.Config(ctx, req.Storage) if err != nil { return nil, nil, nil, err @@ -160,30 +165,53 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri // active factor enrollment). This bypass removes visibility // into the authenticating user's password expiry, but still ensures the // credentials are valid and the user is not locked out. + // + // API reference: https://developer.okta.com/docs/reference/api/authn/#verify-factor if cfg.BypassOktaMFA { result.Status = "SUCCESS" break } - factorAvailable := false + var selectedFactor, totpFactor, pushFactor *mfaFactor - var selectedFactor mfaFactor - // only okta push is currently supported + // Scan for available factors for _, v := range result.Embedded.Factors { - if v.Type == "push" && v.Provider == "OKTA" { - factorAvailable = true - selectedFactor = v + v := v // create a new copy since we'll be taking the address later + + if v.Provider != "OKTA" { + continue + } + + switch v.Type { + case mfaTOTPMethod: + totpFactor = &v + case mfaPushMethod: + pushFactor = &v } } - if !factorAvailable { - return nil, logical.ErrorResponse("Okta Verify Push factor is required in order to perform MFA"), nil, nil + // Okta push and 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: + 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 } requestPath := fmt.Sprintf("authn/factors/%s/verify", selectedFactor.Id) + payload := map[string]interface{}{ "stateToken": result.StateToken, } + if selectedFactor.Type == mfaTOTPMethod { + payload["passCode"] = totp + } + verifyReq, err := shim.NewRequest("POST", requestPath, payload) if err != nil { return nil, nil, nil, err diff --git a/builtin/credential/okta/cli.go b/builtin/credential/okta/cli.go index 01b04555c..4dbd3e309 100644 --- a/builtin/credential/okta/cli.go +++ b/builtin/credential/okta/cli.go @@ -38,6 +38,12 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro "password": password, } + // Okta totp code + if totp, ok := m["totp"]; ok { + data["totp"] = totp + } + + // Legacy MFA support mfa_method, ok := m["method"] if ok { data["method"] = mfa_method diff --git a/builtin/credential/okta/path_login.go b/builtin/credential/okta/path_login.go index b1fb6b857..f21d9a6fd 100644 --- a/builtin/credential/okta/path_login.go +++ b/builtin/credential/okta/path_login.go @@ -15,15 +15,19 @@ func pathLogin(b *backend) *framework.Path { return &framework.Path{ Pattern: `login/(?P.+)`, Fields: map[string]*framework.FieldSchema{ - "username": &framework.FieldSchema{ + "username": { Type: framework.TypeString, Description: "Username to be used for login.", }, - "password": &framework.FieldSchema{ + "password": { Type: framework.TypeString, Description: "Password for this user.", }, + "totp": { + Type: framework.TypeString, + Description: "TOTP passcode.", + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -54,8 +58,9 @@ func (b *backend) pathLoginAliasLookahead(ctx context.Context, req *logical.Requ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { username := d.Get("username").(string) password := d.Get("password").(string) + totp := d.Get("totp").(string) - policies, resp, groupNames, err := b.Login(ctx, req, username, password) + policies, resp, groupNames, err := b.Login(ctx, req, username, password, totp) // Handle an internal error if err != nil { return nil, err @@ -117,7 +122,9 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *f return nil, err } - loginPolicies, resp, groupNames, err := b.Login(ctx, req, username, password) + // 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, "") if err != nil || (resp != nil && resp.IsError()) { return resp, err } diff --git a/changelog/10942.txt b/changelog/10942.txt new file mode 100644 index 000000000..fda073ac6 --- /dev/null +++ b/changelog/10942.txt @@ -0,0 +1,3 @@ +```release-note:improvement +auth/okta: Adds support for Okta Verify TOTP MFA. +``` diff --git a/website/content/api-docs/auth/okta.mdx b/website/content/api-docs/auth/okta.mdx index 6f08f10d4..8d4f7cde9 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. ### Sample Payload diff --git a/website/content/docs/auth/okta.mdx b/website/content/docs/auth/okta.mdx index c43e82e33..c332448f6 100644 --- a/website/content/docs/auth/okta.mdx +++ b/website/content/docs/auth/okta.mdx @@ -52,6 +52,21 @@ 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 +passcode may be provided via the `totp` parameter: + +```shell-session +$ vault login -method=okta username=my-username totp=123456 +``` + +If `totp` is not set and MFA Push is configured in Okta, a Push will be sent during login. + +Note that this MFA support is integrated with Okta Auth and is limited strictly to login +operations. It is not related to [Enterprise MFA](https://www.vaultproject.io/docs/enterprise/mfa). + + ## Configuration Auth methods must be configured in advance before users or machines can