diff --git a/builtin/credential/github/backend.go b/builtin/credential/github/backend.go index 3d9090737..b7b16a633 100644 --- a/builtin/credential/github/backend.go +++ b/builtin/credential/github/backend.go @@ -5,7 +5,6 @@ import ( "github.com/google/go-github/github" cleanhttp "github.com/hashicorp/go-cleanhttp" - "github.com/hashicorp/vault/helper/mfa" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" "golang.org/x/oauth2" @@ -40,15 +39,12 @@ func Backend() *backend { Help: backendHelp, PathsSpecial: &logical.Paths{ - Root: mfa.MFARootPaths(), Unauthenticated: []string{ "login", }, }, - Paths: append([]*framework.Path{ - pathConfig(&b), - }, append(allPaths, mfa.MFAPaths(b.Backend, pathLogin(&b))...)...), + Paths: append([]*framework.Path{pathConfig(&b), pathLogin(&b)}, allPaths...), AuthRenew: b.pathLoginRenew, BackendType: logical.TypeCredential, } diff --git a/builtin/credential/ldap/backend.go b/builtin/credential/ldap/backend.go index 554f103eb..37ba20ae6 100644 --- a/builtin/credential/ldap/backend.go +++ b/builtin/credential/ldap/backend.go @@ -6,7 +6,6 @@ import ( "strings" "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/ldaputil" "github.com/hashicorp/vault/sdk/logical" @@ -28,8 +27,6 @@ func Backend() *backend { Help: backendHelp, PathsSpecial: &logical.Paths{ - Root: mfa.MFARootPaths(), - Unauthenticated: []string{ "login/*", }, @@ -39,15 +36,14 @@ func Backend() *backend { }, }, - Paths: append([]*framework.Path{ + Paths: []*framework.Path{ pathConfig(&b), pathGroups(&b), pathGroupsList(&b), pathUsers(&b), pathUsersList(&b), + pathLogin(&b), }, - mfa.MFAPaths(b.Backend, pathLogin(&b))..., - ), AuthRenew: b.pathLoginRenew, BackendType: logical.TypeCredential, diff --git a/builtin/credential/ldap/cli.go b/builtin/credential/ldap/cli.go index 21302a7c5..e0d744b4c 100644 --- a/builtin/credential/ldap/cli.go +++ b/builtin/credential/ldap/cli.go @@ -39,15 +39,6 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro "password": password, } - mfa_method, ok := m["method"] - if ok { - data["method"] = mfa_method - } - mfa_passcode, ok := m["passcode"] - if ok { - data["passcode"] = mfa_passcode - } - path := fmt.Sprintf("auth/%s/login/%s", mount, username) secret, err := c.Logical().Write(path, data) if err != nil { @@ -67,11 +58,6 @@ Usage: vault login -method=ldap [CONFIG K=V...] The LDAP auth method allows users to authenticate using LDAP or Active Directory. - If MFA is enabled, a "method" and/or "passcode" may be required depending on - the MFA method. To check which MFA is in use, run: - - $ vault read auth//mfa_config - Authenticate as "sally": $ vault login -method=ldap username=sally @@ -83,12 +69,6 @@ Usage: vault login -method=ldap [CONFIG K=V...] Configuration: - method= - MFA method. - - passcode= - MFA OTP/passcode. - password= LDAP password to use for authentication. If not provided, the CLI will prompt for this on stdin. diff --git a/builtin/credential/okta/backend.go b/builtin/credential/okta/backend.go index ca0fe9f4d..72ba13e6a 100644 --- a/builtin/credential/okta/backend.go +++ b/builtin/credential/okta/backend.go @@ -7,7 +7,6 @@ import ( "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" "github.com/hashicorp/vault/sdk/logical" @@ -33,8 +32,6 @@ func Backend() *backend { Help: backendHelp, PathsSpecial: &logical.Paths{ - Root: mfa.MFARootPaths(), - Unauthenticated: []string{ "login/*", }, @@ -43,15 +40,14 @@ func Backend() *backend { }, }, - Paths: append([]*framework.Path{ + Paths: []*framework.Path{ pathConfig(&b), pathUsers(&b), pathGroups(&b), pathUsersList(&b), pathGroupsList(&b), + pathLogin(&b), }, - mfa.MFAPaths(b.Backend, pathLogin(&b))..., - ), AuthRenew: b.pathLoginRenew, BackendType: logical.TypeCredential, diff --git a/builtin/credential/okta/cli.go b/builtin/credential/okta/cli.go index c1ec74cf0..cf82a09e1 100644 --- a/builtin/credential/okta/cli.go +++ b/builtin/credential/okta/cli.go @@ -48,16 +48,6 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro data["provider"] = provider } - // Legacy MFA support - mfa_method, ok := m["method"] - if ok { - data["method"] = mfa_method - } - mfa_passcode, ok := m["passcode"] - if ok { - data["passcode"] = mfa_passcode - } - path := fmt.Sprintf("auth/%s/login/%s", mount, username) secret, err := c.Logical().Write(path, data) if err != nil { diff --git a/builtin/credential/radius/backend.go b/builtin/credential/radius/backend.go index 72eddda03..03da06efd 100644 --- a/builtin/credential/radius/backend.go +++ b/builtin/credential/radius/backend.go @@ -3,7 +3,6 @@ package radius import ( "context" - "github.com/hashicorp/vault/helper/mfa" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" ) @@ -22,8 +21,6 @@ func Backend() *backend { Help: backendHelp, PathsSpecial: &logical.Paths{ - Root: mfa.MFARootPaths(), - Unauthenticated: []string{ "login", "login/*", @@ -34,13 +31,12 @@ func Backend() *backend { }, }, - Paths: append([]*framework.Path{ + Paths: []*framework.Path{ pathConfig(&b), pathUsers(&b), pathUsersList(&b), + pathLogin(&b), }, - mfa.MFAPaths(b.Backend, pathLogin(&b))..., - ), AuthRenew: b.pathLoginRenew, BackendType: logical.TypeCredential, diff --git a/builtin/credential/userpass/backend.go b/builtin/credential/userpass/backend.go index 498b296c8..aa45dc376 100644 --- a/builtin/credential/userpass/backend.go +++ b/builtin/credential/userpass/backend.go @@ -3,7 +3,6 @@ package userpass import ( "context" - "github.com/hashicorp/vault/helper/mfa" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" ) @@ -22,21 +21,18 @@ func Backend() *backend { Help: backendHelp, PathsSpecial: &logical.Paths{ - Root: mfa.MFARootPaths(), - Unauthenticated: []string{ "login/*", }, }, - Paths: append([]*framework.Path{ + Paths: []*framework.Path{ pathUsers(&b), pathUsersList(&b), pathUserPolicies(&b), pathUserPassword(&b), + pathLogin(&b), }, - mfa.MFAPaths(b.Backend, pathLogin(&b))..., - ), AuthRenew: b.pathLoginRenew, BackendType: logical.TypeCredential, diff --git a/builtin/credential/userpass/cli.go b/builtin/credential/userpass/cli.go index 34c3c3191..092d0927e 100644 --- a/builtin/credential/userpass/cli.go +++ b/builtin/credential/userpass/cli.go @@ -19,8 +19,6 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro Username string `mapstructure:"username"` Password string `mapstructure:"password"` Mount string `mapstructure:"mount"` - Method string `mapstructure:"method"` - Passcode string `mapstructure:"passcode"` } if err := mapstructure.WeakDecode(m, &data); err != nil { return nil, err @@ -45,12 +43,6 @@ func (h *CLIHandler) Auth(c *api.Client, m map[string]string) (*api.Secret, erro options := map[string]interface{}{ "password": data.Password, } - if data.Method != "" { - options["method"] = data.Method - } - if data.Passcode != "" { - options["passcode"] = data.Passcode - } path := fmt.Sprintf("auth/%s/login/%s", data.Mount, data.Username) secret, err := c.Logical().Write(path, options) @@ -71,11 +63,6 @@ Usage: vault login -method=userpass [CONFIG K=V...] The userpass auth method allows users to authenticate using Vault's internal user database. - If MFA is enabled, a "method" and/or "passcode" may be required depending on - the MFA method. To check which MFA is in use, run: - - $ vault read auth//mfa_config - Authenticate as "sally": $ vault login -method=userpass username=sally @@ -87,12 +74,6 @@ Usage: vault login -method=userpass [CONFIG K=V...] Configuration: - method= - MFA method. - - passcode= - MFA OTP/passcode. - password= Password to use for authentication. If not provided, the CLI will prompt for this on stdin. diff --git a/changelog/14869.txt b/changelog/14869.txt new file mode 100644 index 000000000..1ee612277 --- /dev/null +++ b/changelog/14869.txt @@ -0,0 +1,4 @@ +```release-note:change +auth: Remove support for legacy MFA +(https://www.vaultproject.io/docs/v1.10.x/auth/mfa) +``` diff --git a/helper/mfa/duo/duo.go b/helper/mfa/duo/duo.go deleted file mode 100644 index 7530535bd..000000000 --- a/helper/mfa/duo/duo.go +++ /dev/null @@ -1,149 +0,0 @@ -// Package duo provides a Duo MFA handler to authenticate users -// with Duo. This handler is registered as the "duo" type in -// mfa_config. -package duo - -import ( - "context" - "fmt" - "net/url" - - "github.com/duosecurity/duo_api_golang/authapi" - "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/logical" -) - -// DuoPaths returns path functions to configure Duo. -func DuoPaths() []*framework.Path { - return []*framework.Path{ - pathDuoConfig(), - pathDuoAccess(), - } -} - -// DuoRootPaths returns the paths that are used to configure Duo. -func DuoRootPaths() []string { - return []string{ - "duo/access", - "duo/config", - } -} - -// DuoHandler interacts with the Duo Auth API to authenticate a user -// login request. If successful, the original response from the login -// backend is returned. -func DuoHandler(ctx context.Context, req *logical.Request, d *framework.FieldData, resp *logical.Response) ( - *logical.Response, error) { - duoConfig, err := GetDuoConfig(ctx, req) - if err != nil || duoConfig == nil { - return logical.ErrorResponse("Could not load Duo configuration"), nil - } - - duoAuthClient, err := GetDuoAuthClient(ctx, req, duoConfig) - if err != nil { - return logical.ErrorResponse(err.Error()), nil - } - - username, ok := resp.Auth.Metadata["username"] - if !ok { - return logical.ErrorResponse("Could not read username for MFA"), nil - } - - var request *duoAuthRequest = &duoAuthRequest{} - request.successResp = resp - request.username = username - request.method = d.Get("method").(string) - request.passcode = d.Get("passcode").(string) - request.ipAddr = req.Connection.RemoteAddr - - return duoHandler(duoConfig, duoAuthClient, request) -} - -type duoAuthRequest struct { - successResp *logical.Response - username string - method string - passcode string - ipAddr string -} - -func duoHandler(duoConfig *DuoConfig, duoAuthClient AuthClient, request *duoAuthRequest) ( - *logical.Response, error, -) { - duoUser := fmt.Sprintf(duoConfig.UsernameFormat, request.username) - - preauth, err := duoAuthClient.Preauth( - authapi.PreauthUsername(duoUser), - authapi.PreauthIpAddr(request.ipAddr), - ) - - if err != nil || preauth == nil { - return logical.ErrorResponse("Could not call Duo preauth"), nil - } - - if preauth.StatResult.Stat != "OK" { - errorMsg := "Could not look up Duo user information" - if preauth.StatResult.Message != nil { - errorMsg = errorMsg + ": " + *preauth.StatResult.Message - } - if preauth.StatResult.Message_Detail != nil { - errorMsg = errorMsg + " (" + *preauth.StatResult.Message_Detail + ")" - } - return logical.ErrorResponse(errorMsg), nil - } - - switch preauth.Response.Result { - case "allow": - return request.successResp, err - case "deny": - return logical.ErrorResponse(preauth.Response.Status_Msg), nil - case "enroll": - return logical.ErrorResponse(fmt.Sprintf("%s (%s)", - preauth.Response.Status_Msg, - preauth.Response.Enroll_Portal_Url)), nil - case "auth": - break - default: - return logical.ErrorResponse(fmt.Sprintf("Invalid Duo preauth response: %s", - preauth.Response.Result)), nil - } - - options := []func(*url.Values){authapi.AuthUsername(duoUser)} - if request.method == "" { - request.method = "auto" - } - if request.method == "auto" || request.method == "push" { - if duoConfig.PushInfo != "" { - options = append(options, authapi.AuthPushinfo(duoConfig.PushInfo)) - } - } - if request.passcode != "" { - request.method = "passcode" - options = append(options, authapi.AuthPasscode(request.passcode)) - } else { - options = append(options, authapi.AuthDevice("auto")) - } - - result, err := duoAuthClient.Auth(request.method, options...) - - if err != nil || result == nil { - return logical.ErrorResponse("Could not call Duo auth"), nil - } - - if result.StatResult.Stat != "OK" { - errorMsg := "Could not authenticate Duo user" - if result.StatResult.Message != nil { - errorMsg = errorMsg + ": " + *result.StatResult.Message - } - if result.StatResult.Message_Detail != nil { - errorMsg = errorMsg + " (" + *result.StatResult.Message_Detail + ")" - } - return logical.ErrorResponse(errorMsg), nil - } - - if result.Response.Result != "allow" { - return logical.ErrorResponse(result.Response.Status_Msg), nil - } - - return request.successResp, nil -} diff --git a/helper/mfa/duo/duo_test.go b/helper/mfa/duo/duo_test.go deleted file mode 100644 index 0360d2cb3..000000000 --- a/helper/mfa/duo/duo_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package duo - -import ( - "net/url" - "strings" - "testing" - - "github.com/duosecurity/duo_api_golang/authapi" - "github.com/hashicorp/vault/sdk/helper/jsonutil" - "github.com/hashicorp/vault/sdk/logical" -) - -type MockClientData struct { - PreauthData *authapi.PreauthResult - PreauthError error - AuthData *authapi.AuthResult - AuthError error -} - -type MockAuthClient struct { - MockData *MockClientData -} - -func (c *MockAuthClient) Preauth(options ...func(*url.Values)) (*authapi.PreauthResult, error) { - return c.MockData.PreauthData, c.MockData.PreauthError -} - -func (c *MockAuthClient) Auth(factor string, options ...func(*url.Values)) (*authapi.AuthResult, error) { - return c.MockData.AuthData, c.MockData.AuthError -} - -func MockGetDuoAuthClient(data *MockClientData) func(*logical.Request, *DuoConfig) (AuthClient, error) { - return func(*logical.Request, *DuoConfig) (AuthClient, error) { - return getDuoAuthClient(data), nil - } -} - -func getDuoAuthClient(data *MockClientData) AuthClient { - var c MockAuthClient - // set default response to be successful - preauthSuccessJSON := ` - { - "Stat": "OK", - "Response": { - "Result": "auth", - "Status_Msg": "Needs authentication", - "Devices": [] - } - }` - if data.PreauthData == nil { - data.PreauthData = &authapi.PreauthResult{} - jsonutil.DecodeJSON([]byte(preauthSuccessJSON), data.PreauthData) - } - - authSuccessJSON := ` - { - "Stat": "OK", - "Response": { - "Result": "allow" - } - }` - if data.AuthData == nil { - data.AuthData = &authapi.AuthResult{} - jsonutil.DecodeJSON([]byte(authSuccessJSON), data.AuthData) - } - - c.MockData = data - return &c -} - -func TestDuoHandlerSuccess(t *testing.T) { - successResp := &logical.Response{ - Auth: &logical.Auth{}, - } - duoConfig := &DuoConfig{ - UsernameFormat: "%s", - } - duoAuthClient := getDuoAuthClient(&MockClientData{}) - resp, err := duoHandler(duoConfig, duoAuthClient, &duoAuthRequest{ - successResp: successResp, - username: "", - }) - if err != nil { - t.Fatalf(err.Error()) - } - if resp != successResp { - t.Fatalf("Testing Duo authentication gave incorrect response (expected success, got: %v)", resp) - } -} - -func TestDuoHandlerReject(t *testing.T) { - AuthData := &authapi.AuthResult{} - authRejectJSON := ` - { - "Stat": "OK", - "Response": { - "Result": "deny", - "Status_Msg": "Invalid auth" - } - }` - jsonutil.DecodeJSON([]byte(authRejectJSON), AuthData) - successResp := &logical.Response{ - Auth: &logical.Auth{}, - } - expectedError := AuthData.Response.Status_Msg - duoConfig := &DuoConfig{ - UsernameFormat: "%s", - } - duoAuthClient := getDuoAuthClient(&MockClientData{ - AuthData: AuthData, - }) - resp, err := duoHandler(duoConfig, duoAuthClient, &duoAuthRequest{ - successResp: successResp, - username: "user", - }) - if err != nil { - t.Fatalf(err.Error()) - } - error, ok := resp.Data["error"].(string) - if !ok || !strings.Contains(error, expectedError) { - t.Fatalf("Testing Duo authentication gave incorrect response (expected deny, got: %v)", error) - } -} diff --git a/helper/mfa/duo/path_duo_access.go b/helper/mfa/duo/path_duo_access.go deleted file mode 100644 index e53213797..000000000 --- a/helper/mfa/duo/path_duo_access.go +++ /dev/null @@ -1,119 +0,0 @@ -package duo - -import ( - "context" - "fmt" - "net/url" - - duoapi "github.com/duosecurity/duo_api_golang" - "github.com/duosecurity/duo_api_golang/authapi" - "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/logical" -) - -type AuthClient interface { - Preauth(options ...func(*url.Values)) (*authapi.PreauthResult, error) - Auth(factor string, options ...func(*url.Values)) (*authapi.AuthResult, error) -} - -func pathDuoAccess() *framework.Path { - return &framework.Path{ - Pattern: `duo/access`, - Fields: map[string]*framework.FieldSchema{ - "skey": { - Type: framework.TypeString, - Description: "Duo secret key", - }, - "ikey": { - Type: framework.TypeString, - Description: "Duo integration key", - }, - "host": { - Type: framework.TypeString, - Description: "Duo api host", - }, - }, - - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: pathDuoAccessWrite, - }, - - HelpSynopsis: pathDuoAccessHelpSyn, - HelpDescription: pathDuoAccessHelpDesc, - } -} - -func GetDuoAuthClient(ctx context.Context, req *logical.Request, config *DuoConfig) (AuthClient, error) { - entry, err := req.Storage.Get(ctx, "duo/access") - if err != nil { - return nil, err - } - if entry == nil { - return nil, fmt.Errorf( - "Duo access credentials haven't been configured. Please configure\n" + - "them at the 'duo/access' endpoint") - } - var access DuoAccess - if err := entry.DecodeJSON(&access); err != nil { - return nil, err - } - - duoClient := duoapi.NewDuoApi( - access.IKey, - access.SKey, - access.Host, - config.UserAgent, - ) - duoAuthClient := authapi.NewAuthApi(*duoClient) - check, err := duoAuthClient.Check() - if err != nil { - return nil, err - } - if check == nil { - return nil, fmt.Errorf("could not connect to Duo; got nil result back from API check call") - } - var msg, detail string - if check.StatResult.Message != nil { - msg = *check.StatResult.Message - } - if check.StatResult.Message_Detail != nil { - detail = *check.StatResult.Message_Detail - } - if check.StatResult.Stat != "OK" { - return nil, fmt.Errorf("could not connect to Duo: %q (%q)", msg, detail) - } - return duoAuthClient, nil -} - -func pathDuoAccessWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - entry, err := logical.StorageEntryJSON("duo/access", DuoAccess{ - SKey: d.Get("skey").(string), - IKey: d.Get("ikey").(string), - Host: d.Get("host").(string), - }) - if err != nil { - return nil, err - } - - if err := req.Storage.Put(ctx, entry); err != nil { - return nil, err - } - - return nil, nil -} - -type DuoAccess struct { - SKey string `json:"skey"` - IKey string `json:"ikey"` - Host string `json:"host"` -} - -const pathDuoAccessHelpSyn = ` -Configure the access keys and host for Duo API connections. -` - -const pathDuoAccessHelpDesc = ` -To authenticate users with Duo, the backend needs to know what host to connect to -and must authenticate with an integration key and secret key. This endpoint is used -to configure that information. -` diff --git a/helper/mfa/duo/path_duo_config.go b/helper/mfa/duo/path_duo_config.go deleted file mode 100644 index d2299d3d6..000000000 --- a/helper/mfa/duo/path_duo_config.go +++ /dev/null @@ -1,110 +0,0 @@ -package duo - -import ( - "context" - "errors" - "strings" - - "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/logical" -) - -func pathDuoConfig() *framework.Path { - return &framework.Path{ - Pattern: `duo/config`, - Fields: map[string]*framework.FieldSchema{ - "user_agent": { - Type: framework.TypeString, - Description: "User agent to connect to Duo (default \"\")", - }, - "username_format": { - Type: framework.TypeString, - Description: "Format string given auth method username as argument to create Duo username (default '%s')", - }, - "push_info": { - Type: framework.TypeString, - Description: "A string of URL-encoded key/value pairs that provides additional context about the authentication attempt in the Duo Mobile app", - }, - }, - - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: pathDuoConfigWrite, - logical.ReadOperation: pathDuoConfigRead, - }, - - HelpSynopsis: pathDuoConfigHelpSyn, - HelpDescription: pathDuoConfigHelpDesc, - } -} - -func GetDuoConfig(ctx context.Context, req *logical.Request) (*DuoConfig, error) { - var result DuoConfig - // all config parameters are optional, so path need not exist - entry, err := req.Storage.Get(ctx, "duo/config") - if err == nil && entry != nil { - if err := entry.DecodeJSON(&result); err != nil { - return nil, err - } - } - if result.UsernameFormat == "" { - result.UsernameFormat = "%s" - } - return &result, nil -} - -func pathDuoConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - username_format := d.Get("username_format").(string) - if username_format == "" { - username_format = "%s" - } - if !strings.Contains(username_format, "%s") { - return nil, errors.New("username_format must include username ('%s')") - } - entry, err := logical.StorageEntryJSON("duo/config", DuoConfig{ - UsernameFormat: username_format, - UserAgent: d.Get("user_agent").(string), - PushInfo: d.Get("push_info").(string), - }) - if err != nil { - return nil, err - } - - if err := req.Storage.Put(ctx, entry); err != nil { - return nil, err - } - - return nil, nil -} - -func pathDuoConfigRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - config, err := GetDuoConfig(ctx, req) - if err != nil { - return nil, err - } - if config == nil { - return nil, nil - } - - return &logical.Response{ - Data: map[string]interface{}{ - "username_format": config.UsernameFormat, - "user_agent": config.UserAgent, - "push_info": config.PushInfo, - }, - }, nil -} - -type DuoConfig struct { - UsernameFormat string `json:"username_format"` - UserAgent string `json:"user_agent"` - PushInfo string `json:"push_info"` -} - -const pathDuoConfigHelpSyn = ` -Configure Duo second factor behavior. -` - -const pathDuoConfigHelpDesc = ` -This endpoint allows you to configure how the original auth method username maps to -the Duo username by providing a template format string. -` diff --git a/helper/mfa/mfa.go b/helper/mfa/mfa.go deleted file mode 100644 index e1462cebe..000000000 --- a/helper/mfa/mfa.go +++ /dev/null @@ -1,88 +0,0 @@ -// Package mfa provides wrappers to add multi-factor authentication -// to any auth method. -// -// To add MFA to a backend, replace its login path with the -// paths returned by MFAPaths and add the additional root -// paths returned by MFARootPaths. The backend provides -// the username to the MFA wrapper in Auth.Metadata['username']. -// -// To add an additional MFA type, create a subpackage that -// implements [Type]Paths, [Type]RootPaths, and [Type]Handler -// functions and add them to MFAPaths, MFARootPaths, and -// handlers respectively. -package mfa - -import ( - "context" - - "github.com/hashicorp/vault/helper/mfa/duo" - "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/logical" -) - -// MFAPaths returns paths to wrap the original login path and configure MFA. -// When adding MFA to a backend, these paths should be included instead of -// the login path in Backend.Paths. -func MFAPaths(originalBackend *framework.Backend, loginPath *framework.Path) []*framework.Path { - var b backend - b.Backend = originalBackend - return append(duo.DuoPaths(), pathMFAConfig(&b), wrapLoginPath(&b, loginPath)) -} - -// MFARootPaths returns path strings used to configure MFA. When adding MFA -// to a backend, these paths should be included in -// Backend.PathsSpecial.Root. -func MFARootPaths() []string { - return append(duo.DuoRootPaths(), "mfa_config") -} - -// HandlerFunc is the callback called to handle MFA for a login request. -type HandlerFunc func(context.Context, *logical.Request, *framework.FieldData, *logical.Response) (*logical.Response, error) - -// handlers maps each supported MFA type to its handler. -var handlers = map[string]HandlerFunc{ - "duo": duo.DuoHandler, -} - -type backend struct { - *framework.Backend -} - -func wrapLoginPath(b *backend, loginPath *framework.Path) *framework.Path { - loginPath.Fields["passcode"] = &framework.FieldSchema{ - Type: framework.TypeString, - Description: "One time passcode (optional)", - } - loginPath.Fields["method"] = &framework.FieldSchema{ - Type: framework.TypeString, - Description: "Multi-factor auth method to use (optional)", - } - // wrap write callback to do MFA after auth - loginHandler := loginPath.Callbacks[logical.UpdateOperation] - loginPath.Callbacks[logical.UpdateOperation] = b.wrapLoginHandler(loginHandler) - return loginPath -} - -func (b *backend) wrapLoginHandler(loginHandler framework.OperationFunc) framework.OperationFunc { - return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - // login with original login function first - resp, err := loginHandler(ctx, req, d) - if err != nil || resp.Auth == nil { - return resp, err - } - - // check if multi-factor enabled - mfa_config, err := b.MFAConfig(ctx, req) - if err != nil || mfa_config == nil { - return resp, nil - } - - // perform multi-factor authentication if type supported - handler, ok := handlers[mfa_config.Type] - if ok { - return handler(ctx, req, d, resp) - } else { - return resp, err - } - } -} diff --git a/helper/mfa/mfa_test.go b/helper/mfa/mfa_test.go deleted file mode 100644 index 6e338ae19..000000000 --- a/helper/mfa/mfa_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package mfa - -import ( - "context" - "testing" - - logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" - "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/logical" -) - -// MakeTestBackend creates a simple MFA enabled backend. -// Login (before MFA) always succeeds with policy "foo". -// An MFA "test" type is added to mfa.handlers that succeeds -// if MFA method is "accept", otherwise it rejects. -func MakeTestBackend() *framework.Backend { - handlers["test"] = testMFAHandler - b := &framework.Backend{ - Help: "", - - PathsSpecial: &logical.Paths{ - Root: MFARootPaths(), - Unauthenticated: []string{ - "login", - }, - }, - Paths: MFAPaths(nil, testPathLogin()), - } - return b -} - -func testPathLogin() *framework.Path { - return &framework.Path{ - Pattern: `login`, - Fields: map[string]*framework.FieldSchema{ - "username": { - Type: framework.TypeString, - }, - }, - - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: testPathLoginHandler, - }, - } -} - -func testPathLoginHandler(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - username := d.Get("username").(string) - - return &logical.Response{ - Auth: &logical.Auth{ - Policies: []string{"foo"}, - Metadata: map[string]string{ - "username": username, - }, - }, - }, nil -} - -func testMFAHandler(ctx context.Context, req *logical.Request, d *framework.FieldData, resp *logical.Response) ( - *logical.Response, error) { - if d.Get("method").(string) != "accept" { - return logical.ErrorResponse("Deny access"), nil - } else { - return resp, nil - } -} - -func TestMFALogin(t *testing.T) { - b := MakeTestBackend() - - logicaltest.Test(t, logicaltest.TestCase{ - AcceptanceTest: true, - LogicalBackend: b, - Steps: []logicaltest.TestStep{ - testAccStepEnableMFA(t), - testAccStepLogin(t, "user"), - }, - }) -} - -func TestMFALoginDenied(t *testing.T) { - b := MakeTestBackend() - - logicaltest.Test(t, logicaltest.TestCase{ - AcceptanceTest: true, - LogicalBackend: b, - Steps: []logicaltest.TestStep{ - testAccStepEnableMFA(t), - testAccStepLoginDenied(t, "user"), - }, - }) -} - -func testAccStepEnableMFA(t *testing.T) logicaltest.TestStep { - return logicaltest.TestStep{ - Operation: logical.UpdateOperation, - Path: "mfa_config", - Data: map[string]interface{}{ - "type": "test", - }, - } -} - -func testAccStepLogin(t *testing.T, username string) logicaltest.TestStep { - return logicaltest.TestStep{ - Operation: logical.UpdateOperation, - Path: "login", - Data: map[string]interface{}{ - "method": "accept", - "username": username, - }, - Unauthenticated: true, - Check: logicaltest.TestCheckAuth([]string{"foo"}), - } -} - -func testAccStepLoginDenied(t *testing.T, username string) logicaltest.TestStep { - return logicaltest.TestStep{ - Operation: logical.UpdateOperation, - Path: "login", - Data: map[string]interface{}{ - "method": "deny", - "username": username, - }, - Unauthenticated: true, - Check: logicaltest.TestCheckError(), - } -} diff --git a/helper/mfa/path_mfa_config.go b/helper/mfa/path_mfa_config.go deleted file mode 100644 index 84b3ea133..000000000 --- a/helper/mfa/path_mfa_config.go +++ /dev/null @@ -1,87 +0,0 @@ -package mfa - -import ( - "context" - - "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/logical" -) - -func pathMFAConfig(b *backend) *framework.Path { - return &framework.Path{ - Pattern: `mfa_config`, - Fields: map[string]*framework.FieldSchema{ - "type": { - Type: framework.TypeString, - Description: "Enables MFA with given backend (available: duo)", - }, - }, - - Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: b.pathMFAConfigWrite, - logical.ReadOperation: b.pathMFAConfigRead, - }, - - HelpSynopsis: pathMFAConfigHelpSyn, - HelpDescription: pathMFAConfigHelpDesc, - } -} - -func (b *backend) MFAConfig(ctx context.Context, req *logical.Request) (*MFAConfig, error) { - entry, err := req.Storage.Get(ctx, "mfa_config") - if err != nil { - return nil, err - } - if entry == nil { - return nil, nil - } - var result MFAConfig - if err := entry.DecodeJSON(&result); err != nil { - return nil, err - } - return &result, nil -} - -func (b *backend) pathMFAConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - entry, err := logical.StorageEntryJSON("mfa_config", MFAConfig{ - Type: d.Get("type").(string), - }) - if err != nil { - return nil, err - } - - if err := req.Storage.Put(ctx, entry); err != nil { - return nil, err - } - - return nil, nil -} - -func (b *backend) pathMFAConfigRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - config, err := b.MFAConfig(ctx, req) - if err != nil { - return nil, err - } - if config == nil { - return nil, nil - } - - return &logical.Response{ - Data: map[string]interface{}{ - "type": config.Type, - }, - }, nil -} - -type MFAConfig struct { - Type string `json:"type"` -} - -const pathMFAConfigHelpSyn = ` -Configure multi factor backend. -` - -const pathMFAConfigHelpDesc = ` -This endpoint allows you to turn on multi-factor authentication with a given backend. -Currently only Duo is supported. -` diff --git a/website/content/docs/auth/mfa.mdx b/website/content/docs/auth/mfa.mdx deleted file mode 100644 index ca794d917..000000000 --- a/website/content/docs/auth/mfa.mdx +++ /dev/null @@ -1,106 +0,0 @@ ---- -layout: docs -page_title: Multi-Factor Authentication (MFA) - Auth Methods -description: |- - Multi-factor authentication (MFA) is supported for several authentication - methods. ---- - -# Multi-Factor Authentication - -~> **NOTE**: This page describes the legacy MFA system available in the OSS -edition of Vault. This system is not supported by HashiCorp. Vault Enterprise -contains a fully-supported MFA system that is significantly more complete and -flexible and which can be used throughout Vault's API. See the [Vault -Enterprise MFA](/docs/enterprise/mfa) page for more information. - -Several auth methods support multi-factor authentication (MFA). Once -enabled for a method, users are required to provide additional verification, -like a one-time passcode, before being authenticated. - -Currently, the "ldap", "okta", "radius", and "userpass" backends support MFA. - -## Authentication - -When authenticating, users still provide the same information as before, in -addition to MFA verification. Usually this is a passcode, but in other cases, -like a Duo Push notification, no additional information is needed. - -### Via the CLI - -```tedt -$ vault login -method=userpass \ - username=my-username \ - password=test \ - passcode=111111 -``` - -```shell-session -$ vault login -method=userpass \ - username=my-username \ - password=test \ - method=push -``` - -### Via the API - -The endpoint for the login is the same as for the original method. Additional -MFA information should be sent in the POST body encoded as JSON. - -```shell-session -$ curl \ - --request POST \ - --data '{"password": "test", "passcode": "111111"}' \ - http://127.0.0.1:8200/v1/auth/userpass/login/my-username -``` - -The response is the same as for the original method. - -## Configuration - -To enable MFA for a supported method, the MFA type must be set in `mfa_config`. -For example: - -```shell-session -$ vault write auth/userpass/mfa_config type=duo -``` - -This enables the Duo MFA type, which is currently the only MFA type supported. -The username used for MFA is the same as the login username, unless the method -or MFA type provide options to behave differently (see Duo configuration below). - -### Duo - -The Duo MFA type is configured through two paths: `duo/config` and `duo/access`. - -`duo/access` contains connection information for the Duo Auth API. To configure: - -```shell-session -$ vault write auth/[mount]/duo/access \ - host=[host] \ - ikey=[integration key] \ - skey=[secret key] -``` - -`duo/config` is an optional path that contains general configuration information -for Duo authentication. To configure: - -```shell-session -$ vault write auth/[mount]/duo/config \ - user_agent="" \ - username_format="%s" -``` - -- `user_agent` is the user agent to use when connecting to Duo. - -- `username_format` controls how the username used to login is transformed - before authenticating with Duo. This field is a format string that is passed - the original username as its first argument and outputs the new username. For - example "%s@example.com" would append "@example.com" to the provided username - before connecting to Duo. - -- `push_info` is a string of URL-encoded key/value pairs that provides - additional context about the authentication attempt in the Duo Mobile - application. - -More information can be found through the CLI `path-help` command. diff --git a/website/content/docs/deprecation/index.mdx b/website/content/docs/deprecation/index.mdx index 4fd28cd4c..8555f7be3 100644 --- a/website/content/docs/deprecation/index.mdx +++ b/website/content/docs/deprecation/index.mdx @@ -23,7 +23,7 @@ This announcement page is maintained and updated periodically to communicate imp | End of Support: Etcd V2 API (OSS) | v1.9 | N/A | v1.10 | The Etcd v2 has been deprecated with the release of Etcd v3.5, and will be decomissioned by Etcd v3.6. Etcd v2 API has been removed in Vaut 1.10. Users of Etcd storage backend must migrate Vault storage to an Etcd V3 cluster prior to upgrading to Vault 1.10. All storage migrations should be backed up prior to migration. | [Etcd Storage Backend](/docs/configuration/storage/etcd) | | End of Support: Licenses in storage (ENT) | v1.8 | v1.10 | v1.11 | Migrate to [Autoloading](/docs/enterprise/license/autoloading) by v1.11. | [Vault License](/docs/enterprise/license) [System Backend](https://www.vaultproject.io/api-docs/system/license) [FAQ](/docs/enterprise/license/faq) | | Feature Removal: Mount Filters (ENT) | v1.3 | v1.10 | v1.11 | Use the alternative feature: [Path Filters](https://www.vaultproject.io/api-docs/system/replication/replication-performance#create-paths-filter) | [API Deprecation Notice](https://www.vaultproject.io/api-docs/system/replication/replication-performance#create-mounts-filter-deprecated) [Filter Mount Replication Deprecation Notice](/docs/upgrading/upgrade-to-1.3.0#filtered-mount-replication-deprecation) | -| Feature Removal: Legacy MFA (OSS) | v1.0 | N/A | v1.11 | Based on your use case, use the Policy-based Enterprise MFA or Login MFA supported in Vault OSS as of v1.10 | [Multi-Factor Authentication](/docs/auth/mfa) | +| Feature Removal: Legacy MFA (OSS) | v1.0 | N/A | v1.11 | Based on your use case, use the Policy-based Enterprise MFA or Login MFA supported in Vault OSS as of v1.10 | [Multi-Factor Authentication](https://www.vaultproject.io/docs/v1.10.x/auth/mfa) | | Feature Removal: Standalone DB Engines (OSS) | v0.8 | N/A | v1.11 | Use the alternative DB secrets engine feature | [DB secrets engine](/docs/secrets/databases) | | Feature Removal: AppID (OSS) | v0.6 | N/A | v1.11 | Use the alternative feature: [AppRole auth method](https://www.vaultproject.io/docs/auth/approle) | [AppID Auth Method Deprecation Notice](/docs/auth/app-id) | | End of Support: AAD Graph on Azure Secrets Engine | v1.10 | 1.11 | v1.12 | Microsoft will end its support of the [AAD Graph API on June 30, 2022](https://docs.microsoft.com/en-us/graph/migrate-azure-ad-graph-overview). Support for Microsoft Graph API was introduced in Vault 1.9. If your Vault deployment is on a prior release, you may use the Azure Secrets Engine as an external plugin while you plan to upgrade. | [AAD (Azure Active Directory](https://vault-git-post-1-10-doc-changes-hashicorp.vercel.app/docs/secrets/azure#aad-azure-active-directory) | diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 1f9e3193a..ff11d2452 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -1256,10 +1256,6 @@ { "title": "App ID DEPRECATED", "path": "auth/app-id" - }, - { - "title": "MFA LEGACY / UNSUPPORTED", - "path": "auth/mfa" } ] },