mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	User Lockout Perf Standby Error oss (#20783)
* adding changes from ent * add changelog * removing new line * rename changelog file name
This commit is contained in:
		
							
								
								
									
										3
									
								
								changelog/20783.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/20783.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | ```release-note:bug | ||||||
|  | core: Fix writes to readonly storage on performance standbys when user lockout feature is enabled. | ||||||
|  | ``` | ||||||
| @@ -3546,18 +3546,19 @@ func (c *Core) runLockedUserEntryUpdatesForMountAccessor(ctx context.Context, mo | |||||||
| 		lockoutDurationFromConfiguration := userLockoutConfiguration.LockoutDuration | 		lockoutDurationFromConfiguration := userLockoutConfiguration.LockoutDuration | ||||||
|  |  | ||||||
| 		// get the entry for the locked user from userFailedLoginInfo map | 		// get the entry for the locked user from userFailedLoginInfo map | ||||||
| 		failedLoginInfoFromMap := c.GetUserFailedLoginInfo(ctx, loginUserInfoKey) | 		failedLoginInfoFromMap := c.LocalGetUserFailedLoginInfo(ctx, loginUserInfoKey) | ||||||
|  |  | ||||||
| 		// check if the storage entry for locked user is stale | 		// check if the storage entry for locked user is stale | ||||||
| 		if time.Now().After(lastFailedLoginTimeFromStorageEntry.Add(lockoutDurationFromConfiguration)) { | 		if time.Now().After(lastFailedLoginTimeFromStorageEntry.Add(lockoutDurationFromConfiguration)) { | ||||||
| 			// stale entry, remove from storage | 			// stale entry, remove from storage | ||||||
|  | 			// leaving this as it is as this happens on the active node | ||||||
|  | 			// also handles case where namespace is deleted | ||||||
| 			if err := c.barrier.Delete(ctx, path+alias); err != nil { | 			if err := c.barrier.Delete(ctx, path+alias); err != nil { | ||||||
| 				return 0, err | 				return 0, err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// remove entry for this user from userFailedLoginInfo map if present as the user is not locked | 			// remove entry for this user from userFailedLoginInfo map if present as the user is not locked | ||||||
| 			if failedLoginInfoFromMap != nil { | 			if failedLoginInfoFromMap != nil { | ||||||
| 				if err = c.UpdateUserFailedLoginInfo(ctx, loginUserInfoKey, nil, true); err != nil { | 				if err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, nil, true); err != nil { | ||||||
| 					return 0, err | 					return 0, err | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @@ -3574,7 +3575,7 @@ func (c *Core) runLockedUserEntryUpdatesForMountAccessor(ctx context.Context, mo | |||||||
|  |  | ||||||
| 		if failedLoginInfoFromMap != &actualFailedLoginInfo { | 		if failedLoginInfoFromMap != &actualFailedLoginInfo { | ||||||
| 			// entry is invalid, updating the entry in userFailedLoginMap with correct information | 			// entry is invalid, updating the entry in userFailedLoginMap with correct information | ||||||
| 			if err = c.UpdateUserFailedLoginInfo(ctx, loginUserInfoKey, &actualFailedLoginInfo, false); err != nil { | 			if err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, &actualFailedLoginInfo, false); err != nil { | ||||||
| 				return 0, err | 				return 0, err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -482,7 +482,7 @@ func TestCore_RunLockedUserUpdatesForValidEntry(t *testing.T) { | |||||||
| 		mountAccessor: "mountAccessor1", | 		mountAccessor: "mountAccessor1", | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	failedLoginInfoFromMap := core.GetUserFailedLoginInfo(context.Background(), loginUserInfoKey) | 	failedLoginInfoFromMap := core.LocalGetUserFailedLoginInfo(context.Background(), loginUserInfoKey) | ||||||
| 	if failedLoginInfoFromMap == nil { | 	if failedLoginInfoFromMap == nil { | ||||||
| 		t.Fatalf("err: entry must exist for locked user in userFailedLoginInfo map") | 		t.Fatalf("err: entry must exist for locked user in userFailedLoginInfo map") | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -4,9 +4,9 @@ | |||||||
| package identity | package identity | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/api" | 	"github.com/hashicorp/vault/api" | ||||||
| 	"github.com/hashicorp/vault/builtin/credential/userpass" | 	"github.com/hashicorp/vault/builtin/credential/userpass" | ||||||
| @@ -15,94 +15,21 @@ import ( | |||||||
| 	"github.com/hashicorp/vault/vault" | 	"github.com/hashicorp/vault/vault" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // TestIdentityStore_UserLockoutTest tests that the user gets locked after | const ( | ||||||
| // more than 1 failed login request than the number specified for | 	UserLockoutThresholdDefault = 5 | ||||||
| // lockout threshold field in user lockout configuration. It also | ) | ||||||
| // tests that the user gets unlocked after the duration specified |  | ||||||
| // for lockout duration field has passed |  | ||||||
| func TestIdentityStore_UserLockoutTest(t *testing.T) { |  | ||||||
| 	coreConfig := &vault.CoreConfig{ |  | ||||||
| 		CredentialBackends: map[string]logical.Factory{ |  | ||||||
| 			"userpass": userpass.Factory, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ |  | ||||||
| 		HandlerFunc: vaulthttp.Handler, |  | ||||||
| 	}) |  | ||||||
| 	cluster.Start() |  | ||||||
| 	defer cluster.Cleanup() |  | ||||||
| 	active := cluster.Cores[0].Client |  | ||||||
| 	standby := cluster.Cores[1].Client |  | ||||||
|  |  | ||||||
| 	err := active.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{ | // TestIdentityStore_DisableUserLockoutTest tests that user login will | ||||||
| 		Type: "userpass", | // fail when supplied with wrong credentials. If the user is locked, | ||||||
| 	}) | // it returns permission denied. Otherwise, it returns invalid user | ||||||
| 	if err != nil { | // credentials error if the user lockout feature is disabled. | ||||||
| 		t.Fatal(err) | // It tests disabling the feature using env variable VAULT_DISABLE_USER_LOCKOUT | ||||||
| 	} | // and also using auth tune. Also, tests that env var has more precedence over | ||||||
|  | // settings in auth tune. | ||||||
|  | func TestIdentityStore_DisableUserLockoutTest(t *testing.T) { | ||||||
|  | 	// reset to false before exiting | ||||||
|  | 	defer os.Unsetenv("VAULT_DISABLE_USER_LOCKOUT") | ||||||
|  |  | ||||||
| 	// tune auth mount |  | ||||||
| 	userlockoutConfig := &api.UserLockoutConfigInput{ |  | ||||||
| 		LockoutThreshold:            "3", |  | ||||||
| 		LockoutDuration:             "5s", |  | ||||||
| 		LockoutCounterResetDuration: "5s", |  | ||||||
| 	} |  | ||||||
| 	err = active.Sys().TuneMount("auth/userpass", api.MountConfigInput{ |  | ||||||
| 		UserLockoutConfig: userlockoutConfig, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// create a user for userpass |  | ||||||
| 	_, err = standby.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// login failure count 1 |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login failure count 2 |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login failure count 3 |  | ||||||
| 	active.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login : permission denied as user locked out |  | ||||||
| 	_, err = standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}) |  | ||||||
| 	if err == nil { |  | ||||||
| 		t.Fatal("expected login to fail as user locked out") |  | ||||||
| 	} |  | ||||||
| 	if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) { |  | ||||||
| 		t.Fatalf("expected to see permission denied error as user locked out, got %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	time.Sleep(5 * time.Second) |  | ||||||
|  |  | ||||||
| 	// login with right password and wait for user to get unlocked |  | ||||||
| 	_, err = standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal("expected login to succeed as user is unlocked") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TestIdentityStore_UserFailedLoginMapResetOnSuccess tests that |  | ||||||
| // the user lockout feature is reset for a user after one successfull attempt |  | ||||||
| // after multiple failed login attempts (within lockout threshold) |  | ||||||
| func TestIdentityStore_UserFailedLoginMapResetOnSuccess(t *testing.T) { |  | ||||||
| 	coreConfig := &vault.CoreConfig{ | 	coreConfig := &vault.CoreConfig{ | ||||||
| 		CredentialBackends: map[string]logical.Factory{ | 		CredentialBackends: map[string]logical.Factory{ | ||||||
| 			"userpass": userpass.Factory, | 			"userpass": userpass.Factory, | ||||||
| @@ -114,8 +41,10 @@ func TestIdentityStore_UserFailedLoginMapResetOnSuccess(t *testing.T) { | |||||||
| 	cluster.Start() | 	cluster.Start() | ||||||
| 	defer cluster.Cleanup() | 	defer cluster.Cleanup() | ||||||
|  |  | ||||||
| 	client := cluster.Cores[0].Client | 	// standby client | ||||||
|  | 	client := cluster.Cores[1].Client | ||||||
|  |  | ||||||
|  | 	// enable userpass | ||||||
| 	err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{ | 	err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{ | ||||||
| 		Type: "userpass", | 		Type: "userpass", | ||||||
| 	}) | 	}) | ||||||
| @@ -123,20 +52,7 @@ func TestIdentityStore_UserFailedLoginMapResetOnSuccess(t *testing.T) { | |||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// tune auth mount | 	// create a userpass user | ||||||
| 	userlockoutConfig := &api.UserLockoutConfigInput{ |  | ||||||
| 		LockoutThreshold:            "3", |  | ||||||
| 		LockoutDuration:             "5s", |  | ||||||
| 		LockoutCounterResetDuration: "5s", |  | ||||||
| 	} |  | ||||||
| 	err = client.Sys().TuneMount("auth/userpass", api.MountConfigInput{ |  | ||||||
| 		UserLockoutConfig: userlockoutConfig, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// create a user for userpass |  | ||||||
| 	_, err = client.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{ | 	_, err = client.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{ | ||||||
| 		"password": "training", | 		"password": "training", | ||||||
| 	}) | 	}) | ||||||
| @@ -144,308 +60,119 @@ func TestIdentityStore_UserFailedLoginMapResetOnSuccess(t *testing.T) { | |||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// login failure count 1 |  | ||||||
| 	client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login failure count 2 |  | ||||||
| 	client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login with right credentials - successful login |  | ||||||
| 	// entry for this user is removed from userFailedLoginInfo map |  | ||||||
| 	_, err = client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// login failure count 3, is now count 1 after successful login |  | ||||||
| 	client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login failure count 4, is now count 2 after successful login |  | ||||||
| 	// error should not be permission denied as user not locked out |  | ||||||
| 	_, err = client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
| 	if err == nil { |  | ||||||
| 		t.Fatal("expected login to fail due to wrong credentials") |  | ||||||
| 	} |  | ||||||
| 	if !strings.Contains(err.Error(), "invalid username or password") { |  | ||||||
| 		t.Fatalf("expected to see invalid username or password error as user is not locked out, got %v", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TestIdentityStore_DisableUserLockoutTest tests that user login will |  | ||||||
| // fail when supplied with wrong credentials. If the user is locked, |  | ||||||
| // it returns permission denied. In this case, it returns invalid user |  | ||||||
| // credentials error as the user lockout feature is disabled and the |  | ||||||
| // user did not get locked after multiple failed login attempts |  | ||||||
| func TestIdentityStore_DisableUserLockoutTest(t *testing.T) { |  | ||||||
| 	coreConfig := &vault.CoreConfig{ |  | ||||||
| 		CredentialBackends: map[string]logical.Factory{ |  | ||||||
| 			"userpass": userpass.Factory, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ |  | ||||||
| 		HandlerFunc: vaulthttp.Handler, |  | ||||||
| 	}) |  | ||||||
| 	cluster.Start() |  | ||||||
| 	defer cluster.Cleanup() |  | ||||||
|  |  | ||||||
| 	active := cluster.Cores[0].Client |  | ||||||
| 	standby := cluster.Cores[1].Client |  | ||||||
|  |  | ||||||
| 	err := active.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{ |  | ||||||
| 		Type: "userpass", |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// tune auth mount |  | ||||||
| 	disableLockout := true |  | ||||||
| 	userlockoutConfig := &api.UserLockoutConfigInput{ |  | ||||||
| 		LockoutThreshold: "3", |  | ||||||
| 		DisableLockout:   &disableLockout, |  | ||||||
| 	} |  | ||||||
| 	err = active.Sys().TuneMount("auth/userpass", api.MountConfigInput{ |  | ||||||
| 		UserLockoutConfig: userlockoutConfig, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// create a userpass user |  | ||||||
| 	_, err = standby.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// login failure count 1 |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login failure count 2 |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login failure count 3 |  | ||||||
| 	active.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// login failure count 4 |  | ||||||
| 	_, err = standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
| 	if err == nil { |  | ||||||
| 		t.Fatal("expected login to fail due to wrong credentials") |  | ||||||
| 	} |  | ||||||
| 	if !strings.Contains(err.Error(), "invalid username or password") { |  | ||||||
| 		t.Fatalf("expected to see invalid username or password error as user is not locked out, got %v", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TestIdentityStore_LockoutCounterResetTest tests that the user lockout counter |  | ||||||
| // for a user is reset after no failed login attempts for a duration |  | ||||||
| // as specified for lockout counter reset field in user lockout configuration |  | ||||||
| func TestIdentityStore_LockoutCounterResetTest(t *testing.T) { |  | ||||||
| 	coreConfig := &vault.CoreConfig{ |  | ||||||
| 		CredentialBackends: map[string]logical.Factory{ |  | ||||||
| 			"userpass": userpass.Factory, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ |  | ||||||
| 		HandlerFunc: vaulthttp.Handler, |  | ||||||
| 	}) |  | ||||||
| 	cluster.Start() |  | ||||||
| 	defer cluster.Cleanup() |  | ||||||
| 	active := cluster.Cores[0].Client |  | ||||||
| 	standby := cluster.Cores[1].Client |  | ||||||
|  |  | ||||||
| 	err := active.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{ |  | ||||||
| 		Type: "userpass", |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// tune auth mount |  | ||||||
| 	userlockoutConfig := &api.UserLockoutConfigInput{ |  | ||||||
| 		LockoutThreshold:            "3", |  | ||||||
| 		LockoutCounterResetDuration: "5s", |  | ||||||
| 	} |  | ||||||
| 	err = active.Sys().TuneMount("auth/userpass", api.MountConfigInput{ |  | ||||||
| 		UserLockoutConfig: userlockoutConfig, |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// create a user for userpass |  | ||||||
| 	_, err = standby.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// login failure count 1 |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
| 	// login failure count 2 |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// set sleep timer to reset login counter |  | ||||||
| 	time.Sleep(5 * time.Second) |  | ||||||
|  |  | ||||||
| 	// login failure 3, count should be reset, this will be treated as failed count 1 |  | ||||||
| 	active.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
| 	// login failure 4, this will be treated as failed count 2 |  | ||||||
| 	_, err = standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
| 	if err == nil { |  | ||||||
| 		t.Fatal("expected login to fail due to wrong credentials") |  | ||||||
| 	} |  | ||||||
| 	if !strings.Contains(err.Error(), "invalid username or password") { |  | ||||||
| 		t.Fatalf("expected to see invalid username or password error as user is not locked out, got %v", err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // TestIdentityStore_UnlockUserTest tests the user is |  | ||||||
| // unlocked if locked  using |  | ||||||
| // sys/locked-users/[mount_accessor]/unlock/[alias-identifier] |  | ||||||
| func TestIdentityStore_UnlockUserTest(t *testing.T) { |  | ||||||
| 	coreConfig := &vault.CoreConfig{ |  | ||||||
| 		CredentialBackends: map[string]logical.Factory{ |  | ||||||
| 			"userpass": userpass.Factory, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ |  | ||||||
| 		HandlerFunc: vaulthttp.Handler, |  | ||||||
| 	}) |  | ||||||
| 	cluster.Start() |  | ||||||
| 	defer cluster.Cleanup() |  | ||||||
| 	active := cluster.Cores[0].Client |  | ||||||
| 	standby := cluster.Cores[1].Client |  | ||||||
|  |  | ||||||
| 	// enable userpass auth method on path userpass |  | ||||||
| 	if err := active.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{ |  | ||||||
| 		Type: "userpass", |  | ||||||
| 	}); err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// get mount accessor for userpass mount | 	// get mount accessor for userpass mount | ||||||
| 	secret, err := standby.Logical().Read("sys/auth/userpass") | 	secret, err := client.Logical().Read("sys/auth/userpass") | ||||||
| 	if err != nil || secret == nil { | 	if err != nil || secret == nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	mountAccessor := secret.Data["accessor"].(string) | 	mountAccessor := secret.Data["accessor"].(string) | ||||||
|  |  | ||||||
|  | 	// variables for auth tune | ||||||
|  | 	disableLockout := true | ||||||
|  | 	enableLockout := false | ||||||
|  |  | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name                        string | ||||||
|  | 		setDisableUserLockoutEnvVar string | ||||||
|  | 		// default is false | ||||||
|  | 		setDisableLockoutAuthTune bool | ||||||
|  | 		expectedUserLocked        bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:                        "Both unset, uses default behaviour i.e; user lockout feature enabled", | ||||||
|  | 			setDisableUserLockoutEnvVar: "", | ||||||
|  | 			setDisableLockoutAuthTune:   false, | ||||||
|  | 			expectedUserLocked:          true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                        "User lockout feature is disabled using auth tune", | ||||||
|  | 			setDisableUserLockoutEnvVar: "", | ||||||
|  | 			setDisableLockoutAuthTune:   true, | ||||||
|  | 			expectedUserLocked:          false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                        "User Lockout feature is disabled using env var VAULT_DISABLE_USER_LOCKOUT", | ||||||
|  | 			setDisableUserLockoutEnvVar: "true", | ||||||
|  | 			setDisableLockoutAuthTune:   false, | ||||||
|  | 			expectedUserLocked:          false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                        "User lockout feature is enabled using env variable, disabled using auth tune", | ||||||
|  | 			setDisableUserLockoutEnvVar: "false", | ||||||
|  | 			setDisableLockoutAuthTune:   true, | ||||||
|  | 			expectedUserLocked:          true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:                        "User lockout feature is disabled using auth tune and env variable", | ||||||
|  | 			setDisableUserLockoutEnvVar: "true", | ||||||
|  | 			setDisableLockoutAuthTune:   true, | ||||||
|  | 			expectedUserLocked:          false, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			if tt.setDisableUserLockoutEnvVar != "" { | ||||||
|  | 				os.Setenv("VAULT_DISABLE_USER_LOCKOUT", tt.setDisableUserLockoutEnvVar) | ||||||
|  | 			} else { | ||||||
|  | 				os.Unsetenv("VAULT_DISABLE_USER_LOCKOUT") | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			var disableLockoutAuthTune *bool | ||||||
|  |  | ||||||
|  | 			// default for disable lockout is false | ||||||
|  | 			disableLockoutAuthTune = &enableLockout | ||||||
|  |  | ||||||
|  | 			if tt.setDisableLockoutAuthTune == true { | ||||||
|  | 				disableLockoutAuthTune = &disableLockout | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// tune auth mount | 			// tune auth mount | ||||||
| 			userlockoutConfig := &api.UserLockoutConfigInput{ | 			userlockoutConfig := &api.UserLockoutConfigInput{ | ||||||
| 		LockoutThreshold: "2", | 				DisableLockout: disableLockoutAuthTune, | ||||||
| 		LockoutDuration:  "5m", |  | ||||||
| 			} | 			} | ||||||
| 	if err = active.Sys().TuneMount("auth/userpass", api.MountConfigInput{ | 			err := client.Sys().TuneMount("auth/userpass", api.MountConfigInput{ | ||||||
| 				UserLockoutConfig: userlockoutConfig, | 				UserLockoutConfig: userlockoutConfig, | ||||||
| 	}); err != nil { | 			}) | ||||||
|  | 			if err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 	// create a user for userpass | 			// login for default lockout threshold times with wrong credentials | ||||||
| 	if _, err = standby.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{ | 			for i := 0; i < UserLockoutThresholdDefault; i++ { | ||||||
| 		"password": "training", | 				_, err = client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ | ||||||
| 	}); err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// create another user for userpass with a different case |  | ||||||
| 	if _, err = standby.Logical().Write("auth/userpass/users/bSmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}); err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// login failure count 1 |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ |  | ||||||
| 					"password": "wrongPassword", | 					"password": "wrongPassword", | ||||||
| 				}) | 				}) | ||||||
| 	// login failure count 2 | 				if err == nil { | ||||||
| 	standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ | 					t.Fatal("expected login to fail due to wrong credentials") | ||||||
|  | 				} | ||||||
|  | 				if !strings.Contains(err.Error(), "invalid username or password") { | ||||||
|  | 					t.Fatal(err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// login to check if user locked | ||||||
|  | 			_, err = client.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ | ||||||
| 				"password": "wrongPassword", | 				"password": "wrongPassword", | ||||||
| 			}) | 			}) | ||||||
| 	// login : permission denied as user locked out | 			if err == nil { | ||||||
| 	if _, err = standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ | 				t.Fatal("expected login to fail due to wrong credentials") | ||||||
| 		"password": "training", |  | ||||||
| 	}); err == nil { |  | ||||||
| 		t.Fatal("expected login to fail as user locked out") |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			switch tt.expectedUserLocked { | ||||||
|  | 			case true: | ||||||
| 				if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) { | 				if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) { | ||||||
| 		t.Fatalf("expected to see permission denied error as user locked out, got %v", err) | 					t.Fatalf("expected user to get locked but got %v", err) | ||||||
| 				} | 				} | ||||||
|  | 				// user locked, unlock user to perform next test iteration | ||||||
| 	// unlock user | 				if _, err = client.Logical().Write("sys/locked-users/"+mountAccessor+"/unlock/bsmith", nil); err != nil { | ||||||
| 	if _, err = standby.Logical().Write("sys/locked-users/"+mountAccessor+"/unlock/bsmith", nil); err != nil { |  | ||||||
| 					t.Fatal(err) | 					t.Fatal(err) | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 	// login: should be successful as user unlocked | 			default: | ||||||
| 	if _, err = standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{ | 				if !strings.Contains(err.Error(), "invalid username or password") { | ||||||
| 		"password": "training", | 					t.Fatalf("expected user to be unlocked but locked, got  %v", err) | ||||||
| 	}); err != nil { | 				} | ||||||
| 		t.Fatal("expected login to succeed as user is unlocked") |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 	// login failure count 1 for user bSmith |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bSmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 		}) | 		}) | ||||||
| 	// login failure count 2 for user bSmith |  | ||||||
| 	standby.Logical().Write("auth/userpass/login/bSmith", map[string]interface{}{ |  | ||||||
| 		"password": "wrongPassword", |  | ||||||
| 	}) |  | ||||||
| 	// login : permission denied as user locked out for user bSmith |  | ||||||
| 	if _, err = standby.Logical().Write("auth/userpass/login/bSmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}); err == nil { |  | ||||||
| 		t.Fatal("expected login to fail as user locked out") |  | ||||||
| 	} |  | ||||||
| 	if !strings.Contains(err.Error(), logical.ErrPermissionDenied.Error()) { |  | ||||||
| 		t.Fatalf("expected to see permission denied error as user locked out, got %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// unlock user bSmith |  | ||||||
| 	if _, err = standby.Logical().Write("sys/locked-users/"+mountAccessor+"/unlock/bSmith", nil); err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// login: should be successful as user bSmith unlocked |  | ||||||
| 	if _, err = standby.Logical().Write("auth/userpass/login/bSmith", map[string]interface{}{ |  | ||||||
| 		"password": "training", |  | ||||||
| 	}); err != nil { |  | ||||||
| 		t.Fatal("expected login to succeed as user is unlocked") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// unlock unlocked user |  | ||||||
| 	if _, err = active.Logical().Write("sys/locked-users/mountAccessor/unlock/bsmith", nil); err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -35,6 +35,8 @@ func unlockUser(ctx context.Context, core *Core, mountAccessor string, aliasName | |||||||
| 	lockedUserStoragePath := coreLockedUsersPath + ns.ID + "/" + mountAccessor + "/" + aliasName | 	lockedUserStoragePath := coreLockedUsersPath + ns.ID + "/" + mountAccessor + "/" + aliasName | ||||||
|  |  | ||||||
| 	// remove entry for locked user from storage | 	// remove entry for locked user from storage | ||||||
|  | 	// if read only error, the error is handled by handleError in logical_system.go | ||||||
|  | 	// this will be forwarded to the active node | ||||||
| 	if err := core.barrier.Delete(ctx, lockedUserStoragePath); err != nil { | 	if err := core.barrier.Delete(ctx, lockedUserStoragePath); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -44,7 +46,7 @@ func unlockUser(ctx context.Context, core *Core, mountAccessor string, aliasName | |||||||
| 		mountAccessor: mountAccessor, | 		mountAccessor: mountAccessor, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// remove entry for locked user from userFailedLoginInfo map | 	// remove entry for locked user from userFailedLoginInfo map and storage | ||||||
| 	if err := updateUserFailedLoginInfo(ctx, core, loginUserInfoKey, nil, true); err != nil { | 	if err := updateUserFailedLoginInfo(ctx, core, loginUserInfoKey, nil, true); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1402,10 +1402,12 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re | |||||||
|  |  | ||||||
| 	// if routeErr has invalid credentials error, update the userFailedLoginMap | 	// if routeErr has invalid credentials error, update the userFailedLoginMap | ||||||
| 	if routeErr != nil && routeErr == logical.ErrInvalidCredentials { | 	if routeErr != nil && routeErr == logical.ErrInvalidCredentials { | ||||||
|  | 		if !isUserLockoutDisabled { | ||||||
| 			err := c.failedUserLoginProcess(ctx, entry, req) | 			err := c.failedUserLoginProcess(ctx, entry, req) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, nil, err | 				return nil, nil, err | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 		return nil, nil, resp.Error() | 		return nil, nil, resp.Error() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -1689,6 +1691,10 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re | |||||||
| 			aliasName:     auth.Alias.Name, | 			aliasName:     auth.Alias.Name, | ||||||
| 			mountAccessor: auth.Alias.MountAccessor, | 			mountAccessor: auth.Alias.MountAccessor, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// We don't need to try to delete the lockedUsers storage entry, since we're | ||||||
|  | 		// processing a login request. If a login attempt is allowed, it means the user is | ||||||
|  | 		// unlocked and we only add storage entry when the user gets locked. | ||||||
| 		err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, nil, true) | 		err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, nil, true) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, nil, err | 			return nil, nil, err | ||||||
| @@ -1855,39 +1861,12 @@ func (c *Core) failedUserLoginProcess(ctx context.Context, mountEntry *MountEntr | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// update the userFailedLoginInfo map with the updated/new entry | 	// update the userFailedLoginInfo map (and/or storage) with the updated/new entry | ||||||
| 	err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, &failedLoginInfo, false) | 	err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, &failedLoginInfo, false) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// if failed login count has reached threshold, create a storage entry as the user got locked |  | ||||||
| 	if failedLoginInfo.count >= uint(userLockoutConfiguration.LockoutThreshold) { |  | ||||||
| 		// user locked |  | ||||||
| 		ns, err := namespace.FromContext(ctx) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("could not parse namespace from http context: %w", err) |  | ||||||
| 		} |  | ||||||
| 		storageUserLockoutPath := fmt.Sprintf(coreLockedUsersPath+"%s/%s/%s", ns.ID, loginUserInfoKey.mountAccessor, loginUserInfoKey.aliasName) |  | ||||||
| 		compressedBytes, err := jsonutil.EncodeJSONAndCompress(failedLoginInfo.lastFailedLoginTime, nil) |  | ||||||
| 		if err != nil { |  | ||||||
| 			c.logger.Error("failed to encode or compress failed login user entry", "error", err) |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Create an entry |  | ||||||
| 		entry := &logical.StorageEntry{ |  | ||||||
| 			Key:   storageUserLockoutPath, |  | ||||||
| 			Value: compressedBytes, |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Write to the physical backend |  | ||||||
| 		if err := c.barrier.Put(ctx, entry); err != nil { |  | ||||||
| 			c.logger.Error("failed to persist failed login user entry", "error", err) |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 	} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1916,17 +1895,17 @@ func (c *Core) isUserLockoutDisabled(mountEntry *MountEntry) (bool, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// check environment variable | 	// check environment variable | ||||||
| 	var disableUserLockout bool |  | ||||||
| 	if disableUserLockoutEnv := os.Getenv(consts.VaultDisableUserLockout); disableUserLockoutEnv != "" { | 	if disableUserLockoutEnv := os.Getenv(consts.VaultDisableUserLockout); disableUserLockoutEnv != "" { | ||||||
| 		var err error | 		var err error | ||||||
| 		disableUserLockout, err = strconv.ParseBool(disableUserLockoutEnv) | 		disableUserLockout, err := strconv.ParseBool(disableUserLockoutEnv) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return false, errors.New("Error parsing the environment variable VAULT_DISABLE_USER_LOCKOUT") | 			return false, errors.New("Error parsing the environment variable VAULT_DISABLE_USER_LOCKOUT") | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
| 		if disableUserLockout { | 		if disableUserLockout { | ||||||
| 			return true, nil | 			return true, nil | ||||||
| 		} | 		} | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// read auth tune for mount entry | 	// read auth tune for mount entry | ||||||
| 	userLockoutConfigFromMount := mountEntry.Config.UserLockoutConfig | 	userLockoutConfigFromMount := mountEntry.Config.UserLockoutConfig | ||||||
| @@ -1986,13 +1965,11 @@ func (c *Core) isUserLocked(ctx context.Context, mountEntry *MountEntry, req *lo | |||||||
| 		if time.Now().Unix()-int64(lastLoginTime) < int64(userLockoutConfiguration.LockoutDuration.Seconds()) { | 		if time.Now().Unix()-int64(lastLoginTime) < int64(userLockoutConfiguration.LockoutDuration.Seconds()) { | ||||||
| 			// user locked | 			// user locked | ||||||
| 			return true, nil | 			return true, nil | ||||||
| 		} else { |  | ||||||
| 			// user is not locked. Entry is stale, remove this from storage |  | ||||||
| 			if err := c.barrier.Delete(ctx, storageUserLockoutPath); err != nil { |  | ||||||
| 				c.logger.Error("failed to cleanup storage entry for user", "path", storageUserLockoutPath, "error", err) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// else user is not locked. Entry is stale, this will be removed from storage during cleanup | ||||||
|  | 		// by the background thread | ||||||
|  |  | ||||||
| 	default: | 	default: | ||||||
| 		// entry found in userFailedLoginInfo map, check if the user is locked | 		// entry found in userFailedLoginInfo map, check if the user is locked | ||||||
| 		isCountOverLockoutThreshold := userFailedLoginInfo.count >= uint(userLockoutConfiguration.LockoutThreshold) | 		isCountOverLockoutThreshold := userFailedLoginInfo.count >= uint(userLockoutConfiguration.LockoutThreshold) | ||||||
| @@ -2185,7 +2162,11 @@ func (c *Core) RegisterAuth(ctx context.Context, tokenTTL time.Duration, path st | |||||||
| 				aliasName:     auth.Alias.Name, | 				aliasName:     auth.Alias.Name, | ||||||
| 				mountAccessor: auth.Alias.MountAccessor, | 				mountAccessor: auth.Alias.MountAccessor, | ||||||
| 			} | 			} | ||||||
| 			err = c.UpdateUserFailedLoginInfo(ctx, loginUserInfoKey, nil, true) |  | ||||||
|  | 			// We don't need to try to delete the lockedUsers storage entry, since we're | ||||||
|  | 			// processing a login request. If a login attempt is allowed, it means the user is | ||||||
|  | 			// unlocked and we only add storage entry when the user gets locked. | ||||||
|  | 			err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, nil, true) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| @@ -2194,8 +2175,8 @@ func (c *Core) RegisterAuth(ctx context.Context, tokenTTL time.Duration, path st | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetUserFailedLoginInfo gets the failed login information for a user based on alias name and mountAccessor | // LocalGetUserFailedLoginInfo gets the failed login information for a user based on alias name and mountAccessor | ||||||
| func (c *Core) GetUserFailedLoginInfo(ctx context.Context, userKey FailedLoginUser) *FailedLoginInfo { | func (c *Core) LocalGetUserFailedLoginInfo(ctx context.Context, userKey FailedLoginUser) *FailedLoginInfo { | ||||||
| 	c.userFailedLoginInfoLock.Lock() | 	c.userFailedLoginInfoLock.Lock() | ||||||
| 	value, exists := c.userFailedLoginInfo[userKey] | 	value, exists := c.userFailedLoginInfo[userKey] | ||||||
| 	c.userFailedLoginInfoLock.Unlock() | 	c.userFailedLoginInfoLock.Unlock() | ||||||
| @@ -2205,23 +2186,52 @@ func (c *Core) GetUserFailedLoginInfo(ctx context.Context, userKey FailedLoginUs | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // UpdateUserFailedLoginInfo updates the failed login information for a user based on alias name and mountAccessor | // LocalUpdateUserFailedLoginInfo updates the failed login information for a user based on alias name and mountAccessor | ||||||
| func (c *Core) UpdateUserFailedLoginInfo(ctx context.Context, userKey FailedLoginUser, failedLoginInfo *FailedLoginInfo, deleteEntry bool) error { | func (c *Core) LocalUpdateUserFailedLoginInfo(ctx context.Context, userKey FailedLoginUser, failedLoginInfo *FailedLoginInfo, deleteEntry bool) error { | ||||||
| 	c.userFailedLoginInfoLock.Lock() | 	c.userFailedLoginInfoLock.Lock() | ||||||
| 	switch deleteEntry { | 	switch deleteEntry { | ||||||
| 	case false: | 	case false: | ||||||
| 		// create or update entry in the map | 		// update entry in the map | ||||||
| 		c.userFailedLoginInfo[userKey] = failedLoginInfo | 		c.userFailedLoginInfo[userKey] = failedLoginInfo | ||||||
|  |  | ||||||
|  | 		// get the user lockout configuration for the user | ||||||
|  | 		mountEntry := c.router.MatchingMountByAccessor(userKey.mountAccessor) | ||||||
|  | 		if mountEntry == nil { | ||||||
|  | 			mountEntry = &MountEntry{} | ||||||
|  | 			mountEntry.NamespaceID = namespace.RootNamespaceID | ||||||
|  | 		} | ||||||
|  | 		userLockoutConfiguration := c.getUserLockoutConfiguration(mountEntry) | ||||||
|  |  | ||||||
|  | 		// if failed login count has reached threshold, create a storage entry as the user got locked | ||||||
|  | 		if failedLoginInfo.count >= uint(userLockoutConfiguration.LockoutThreshold) { | ||||||
|  | 			// user locked | ||||||
|  | 			storageUserLockoutPath := fmt.Sprintf(coreLockedUsersPath+"%s/%s/%s", mountEntry.NamespaceID, userKey.mountAccessor, userKey.aliasName) | ||||||
|  |  | ||||||
|  | 			compressedBytes, err := jsonutil.EncodeJSONAndCompress(failedLoginInfo.lastFailedLoginTime, nil) | ||||||
|  | 			if err != nil { | ||||||
|  | 				c.logger.Error("failed to encode or compress failed login user entry", "error", err) | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Create an entry | ||||||
|  | 			entry := &logical.StorageEntry{ | ||||||
|  | 				Key:   storageUserLockoutPath, | ||||||
|  | 				Value: compressedBytes, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Write to the physical backend | ||||||
|  | 			if err := c.barrier.Put(ctx, entry); err != nil { | ||||||
|  | 				c.logger.Error("failed to persist failed login user entry", "error", err) | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	default: | 	default: | ||||||
| 		// delete the entry from the map | 		// delete the entry from the map, if no key exists it is no-op | ||||||
| 		delete(c.userFailedLoginInfo, userKey) | 		delete(c.userFailedLoginInfo, userKey) | ||||||
| 	} | 	} | ||||||
| 	c.userFailedLoginInfoLock.Unlock() | 	c.userFailedLoginInfoLock.Unlock() | ||||||
| 	// check if the update worked |  | ||||||
| 	failedLoginResp := c.GetUserFailedLoginInfo(ctx, userKey) |  | ||||||
| 	if (failedLoginResp == nil && !deleteEntry) || (failedLoginResp != nil && deleteEntry) { |  | ||||||
| 		return fmt.Errorf("failed to update entry in userFailedLoginInfo map") |  | ||||||
| 	} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -55,11 +55,11 @@ func getAuthRegisterFunc(c *Core) (RegisterAuthFunc, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func getUserFailedLoginInfo(ctx context.Context, c *Core, userInfo FailedLoginUser) (*FailedLoginInfo, error) { | func getUserFailedLoginInfo(ctx context.Context, c *Core, userInfo FailedLoginUser) (*FailedLoginInfo, error) { | ||||||
| 	return c.GetUserFailedLoginInfo(ctx, userInfo), nil | 	return c.LocalGetUserFailedLoginInfo(ctx, userInfo), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func updateUserFailedLoginInfo(ctx context.Context, c *Core, userInfo FailedLoginUser, failedLoginInfo *FailedLoginInfo, deleteEntry bool) error { | func updateUserFailedLoginInfo(ctx context.Context, c *Core, userInfo FailedLoginUser, failedLoginInfo *FailedLoginInfo, deleteEntry bool) error { | ||||||
| 	return c.UpdateUserFailedLoginInfo(ctx, userInfo, failedLoginInfo, deleteEntry) | 	return c.LocalUpdateUserFailedLoginInfo(ctx, userInfo, failedLoginInfo, deleteEntry) | ||||||
| } | } | ||||||
|  |  | ||||||
| func possiblyForwardAliasCreation(ctx context.Context, c *Core, inErr error, auth *logical.Auth, entity *identity.Entity) (*identity.Entity, bool, error) { | func possiblyForwardAliasCreation(ctx context.Context, c *Core, inErr error, auth *logical.Auth, entity *identity.Entity) (*identity.Entity, bool, error) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 akshya96
					akshya96