16ce923ddb
* brute forcing unlock user bug * add changelog * fix changelog
449 lines
13 KiB
Go
449 lines
13 KiB
Go
package identity
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/hashicorp/vault/builtin/credential/userpass"
|
|
vaulthttp "github.com/hashicorp/vault/http"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/hashicorp/vault/vault"
|
|
)
|
|
|
|
// TestIdentityStore_UserLockoutTest tests that the user gets locked after
|
|
// more than 1 failed login request than the number specified for
|
|
// 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{
|
|
Type: "userpass",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// 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{
|
|
CredentialBackends: map[string]logical.Factory{
|
|
"userpass": userpass.Factory,
|
|
},
|
|
}
|
|
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
|
HandlerFunc: vaulthttp.Handler,
|
|
})
|
|
cluster.Start()
|
|
defer cluster.Cleanup()
|
|
|
|
client := cluster.Cores[0].Client
|
|
|
|
err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
|
|
Type: "userpass",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// tune auth mount
|
|
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{}{
|
|
"password": "training",
|
|
})
|
|
if err != nil {
|
|
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
|
|
secret, err := standby.Logical().Read("sys/auth/userpass")
|
|
if err != nil || secret == nil {
|
|
t.Fatal(err)
|
|
}
|
|
mountAccessor := secret.Data["accessor"].(string)
|
|
|
|
// tune auth mount
|
|
userlockoutConfig := &api.UserLockoutConfigInput{
|
|
LockoutThreshold: "2",
|
|
LockoutDuration: "5m",
|
|
}
|
|
if err = active.Sys().TuneMount("auth/userpass", api.MountConfigInput{
|
|
UserLockoutConfig: userlockoutConfig,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a user for userpass
|
|
if _, err = standby.Logical().Write("auth/userpass/users/bsmith", map[string]interface{}{
|
|
"password": "training",
|
|
}); 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",
|
|
})
|
|
// login failure count 2
|
|
standby.Logical().Write("auth/userpass/login/bsmith", map[string]interface{}{
|
|
"password": "wrongPassword",
|
|
})
|
|
// login : permission denied as user locked out
|
|
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
|
|
if _, err = standby.Logical().Write("sys/locked-users/"+mountAccessor+"/unlock/bsmith", nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// login: should be successful as user 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")
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|