Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TOTP support to Okta Auth #10942

Merged
merged 7 commits into from
Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions builtin/credential/okta/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
kalafut marked this conversation as resolved.
Show resolved Hide resolved
cfg, err := b.Config(ctx, req.Storage)
if err != nil {
return nil, nil, nil, err
Expand Down Expand Up @@ -160,30 +165,52 @@ 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
kalafut marked this conversation as resolved.
Show resolved Hide resolved
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:
kalafut marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
6 changes: 6 additions & 0 deletions builtin/credential/okta/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
kalafut marked this conversation as resolved.
Show resolved Hide resolved
if ok {
data["method"] = mfa_method
Expand Down
14 changes: 10 additions & 4 deletions builtin/credential/okta/path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ func pathLogin(b *backend) *framework.Path {
return &framework.Path{
Pattern: `login/(?P<username>.+)`,
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{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -117,7 +122,8 @@ 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.
kalafut marked this conversation as resolved.
Show resolved Hide resolved
loginPolicies, resp, groupNames, err := b.Login(ctx, req, username, password, "")
if err != nil || (resp != nil && resp.IsError()) {
return resp, err
}
Expand Down
1 change: 1 addition & 0 deletions website/content/api-docs/auth/okta.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ Login with the username and password.

- `username` `(string: <required>)` - Username for this user.
- `password` `(string: <required>)` - Password for the authenticating user.
- `totp` `(string: <optional>)` - Okta Verify TOTP passcode.

### Sample Payload

Expand Down
15 changes: 15 additions & 0 deletions website/content/docs/auth/okta.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down