Add TOTP support to Okta Auth (#10942)

This commit is contained in:
Jim Kalafut 2021-02-21 21:18:17 -08:00 committed by GitHub
parent 2aff402279
commit 7e54bc15c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 13 deletions

View File

@ -12,6 +12,11 @@ import (
"github.com/okta/okta-sdk-golang/v2/okta" "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) { func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b := Backend() b := Backend()
if err := b.Setup(ctx, conf); err != nil { if err := b.Setup(ctx, conf); err != nil {
@ -57,7 +62,7 @@ type backend struct {
*framework.Backend *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) cfg, err := b.Config(ctx, req.Storage)
if err != nil { if err != nil {
return nil, nil, nil, err 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 // active factor enrollment). This bypass removes visibility
// into the authenticating user's password expiry, but still ensures the // into the authenticating user's password expiry, but still ensures the
// credentials are valid and the user is not locked out. // 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 { if cfg.BypassOktaMFA {
result.Status = "SUCCESS" result.Status = "SUCCESS"
break break
} }
factorAvailable := false var selectedFactor, totpFactor, pushFactor *mfaFactor
var selectedFactor mfaFactor // Scan for available factors
// only okta push is currently supported
for _, v := range result.Embedded.Factors { for _, v := range result.Embedded.Factors {
if v.Type == "push" && v.Provider == "OKTA" { v := v // create a new copy since we'll be taking the address later
factorAvailable = true
selectedFactor = v if v.Provider != "OKTA" {
continue
}
switch v.Type {
case mfaTOTPMethod:
totpFactor = &v
case mfaPushMethod:
pushFactor = &v
} }
} }
if !factorAvailable { // Okta push and totp are currently supported. If a totp passcode is provided during
return nil, logical.ErrorResponse("Okta Verify Push factor is required in order to perform MFA"), nil, nil // 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) requestPath := fmt.Sprintf("authn/factors/%s/verify", selectedFactor.Id)
payload := map[string]interface{}{ payload := map[string]interface{}{
"stateToken": result.StateToken, "stateToken": result.StateToken,
} }
if selectedFactor.Type == mfaTOTPMethod {
payload["passCode"] = totp
}
verifyReq, err := shim.NewRequest("POST", requestPath, payload) verifyReq, err := shim.NewRequest("POST", requestPath, payload)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err

View File

@ -38,6 +38,12 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro
"password": password, "password": password,
} }
// Okta totp code
if totp, ok := m["totp"]; ok {
data["totp"] = totp
}
// Legacy MFA support
mfa_method, ok := m["method"] mfa_method, ok := m["method"]
if ok { if ok {
data["method"] = mfa_method data["method"] = mfa_method

View File

@ -15,15 +15,19 @@ func pathLogin(b *backend) *framework.Path {
return &framework.Path{ return &framework.Path{
Pattern: `login/(?P<username>.+)`, Pattern: `login/(?P<username>.+)`,
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
"username": &framework.FieldSchema{ "username": {
Type: framework.TypeString, Type: framework.TypeString,
Description: "Username to be used for login.", Description: "Username to be used for login.",
}, },
"password": &framework.FieldSchema{ "password": {
Type: framework.TypeString, Type: framework.TypeString,
Description: "Password for this user.", Description: "Password for this user.",
}, },
"totp": {
Type: framework.TypeString,
Description: "TOTP passcode.",
},
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ 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) { func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
username := d.Get("username").(string) username := d.Get("username").(string)
password := d.Get("password").(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 // Handle an internal error
if err != nil { if err != nil {
return nil, err return nil, err
@ -117,7 +122,9 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *f
return nil, err 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()) { if err != nil || (resp != nil && resp.IsError()) {
return resp, err return resp, err
} }

3
changelog/10942.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
auth/okta: Adds support for Okta Verify TOTP MFA.
```

View File

@ -352,6 +352,7 @@ Login with the username and password.
- `username` `(string: <required>)` - Username for this user. - `username` `(string: <required>)` - Username for this user.
- `password` `(string: <required>)` - Password for the authenticating user. - `password` `(string: <required>)` - Password for the authenticating user.
- `totp` `(string: <optional>)` - Okta Verify TOTP passcode.
### Sample Payload ### Sample Payload

View File

@ -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 ## Configuration
Auth methods must be configured in advance before users or machines can Auth methods must be configured in advance before users or machines can