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

supporting google authenticator with Okta auth #14985

Merged
merged 7 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 12 additions & 6 deletions builtin/credential/okta/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Comment on lines +183 to +185
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check necessary, or does the strutil.ListContains call below handle this already (both the empty provider case and the matching provider checks)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is needed. Suppose I enabled Google totp, and Okta push and totp on my account. Then, this loop goes through an array containing all three enabled providers methods as the result.Embedded.Factors is populated by the third party Okta api. In this case, suppose I pass in the Google totp to the login command. However, when the loop ends, the totp factor could be populated by the okta totp. Then, MFA validation fails since I have passed in the Google totp code. The reason I introduced the provider in the command line is this. Cause without it, there is no way to distinguish whether the totp code is for Okta or Google.
The below strutil.ListContains just makes sure that we only accept Google and Okta as there are more providers than these two as mentioned in the Okta documentation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes that makes sense! Somehow I overlooked where preferredProvider is coming from.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that if I only have Google TOTP enabled, that I'd have to explicitly set Google as the provider? Because that seems like an unworkable situation -- our org has folks with a mix of either Okta Verify or Google TOTP (but not Okta TOTP), and we can't know in advance which provider to use.

Insisting that the provider be specified should only be required if both Okta and Google TOTP are present.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, if you only have Google TOTP enabled in your Okta account, you don't need to pass in the provider, as result.Embedded.Factors would only have one element. The problem raises when in a single Okta account, both Okta TOTP and Google TOTP are enabled as possible providers. Here, you would need to pass in the provider. Note that if you have enabled Okta Push and Google TOTP, you would NOT need to pass in the provider.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying @hghaf099! 🙇‍♂️


if !strutil.StrListContains(b.getSupportedProviders(), v.Provider) {
continue
}

Expand All @@ -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 {
hghaf099 marked this conversation as resolved.
Show resolved Hide resolved
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)
Expand Down
7 changes: 6 additions & 1 deletion builtin/credential/okta/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 20 additions & 2 deletions builtin/credential/okta/path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -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<username>.+)`,
Expand All @@ -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{
Expand All @@ -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 == "" {
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
3 changes: 3 additions & 0 deletions changelog/14985.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
auth/okta: Add support for Google provider TOTP type in the Okta auth method
```
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.
- `provider` `(string: <optional>)` - MFA TOTP factor provider. `GOOGLE` and `OKTA` are currently supported.

### Sample Payload

Expand Down
9 changes: 8 additions & 1 deletion website/content/docs/auth/okta.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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/).
Expand Down