Add TOTP support to Okta Auth (#10942)
This commit is contained in:
parent
2aff402279
commit
7e54bc15c2
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
auth/okta: Adds support for Okta Verify TOTP MFA.
|
||||||
|
```
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue