116 lines
4.0 KiB
Go
116 lines
4.0 KiB
Go
|
// Copyright (c) HashiCorp, Inc.
|
||
|
// SPDX-License-Identifier: MPL-2.0
|
||
|
|
||
|
package api
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
// Auth is used to perform credential backend related operations.
|
||
|
type Auth struct {
|
||
|
c *Client
|
||
|
}
|
||
|
|
||
|
type AuthMethod interface {
|
||
|
Login(ctx context.Context, client *Client) (*Secret, error)
|
||
|
}
|
||
|
|
||
|
// Auth is used to return the client for credential-backend API calls.
|
||
|
func (c *Client) Auth() *Auth {
|
||
|
return &Auth{c: c}
|
||
|
}
|
||
|
|
||
|
// Login sets up the required request body for login requests to the given auth
|
||
|
// method's /login API endpoint, and then performs a write to it. After a
|
||
|
// successful login, this method will automatically set the client's token to
|
||
|
// the login response's ClientToken as well.
|
||
|
//
|
||
|
// The Secret returned is the authentication secret, which if desired can be
|
||
|
// passed as input to the NewLifetimeWatcher method in order to start
|
||
|
// automatically renewing the token.
|
||
|
func (a *Auth) Login(ctx context.Context, authMethod AuthMethod) (*Secret, error) {
|
||
|
if authMethod == nil {
|
||
|
return nil, fmt.Errorf("no auth method provided for login")
|
||
|
}
|
||
|
return a.login(ctx, authMethod)
|
||
|
}
|
||
|
|
||
|
// MFALogin is a wrapper that helps satisfy Vault's MFA implementation.
|
||
|
// If optional credentials are provided a single-phase login will be attempted
|
||
|
// and the resulting Secret will contain a ClientToken if the authentication is successful.
|
||
|
// The client's token will also be set accordingly.
|
||
|
//
|
||
|
// If no credentials are provided a two-phase MFA login will be assumed and the resulting
|
||
|
// Secret will have a MFARequirement containing the MFARequestID to be used in a follow-up
|
||
|
// call to `sys/mfa/validate` or by passing it to the method (*Auth).MFAValidate.
|
||
|
func (a *Auth) MFALogin(ctx context.Context, authMethod AuthMethod, creds ...string) (*Secret, error) {
|
||
|
if len(creds) > 0 {
|
||
|
a.c.SetMFACreds(creds)
|
||
|
return a.login(ctx, authMethod)
|
||
|
}
|
||
|
|
||
|
return a.twoPhaseMFALogin(ctx, authMethod)
|
||
|
}
|
||
|
|
||
|
// MFAValidate validates an MFA request using the appropriate payload and a secret containing
|
||
|
// Auth.MFARequirement, like the one returned by MFALogin when credentials are not provided.
|
||
|
// Upon successful validation the client token will be set accordingly.
|
||
|
//
|
||
|
// The Secret returned is the authentication secret, which if desired can be
|
||
|
// passed as input to the NewLifetimeWatcher method in order to start
|
||
|
// automatically renewing the token.
|
||
|
func (a *Auth) MFAValidate(ctx context.Context, mfaSecret *Secret, payload map[string]interface{}) (*Secret, error) {
|
||
|
if mfaSecret == nil || mfaSecret.Auth == nil || mfaSecret.Auth.MFARequirement == nil {
|
||
|
return nil, fmt.Errorf("secret does not contain MFARequirements")
|
||
|
}
|
||
|
|
||
|
s, err := a.c.Sys().MFAValidateWithContext(ctx, mfaSecret.Auth.MFARequirement.MFARequestID, payload)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return a.checkAndSetToken(s)
|
||
|
}
|
||
|
|
||
|
// login performs the (*AuthMethod).Login() with the configured client and checks that a ClientToken is returned
|
||
|
func (a *Auth) login(ctx context.Context, authMethod AuthMethod) (*Secret, error) {
|
||
|
s, err := authMethod.Login(ctx, a.c)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to log in to auth method: %w", err)
|
||
|
}
|
||
|
|
||
|
return a.checkAndSetToken(s)
|
||
|
}
|
||
|
|
||
|
// twoPhaseMFALogin performs the (*AuthMethod).Login() with the configured client
|
||
|
// and checks that an MFARequirement is returned
|
||
|
func (a *Auth) twoPhaseMFALogin(ctx context.Context, authMethod AuthMethod) (*Secret, error) {
|
||
|
s, err := authMethod.Login(ctx, a.c)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to log in: %w", err)
|
||
|
}
|
||
|
if s == nil || s.Auth == nil || s.Auth.MFARequirement == nil {
|
||
|
if s != nil {
|
||
|
s.Warnings = append(s.Warnings, "expected secret to contain MFARequirements")
|
||
|
}
|
||
|
return s, fmt.Errorf("assumed two-phase MFA login, returned secret is missing MFARequirements")
|
||
|
}
|
||
|
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
func (a *Auth) checkAndSetToken(s *Secret) (*Secret, error) {
|
||
|
if s == nil || s.Auth == nil || s.Auth.ClientToken == "" {
|
||
|
if s != nil {
|
||
|
s.Warnings = append(s.Warnings, "expected secret to contain ClientToken")
|
||
|
}
|
||
|
return s, fmt.Errorf("response did not return ClientToken, client token not set")
|
||
|
}
|
||
|
|
||
|
a.c.SetToken(s.Auth.ClientToken)
|
||
|
|
||
|
return s, nil
|
||
|
}
|