mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Vault 8305 Prevent Brute Forcing in Auth methods : Setting user lockout configuration (#17338)
* config file changes * lockout config changes * auth tune r/w and auth tune * removing changes at enable * removing q.Q * go mod tidy * removing comments * changing struct name for config file * fixing mount tune * adding test file for user lockout * fixing comments and add changelog * addressing comments * fixing mount table updates * updating consts in auth_tune * small fixes * adding hcl parse test * fixing config compare * fixing github comments * optimize userlockouts.go * fixing test * minor changes * adding comments * adding sort to flaky test * fix flaky test
This commit is contained in:
		| @@ -254,20 +254,20 @@ type MountInput struct { | ||||
| } | ||||
|  | ||||
| type MountConfigInput struct { | ||||
| 	Options                   map[string]string `json:"options" mapstructure:"options"` | ||||
| 	DefaultLeaseTTL           string            `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||
| 	Description               *string           `json:"description,omitempty" mapstructure:"description"` | ||||
| 	MaxLeaseTTL               string            `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||
| 	ForceNoCache              bool              `json:"force_no_cache" mapstructure:"force_no_cache"` | ||||
| 	AuditNonHMACRequestKeys   []string          `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string          `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string            `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string          `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` | ||||
| 	AllowedResponseHeaders    []string          `json:"allowed_response_headers,omitempty" mapstructure:"allowed_response_headers"` | ||||
| 	TokenType                 string            `json:"token_type,omitempty" mapstructure:"token_type"` | ||||
| 	AllowedManagedKeys        []string          `json:"allowed_managed_keys,omitempty" mapstructure:"allowed_managed_keys"` | ||||
| 	PluginVersion             string            `json:"plugin_version,omitempty"` | ||||
|  | ||||
| 	Options                   map[string]string       `json:"options" mapstructure:"options"` | ||||
| 	DefaultLeaseTTL           string                  `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||
| 	Description               *string                 `json:"description,omitempty" mapstructure:"description"` | ||||
| 	MaxLeaseTTL               string                  `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||
| 	ForceNoCache              bool                    `json:"force_no_cache" mapstructure:"force_no_cache"` | ||||
| 	AuditNonHMACRequestKeys   []string                `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string                `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string                  `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string                `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` | ||||
| 	AllowedResponseHeaders    []string                `json:"allowed_response_headers,omitempty" mapstructure:"allowed_response_headers"` | ||||
| 	TokenType                 string                  `json:"token_type,omitempty" mapstructure:"token_type"` | ||||
| 	AllowedManagedKeys        []string                `json:"allowed_managed_keys,omitempty" mapstructure:"allowed_managed_keys"` | ||||
| 	PluginVersion             string                  `json:"plugin_version,omitempty"` | ||||
| 	UserLockoutConfig         *UserLockoutConfigInput `json:"user_lockout_config,omitempty"` | ||||
| 	// Deprecated: This field will always be blank for newer server responses. | ||||
| 	PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||
| } | ||||
| @@ -289,21 +289,35 @@ type MountOutput struct { | ||||
| } | ||||
|  | ||||
| type MountConfigOutput struct { | ||||
| 	DefaultLeaseTTL           int      `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||
| 	MaxLeaseTTL               int      `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||
| 	ForceNoCache              bool     `json:"force_no_cache" mapstructure:"force_no_cache"` | ||||
| 	AuditNonHMACRequestKeys   []string `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string   `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` | ||||
| 	AllowedResponseHeaders    []string `json:"allowed_response_headers,omitempty" mapstructure:"allowed_response_headers"` | ||||
| 	TokenType                 string   `json:"token_type,omitempty" mapstructure:"token_type"` | ||||
| 	AllowedManagedKeys        []string `json:"allowed_managed_keys,omitempty" mapstructure:"allowed_managed_keys"` | ||||
|  | ||||
| 	DefaultLeaseTTL           int                      `json:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||
| 	MaxLeaseTTL               int                      `json:"max_lease_ttl" mapstructure:"max_lease_ttl"` | ||||
| 	ForceNoCache              bool                     `json:"force_no_cache" mapstructure:"force_no_cache"` | ||||
| 	AuditNonHMACRequestKeys   []string                 `json:"audit_non_hmac_request_keys,omitempty" mapstructure:"audit_non_hmac_request_keys"` | ||||
| 	AuditNonHMACResponseKeys  []string                 `json:"audit_non_hmac_response_keys,omitempty" mapstructure:"audit_non_hmac_response_keys"` | ||||
| 	ListingVisibility         string                   `json:"listing_visibility,omitempty" mapstructure:"listing_visibility"` | ||||
| 	PassthroughRequestHeaders []string                 `json:"passthrough_request_headers,omitempty" mapstructure:"passthrough_request_headers"` | ||||
| 	AllowedResponseHeaders    []string                 `json:"allowed_response_headers,omitempty" mapstructure:"allowed_response_headers"` | ||||
| 	TokenType                 string                   `json:"token_type,omitempty" mapstructure:"token_type"` | ||||
| 	AllowedManagedKeys        []string                 `json:"allowed_managed_keys,omitempty" mapstructure:"allowed_managed_keys"` | ||||
| 	UserLockoutConfig         *UserLockoutConfigOutput `json:"user_lockout_config,omitempty"` | ||||
| 	// Deprecated: This field will always be blank for newer server responses. | ||||
| 	PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||
| } | ||||
|  | ||||
| type UserLockoutConfigInput struct { | ||||
| 	LockoutThreshold            string `json:"lockout_threshold,omitempty" structs:"lockout_threshold" mapstructure:"lockout_threshold"` | ||||
| 	LockoutDuration             string `json:"lockout_duration,omitempty" structs:"lockout_duration" mapstructure:"lockout_duration"` | ||||
| 	LockoutCounterResetDuration string `json:"lockout_counter_reset_duration,omitempty" structs:"lockout_counter_reset_duration" mapstructure:"lockout_counter_reset_duration"` | ||||
| 	DisableLockout              *bool  `json:"lockout_disable,omitempty" structs:"lockout_disable" mapstructure:"lockout_disable"` | ||||
| } | ||||
|  | ||||
| type UserLockoutConfigOutput struct { | ||||
| 	LockoutThreshold    uint  `json:"lockout_threshold,omitempty" structs:"lockout_threshold" mapstructure:"lockout_threshold"` | ||||
| 	LockoutDuration     int   `json:"lockout_duration,omitempty" structs:"lockout_duration" mapstructure:"lockout_duration"` | ||||
| 	LockoutCounterReset int   `json:"lockout_counter_reset,omitempty" structs:"lockout_counter_reset" mapstructure:"lockout_counter_reset"` | ||||
| 	DisableLockout      *bool `json:"disable_lockout,omitempty" structs:"disable_lockout" mapstructure:"disable_lockout"` | ||||
| } | ||||
|  | ||||
| type MountMigrationOutput struct { | ||||
| 	MigrationID string `mapstructure:"migration_id"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								changelog/17338.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/17338.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ```release-note:feature | ||||
| core: Add user lockout field to config and configuring this for auth mount using auth tune to prevent brute forcing in auth methods  | ||||
| ``` | ||||
| @@ -20,18 +20,22 @@ var ( | ||||
| type AuthTuneCommand struct { | ||||
| 	*BaseCommand | ||||
|  | ||||
| 	flagAuditNonHMACRequestKeys   []string | ||||
| 	flagAuditNonHMACResponseKeys  []string | ||||
| 	flagDefaultLeaseTTL           time.Duration | ||||
| 	flagDescription               string | ||||
| 	flagListingVisibility         string | ||||
| 	flagMaxLeaseTTL               time.Duration | ||||
| 	flagPassthroughRequestHeaders []string | ||||
| 	flagAllowedResponseHeaders    []string | ||||
| 	flagOptions                   map[string]string | ||||
| 	flagTokenType                 string | ||||
| 	flagVersion                   int | ||||
| 	flagPluginVersion             string | ||||
| 	flagAuditNonHMACRequestKeys         []string | ||||
| 	flagAuditNonHMACResponseKeys        []string | ||||
| 	flagDefaultLeaseTTL                 time.Duration | ||||
| 	flagDescription                     string | ||||
| 	flagListingVisibility               string | ||||
| 	flagMaxLeaseTTL                     time.Duration | ||||
| 	flagPassthroughRequestHeaders       []string | ||||
| 	flagAllowedResponseHeaders          []string | ||||
| 	flagOptions                         map[string]string | ||||
| 	flagTokenType                       string | ||||
| 	flagVersion                         int | ||||
| 	flagPluginVersion                   string | ||||
| 	flagUserLockoutThreshold            uint | ||||
| 	flagUserLockoutDuration             time.Duration | ||||
| 	flagUserLockoutCounterResetDuration time.Duration | ||||
| 	flagUserLockoutDisable              bool | ||||
| } | ||||
|  | ||||
| func (c *AuthTuneCommand) Synopsis() string { | ||||
| @@ -145,6 +149,41 @@ func (c *AuthTuneCommand) Flags() *FlagSets { | ||||
| 		Usage:   "Select the version of the auth method to run. Not supported by all auth methods.", | ||||
| 	}) | ||||
|  | ||||
| 	f.UintVar(&UintVar{ | ||||
| 		Name:   flagNameUserLockoutThreshold, | ||||
| 		Target: &c.flagUserLockoutThreshold, | ||||
| 		Usage: "The threshold for user lockout for this auth method. If unspecified, this " + | ||||
| 			"defaults to the Vault server's globally configured user lockout threshold, " + | ||||
| 			"or a previously configured value for the auth method.", | ||||
| 	}) | ||||
|  | ||||
| 	f.DurationVar(&DurationVar{ | ||||
| 		Name:       flagNameUserLockoutDuration, | ||||
| 		Target:     &c.flagUserLockoutDuration, | ||||
| 		Completion: complete.PredictAnything, | ||||
| 		Usage: "The user lockout duration for this auth method. If unspecified, this " + | ||||
| 			"defaults to the Vault server's globally configured user lockout duration, " + | ||||
| 			"or a previously configured value for the auth method.", | ||||
| 	}) | ||||
|  | ||||
| 	f.DurationVar(&DurationVar{ | ||||
| 		Name:       flagNameUserLockoutCounterResetDuration, | ||||
| 		Target:     &c.flagUserLockoutCounterResetDuration, | ||||
| 		Completion: complete.PredictAnything, | ||||
| 		Usage: "The user lockout counter reset duration for this auth method. If unspecified, this " + | ||||
| 			"defaults to the Vault server's globally configured user lockout counter reset duration, " + | ||||
| 			"or a previously configured value for the auth method.", | ||||
| 	}) | ||||
|  | ||||
| 	f.BoolVar(&BoolVar{ | ||||
| 		Name:    flagNameUserLockoutDisable, | ||||
| 		Target:  &c.flagUserLockoutDisable, | ||||
| 		Default: false, | ||||
| 		Usage: "Disable user lockout for this auth method. If unspecified, this " + | ||||
| 			"defaults to the Vault server's globally configured user lockout disable, " + | ||||
| 			"or a previously configured value for the auth method.", | ||||
| 	}) | ||||
|  | ||||
| 	f.StringVar(&StringVar{ | ||||
| 		Name:    flagNamePluginVersion, | ||||
| 		Target:  &c.flagPluginVersion, | ||||
| @@ -230,6 +269,24 @@ func (c *AuthTuneCommand) Run(args []string) int { | ||||
| 		if fl.Name == flagNameTokenType { | ||||
| 			mountConfigInput.TokenType = c.flagTokenType | ||||
| 		} | ||||
| 		switch fl.Name { | ||||
| 		case flagNameUserLockoutThreshold, flagNameUserLockoutDuration, flagNameUserLockoutCounterResetDuration, flagNameUserLockoutDisable: | ||||
| 			if mountConfigInput.UserLockoutConfig == nil { | ||||
| 				mountConfigInput.UserLockoutConfig = &api.UserLockoutConfigInput{} | ||||
| 			} | ||||
| 		} | ||||
| 		if fl.Name == flagNameUserLockoutThreshold { | ||||
| 			mountConfigInput.UserLockoutConfig.LockoutThreshold = strconv.FormatUint(uint64(c.flagUserLockoutThreshold), 10) | ||||
| 		} | ||||
| 		if fl.Name == flagNameUserLockoutDuration { | ||||
| 			mountConfigInput.UserLockoutConfig.LockoutDuration = ttlToAPI(c.flagUserLockoutDuration) | ||||
| 		} | ||||
| 		if fl.Name == flagNameUserLockoutCounterResetDuration { | ||||
| 			mountConfigInput.UserLockoutConfig.LockoutCounterResetDuration = ttlToAPI(c.flagUserLockoutCounterResetDuration) | ||||
| 		} | ||||
| 		if fl.Name == flagNameUserLockoutDisable { | ||||
| 			mountConfigInput.UserLockoutConfig.DisableLockout = &c.flagUserLockoutDisable | ||||
| 		} | ||||
|  | ||||
| 		if fl.Name == flagNamePluginVersion { | ||||
| 			mountConfigInput.PluginVersion = c.flagPluginVersion | ||||
|   | ||||
| @@ -126,6 +126,14 @@ const ( | ||||
| 	flagNameAllowedManagedKeys = "allowed-managed-keys" | ||||
| 	// flagNamePluginVersion selects what version of a plugin should be used. | ||||
| 	flagNamePluginVersion = "plugin-version" | ||||
| 	// flagNameUserLockoutThreshold is the flag name used for tuning the auth mount lockout threshold parameter | ||||
| 	flagNameUserLockoutThreshold = "user-lockout-threshold" | ||||
| 	// flagNameUserLockoutDuration is the flag name used for tuning the auth mount lockout duration parameter | ||||
| 	flagNameUserLockoutDuration = "user-lockout-duration" | ||||
| 	// flagNameUserLockoutCounterResetDuration is the flag name used for tuning the auth mount lockout counter reset parameter | ||||
| 	flagNameUserLockoutCounterResetDuration = "user-lockout-counter-reset-duration" | ||||
| 	// flagNameUserLockoutDisable is the flag name used for tuning the auth mount disable lockout parameter | ||||
| 	flagNameUserLockoutDisable = "user-lockout-disable" | ||||
| 	// flagNameDisableRedirects is used to prevent the client from honoring a single redirect as a response to a request | ||||
| 	flagNameDisableRedirects = "disable-redirects" | ||||
| ) | ||||
|   | ||||
| @@ -36,6 +36,10 @@ func TestParseListeners(t *testing.T) { | ||||
| 	testParseListeners(t) | ||||
| } | ||||
|  | ||||
| func TestParseUserLockouts(t *testing.T) { | ||||
| 	testParseUserLockouts(t) | ||||
| } | ||||
|  | ||||
| func TestParseSockaddrTemplate(t *testing.T) { | ||||
| 	testParseSockaddrTemplate(t) | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package server | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| @@ -892,6 +893,67 @@ listener "tcp" { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testParseUserLockouts(t *testing.T) { | ||||
| 	obj, _ := hcl.Parse(strings.TrimSpace(` | ||||
| 	user_lockout "all" { | ||||
| 		lockout_duration = "40m" | ||||
| 		lockout_counter_reset = "45m" | ||||
| 		disable_lockout = "false" | ||||
| 	} | ||||
| 	  user_lockout "userpass" { | ||||
| 	     lockout_threshold = "100" | ||||
| 	     lockout_duration = "20m" | ||||
| 	  } | ||||
| 	  user_lockout "ldap" { | ||||
| 		disable_lockout = "true" | ||||
| 	 }`)) | ||||
|  | ||||
| 	config := Config{ | ||||
| 		SharedConfig: &configutil.SharedConfig{}, | ||||
| 	} | ||||
| 	list, _ := obj.Node.(*ast.ObjectList) | ||||
| 	objList := list.Filter("user_lockout") | ||||
| 	configutil.ParseUserLockouts(config.SharedConfig, objList) | ||||
|  | ||||
| 	sort.Slice(config.SharedConfig.UserLockouts[:], func(i, j int) bool { | ||||
| 		return config.SharedConfig.UserLockouts[i].Type < config.SharedConfig.UserLockouts[j].Type | ||||
| 	}) | ||||
|  | ||||
| 	expected := &Config{ | ||||
| 		SharedConfig: &configutil.SharedConfig{ | ||||
| 			UserLockouts: []*configutil.UserLockout{ | ||||
| 				{ | ||||
| 					Type:                "all", | ||||
| 					LockoutThreshold:    5, | ||||
| 					LockoutDuration:     2400000000000, | ||||
| 					LockoutCounterReset: 2700000000000, | ||||
| 					DisableLockout:      false, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Type:                "userpass", | ||||
| 					LockoutThreshold:    100, | ||||
| 					LockoutDuration:     1200000000000, | ||||
| 					LockoutCounterReset: 2700000000000, | ||||
| 					DisableLockout:      false, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Type:                "ldap", | ||||
| 					LockoutThreshold:    5, | ||||
| 					LockoutDuration:     2400000000000, | ||||
| 					LockoutCounterReset: 2700000000000, | ||||
| 					DisableLockout:      true, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	sort.Slice(expected.SharedConfig.UserLockouts[:], func(i, j int) bool { | ||||
| 		return expected.SharedConfig.UserLockouts[i].Type < expected.SharedConfig.UserLockouts[j].Type | ||||
| 	}) | ||||
| 	config.Prune() | ||||
| 	require.Equal(t, config, *expected) | ||||
| } | ||||
|  | ||||
| func testParseSockaddrTemplate(t *testing.T) { | ||||
| 	config, err := ParseConfig(` | ||||
| api_addr = <<EOF | ||||
|   | ||||
| @@ -21,6 +21,8 @@ type SharedConfig struct { | ||||
|  | ||||
| 	Listeners []*Listener `hcl:"-"` | ||||
|  | ||||
| 	UserLockouts []*UserLockout `hcl:"-"` | ||||
|  | ||||
| 	Seals   []*KMS   `hcl:"-"` | ||||
| 	Entropy *Entropy `hcl:"-"` | ||||
|  | ||||
| @@ -134,6 +136,13 @@ func ParseConfig(d string) (*SharedConfig, error) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if o := list.Filter("user_lockout"); len(o.Items) > 0 { | ||||
| 		result.found("user_lockout", "UserLockout") | ||||
| 		if err := ParseUserLockouts(&result, o); err != nil { | ||||
| 			return nil, fmt.Errorf("error parsing 'user_lockout': %w", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if o := list.Filter("telemetry"); len(o.Items) > 0 { | ||||
| 		result.found("telemetry", "Telemetry") | ||||
| 		if err := parseTelemetry(&result, o); err != nil { | ||||
| @@ -194,6 +203,22 @@ func (c *SharedConfig) Sanitized() map[string]interface{} { | ||||
| 		result["listeners"] = sanitizedListeners | ||||
| 	} | ||||
|  | ||||
| 	// Sanitize user lockout stanza | ||||
| 	if len(c.UserLockouts) != 0 { | ||||
| 		var sanitizedUserLockouts []interface{} | ||||
| 		for _, userlockout := range c.UserLockouts { | ||||
| 			cleanUserLockout := map[string]interface{}{ | ||||
| 				"type":                  userlockout.Type, | ||||
| 				"lockout_threshold":     userlockout.LockoutThreshold, | ||||
| 				"lockout_duration":      userlockout.LockoutDuration, | ||||
| 				"lockout_counter_reset": userlockout.LockoutCounterReset, | ||||
| 				"disable_lockout":       userlockout.DisableLockout, | ||||
| 			} | ||||
| 			sanitizedUserLockouts = append(sanitizedUserLockouts, cleanUserLockout) | ||||
| 		} | ||||
| 		result["user_lockout_configs"] = sanitizedUserLockouts | ||||
| 	} | ||||
|  | ||||
| 	// Sanitize seals stanza | ||||
| 	if len(c.Seals) != 0 { | ||||
| 		var sanitizedSeals []interface{} | ||||
|   | ||||
| @@ -14,6 +14,13 @@ func (c *SharedConfig) Merge(c2 *SharedConfig) *SharedConfig { | ||||
| 		result.Listeners = append(result.Listeners, l) | ||||
| 	} | ||||
|  | ||||
| 	for _, userlockout := range c.UserLockouts { | ||||
| 		result.UserLockouts = append(result.UserLockouts, userlockout) | ||||
| 	} | ||||
| 	for _, userlockout := range c2.UserLockouts { | ||||
| 		result.UserLockouts = append(result.UserLockouts, userlockout) | ||||
| 	} | ||||
|  | ||||
| 	result.HCPLinkConf = c.HCPLinkConf | ||||
| 	if c2.HCPLinkConf != nil { | ||||
| 		result.HCPLinkConf = c2.HCPLinkConf | ||||
|   | ||||
							
								
								
									
										186
									
								
								internalshared/configutil/userlockout.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								internalshared/configutil/userlockout.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | ||||
| package configutil | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/hashicorp/go-multierror" | ||||
| 	"github.com/hashicorp/go-secure-stdlib/parseutil" | ||||
| 	"github.com/hashicorp/hcl" | ||||
| 	"github.com/hashicorp/hcl/hcl/ast" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	UserLockoutThresholdDefault    = 5 | ||||
| 	UserLockoutDurationDefault     = 15 * time.Minute | ||||
| 	UserLockoutCounterResetDefault = 15 * time.Minute | ||||
| 	DisableUserLockoutDefault      = false | ||||
| ) | ||||
|  | ||||
| type UserLockout struct { | ||||
| 	Type                   string | ||||
| 	LockoutThreshold       uint64        `hcl:"-"` | ||||
| 	LockoutThresholdRaw    interface{}   `hcl:"lockout_threshold"` | ||||
| 	LockoutDuration        time.Duration `hcl:"-"` | ||||
| 	LockoutDurationRaw     interface{}   `hcl:"lockout_duration"` | ||||
| 	LockoutCounterReset    time.Duration `hcl:"-"` | ||||
| 	LockoutCounterResetRaw interface{}   `hcl:"lockout_counter_reset"` | ||||
| 	DisableLockout         bool          `hcl:"-"` | ||||
| 	DisableLockoutRaw      interface{}   `hcl:"disable_lockout"` | ||||
| } | ||||
|  | ||||
| func ParseUserLockouts(result *SharedConfig, list *ast.ObjectList) error { | ||||
| 	var err error | ||||
| 	result.UserLockouts = make([]*UserLockout, 0, len(list.Items)) | ||||
| 	userLockoutsMap := make(map[string]*UserLockout) | ||||
| 	for i, item := range list.Items { | ||||
| 		var userLockoutConfig UserLockout | ||||
| 		if err := hcl.DecodeObject(&userLockoutConfig, item.Val); err != nil { | ||||
| 			return multierror.Prefix(err, fmt.Sprintf("userLockouts.%d:", i)) | ||||
| 		} | ||||
|  | ||||
| 		// Base values | ||||
| 		{ | ||||
| 			switch { | ||||
| 			case userLockoutConfig.Type != "": | ||||
| 			case len(item.Keys) == 1: | ||||
| 				userLockoutConfig.Type = strings.ToLower(item.Keys[0].Token.Value().(string)) | ||||
| 			default: | ||||
| 				return multierror.Prefix(errors.New("auth type for user lockout must be specified, if it applies to all auth methods specify \"all\" "), fmt.Sprintf("user_lockouts.%d:", i)) | ||||
| 			} | ||||
|  | ||||
| 			userLockoutConfig.Type = strings.ToLower(userLockoutConfig.Type) | ||||
| 			// Supported auth methods for user lockout configuration: ldap, approle, userpass | ||||
| 			// "all" is used to apply the configuration to all supported auth methods | ||||
| 			switch userLockoutConfig.Type { | ||||
| 			case "all", "ldap", "approle", "userpass": | ||||
| 				result.found(userLockoutConfig.Type, userLockoutConfig.Type) | ||||
| 			default: | ||||
| 				return multierror.Prefix(fmt.Errorf("unsupported auth type %q", userLockoutConfig.Type), fmt.Sprintf("user_lockouts.%d:", i)) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Lockout Parameters | ||||
|  | ||||
| 		// Not setting raw entries to nil here as soon as they are parsed | ||||
| 		// as they are used to set the missing user lockout configuration values later. | ||||
| 		{ | ||||
| 			if userLockoutConfig.LockoutThresholdRaw != nil { | ||||
| 				userLockoutThresholdString := fmt.Sprintf("%v", userLockoutConfig.LockoutThresholdRaw) | ||||
| 				if userLockoutConfig.LockoutThreshold, err = strconv.ParseUint(userLockoutThresholdString, 10, 64); err != nil { | ||||
| 					return multierror.Prefix(fmt.Errorf("error parsing lockout_threshold: %w", err), fmt.Sprintf("user_lockouts.%d", i)) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if userLockoutConfig.LockoutDurationRaw != nil { | ||||
| 				if userLockoutConfig.LockoutDuration, err = parseutil.ParseDurationSecond(userLockoutConfig.LockoutDurationRaw); err != nil { | ||||
| 					return multierror.Prefix(fmt.Errorf("error parsing lockout_duration: %w", err), fmt.Sprintf("user_lockouts.%d", i)) | ||||
| 				} | ||||
| 				if userLockoutConfig.LockoutDuration < 0 { | ||||
| 					return multierror.Prefix(errors.New("lockout_duration cannot be negative"), fmt.Sprintf("user_lockouts.%d", i)) | ||||
| 				} | ||||
|  | ||||
| 			} | ||||
|  | ||||
| 			if userLockoutConfig.LockoutCounterResetRaw != nil { | ||||
| 				if userLockoutConfig.LockoutCounterReset, err = parseutil.ParseDurationSecond(userLockoutConfig.LockoutCounterResetRaw); err != nil { | ||||
| 					return multierror.Prefix(fmt.Errorf("error parsing lockout_counter_reset: %w", err), fmt.Sprintf("user_lockouts.%d", i)) | ||||
| 				} | ||||
| 				if userLockoutConfig.LockoutCounterReset < 0 { | ||||
| 					return multierror.Prefix(errors.New("lockout_counter_reset cannot be negative"), fmt.Sprintf("user_lockouts.%d", i)) | ||||
| 				} | ||||
|  | ||||
| 			} | ||||
| 			if userLockoutConfig.DisableLockoutRaw != nil { | ||||
| 				if userLockoutConfig.DisableLockout, err = parseutil.ParseBool(userLockoutConfig.DisableLockoutRaw); err != nil { | ||||
| 					return multierror.Prefix(fmt.Errorf("invalid value for disable_lockout: %w", err), fmt.Sprintf("user_lockouts.%d", i)) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		userLockoutsMap[userLockoutConfig.Type] = &userLockoutConfig | ||||
| 	} | ||||
|  | ||||
| 	// Use raw entries to set values for user lockout configurations fields | ||||
| 	// that were not configured using config file. | ||||
| 	// The raw entries would mean that the entry was configured by the user using the config file. | ||||
| 	// If any of these fields are not configured using the config file (missing fields), | ||||
| 	// we set values for these fields with defaults | ||||
| 	// The issue with not being able to use non-raw entries is because of fields lockout threshold | ||||
| 	// and disable lockout. We cannot differentiate using non-raw entries if the user configured these fields | ||||
| 	// with values (0 and false) or if the the user did not configure these values in config file at all. | ||||
| 	// The raw fields are set to nil after setting missing values in setNilValuesForRawUserLockoutFields function | ||||
| 	userLockoutsMap = setMissingUserLockoutValuesInMap(userLockoutsMap) | ||||
| 	for _, userLockoutValues := range userLockoutsMap { | ||||
| 		result.UserLockouts = append(result.UserLockouts, userLockoutValues) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // setUserLockoutValueAllInMap sets default user lockout values for key "all" (all auth methods) | ||||
| // for user lockout fields that are not configured using config file | ||||
| func setUserLockoutValueAllInMap(userLockoutAll *UserLockout) *UserLockout { | ||||
| 	if userLockoutAll.Type == "" { | ||||
| 		userLockoutAll.Type = "all" | ||||
| 	} | ||||
| 	if userLockoutAll.LockoutThresholdRaw == nil { | ||||
| 		userLockoutAll.LockoutThreshold = UserLockoutThresholdDefault | ||||
| 	} | ||||
| 	if userLockoutAll.LockoutDurationRaw == nil { | ||||
| 		userLockoutAll.LockoutDuration = UserLockoutDurationDefault | ||||
| 	} | ||||
| 	if userLockoutAll.LockoutCounterResetRaw == nil { | ||||
| 		userLockoutAll.LockoutCounterReset = UserLockoutCounterResetDefault | ||||
| 	} | ||||
| 	if userLockoutAll.DisableLockoutRaw == nil { | ||||
| 		userLockoutAll.DisableLockout = DisableUserLockoutDefault | ||||
| 	} | ||||
| 	return setNilValuesForRawUserLockoutFields(userLockoutAll) | ||||
| } | ||||
|  | ||||
| // setDefaultUserLockoutValuesInMap sets missing user lockout fields for auth methods | ||||
| // with default values (from key "all") that are not configured using config file | ||||
| func setMissingUserLockoutValuesInMap(userLockoutsMap map[string]*UserLockout) map[string]*UserLockout { | ||||
| 	// set values for "all" key with default values for "all" user lockout fields that are not configured | ||||
| 	// the "all" key values will be used as default values for other auth methods | ||||
| 	userLockoutAll, ok := userLockoutsMap["all"] | ||||
| 	switch ok { | ||||
| 	case true: | ||||
| 		userLockoutsMap["all"] = setUserLockoutValueAllInMap(userLockoutAll) | ||||
| 	default: | ||||
| 		userLockoutsMap["all"] = setUserLockoutValueAllInMap(&UserLockout{}) | ||||
| 	} | ||||
|  | ||||
| 	for _, userLockoutAuth := range userLockoutsMap { | ||||
| 		if userLockoutAuth.Type == "all" { | ||||
| 			continue | ||||
| 		} | ||||
| 		// set missing values | ||||
| 		if userLockoutAuth.LockoutThresholdRaw == nil { | ||||
| 			userLockoutAuth.LockoutThreshold = userLockoutsMap["all"].LockoutThreshold | ||||
| 		} | ||||
| 		if userLockoutAuth.LockoutDurationRaw == nil { | ||||
| 			userLockoutAuth.LockoutDuration = userLockoutsMap["all"].LockoutDuration | ||||
| 		} | ||||
| 		if userLockoutAuth.LockoutCounterResetRaw == nil { | ||||
| 			userLockoutAuth.LockoutCounterReset = userLockoutsMap["all"].LockoutCounterReset | ||||
| 		} | ||||
| 		if userLockoutAuth.DisableLockoutRaw == nil { | ||||
| 			userLockoutAuth.DisableLockout = userLockoutsMap["all"].DisableLockout | ||||
| 		} | ||||
| 		userLockoutAuth = setNilValuesForRawUserLockoutFields(userLockoutAuth) | ||||
| 		userLockoutsMap[userLockoutAuth.Type] = userLockoutAuth | ||||
| 	} | ||||
| 	return userLockoutsMap | ||||
| } | ||||
|  | ||||
| // setNilValuesForRawUserLockoutFields sets nil values for user lockout Raw fields | ||||
| func setNilValuesForRawUserLockoutFields(userLockout *UserLockout) *UserLockout { | ||||
| 	userLockout.LockoutThresholdRaw = nil | ||||
| 	userLockout.LockoutDurationRaw = nil | ||||
| 	userLockout.LockoutCounterResetRaw = nil | ||||
| 	userLockout.DisableLockoutRaw = nil | ||||
| 	return userLockout | ||||
| } | ||||
							
								
								
									
										69
									
								
								internalshared/configutil/userlockout_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								internalshared/configutil/userlockout_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| package configutil | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func TestParseUserLockout(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	t.Run("Missing user lockout block in config file", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		inputConfig := make(map[string]*UserLockout) | ||||
| 		expectedConfig := make(map[string]*UserLockout) | ||||
| 		expectedConfigall := &UserLockout{} | ||||
| 		expectedConfigall.Type = "all" | ||||
| 		expectedConfigall.LockoutThreshold = UserLockoutThresholdDefault | ||||
| 		expectedConfigall.LockoutDuration = UserLockoutDurationDefault | ||||
| 		expectedConfigall.LockoutCounterReset = UserLockoutCounterResetDefault | ||||
| 		expectedConfigall.DisableLockout = DisableUserLockoutDefault | ||||
| 		expectedConfig["all"] = expectedConfigall | ||||
|  | ||||
| 		outputConfig := setMissingUserLockoutValuesInMap(inputConfig) | ||||
| 		if !reflect.DeepEqual(expectedConfig["all"], outputConfig["all"]) { | ||||
| 			t.Errorf("user lockout config: expected %#v\nactual %#v", expectedConfig["all"], outputConfig["all"]) | ||||
| 		} | ||||
| 	}) | ||||
| 	t.Run("setting default lockout counter reset and lockout duration for userpass in config ", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		// input user lockout in config file | ||||
| 		inputConfig := make(map[string]*UserLockout) | ||||
| 		configAll := &UserLockout{} | ||||
| 		configAll.Type = "all" | ||||
| 		configAll.LockoutCounterReset = 20 * time.Minute | ||||
| 		configAll.LockoutCounterResetRaw = "1200000000000" | ||||
| 		inputConfig["all"] = configAll | ||||
| 		configUserpass := &UserLockout{} | ||||
| 		configUserpass.Type = "userpass" | ||||
| 		configUserpass.LockoutDuration = 10 * time.Minute | ||||
| 		configUserpass.LockoutDurationRaw = "600000000000" | ||||
| 		inputConfig["userpass"] = configUserpass | ||||
|  | ||||
| 		expectedConfig := make(map[string]*UserLockout) | ||||
| 		expectedConfigall := &UserLockout{} | ||||
| 		expectedConfigUserpass := &UserLockout{} | ||||
| 		// expected default values | ||||
| 		expectedConfigall.Type = "all" | ||||
| 		expectedConfigall.LockoutThreshold = UserLockoutThresholdDefault | ||||
| 		expectedConfigall.LockoutDuration = UserLockoutDurationDefault | ||||
| 		expectedConfigall.LockoutCounterReset = 20 * time.Minute | ||||
| 		expectedConfigall.DisableLockout = DisableUserLockoutDefault | ||||
| 		// expected values for userpass | ||||
| 		expectedConfigUserpass.Type = "userpass" | ||||
| 		expectedConfigUserpass.LockoutThreshold = UserLockoutThresholdDefault | ||||
| 		expectedConfigUserpass.LockoutDuration = 10 * time.Minute | ||||
| 		expectedConfigUserpass.LockoutCounterReset = 20 * time.Minute | ||||
| 		expectedConfigUserpass.DisableLockout = DisableUserLockoutDefault | ||||
| 		expectedConfig["all"] = expectedConfigall | ||||
| 		expectedConfig["userpass"] = expectedConfigUserpass | ||||
|  | ||||
| 		outputConfig := setMissingUserLockoutValuesInMap(inputConfig) | ||||
| 		if !reflect.DeepEqual(expectedConfig["all"], outputConfig["all"]) { | ||||
| 			t.Errorf("user lockout config: expected %#v\nactual %#v", expectedConfig["all"], outputConfig["all"]) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(expectedConfig["userpass"], outputConfig["userpass"]) { | ||||
| 			t.Errorf("user lockout config: expected %#v\nactual %#v", expectedConfig["userpass"], outputConfig["userpass"]) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
| @@ -34,4 +34,6 @@ const ( | ||||
| 	ReplicationResolverALPN = "replication_resolver_v1" | ||||
|  | ||||
| 	VaultEnableFilePermissionsCheckEnv = "VAULT_ENABLE_FILE_PERMISSIONS_CHECK" | ||||
|  | ||||
| 	VaultDisableUserLockout = "VAULT_DISABLE_USER_LOCKOUT" | ||||
| ) | ||||
|   | ||||
| @@ -944,6 +944,15 @@ func (b *SystemBackend) mountInfo(ctx context.Context, entry *MountEntry) map[st | ||||
| 	if entry.Table == credentialTableType { | ||||
| 		entryConfig["token_type"] = entry.Config.TokenType.String() | ||||
| 	} | ||||
| 	if entry.Config.UserLockoutConfig != nil { | ||||
| 		userLockoutConfig := map[string]interface{}{ | ||||
| 			"user_lockout_counter_reset_duration": int64(entry.Config.UserLockoutConfig.LockoutCounterReset.Seconds()), | ||||
| 			"user_lockout_threshold":              entry.Config.UserLockoutConfig.LockoutThreshold, | ||||
| 			"user_lockout_duration":               int64(entry.Config.UserLockoutConfig.LockoutDuration.Seconds()), | ||||
| 			"user_lockout_disable":                entry.Config.UserLockoutConfig.DisableLockout, | ||||
| 		} | ||||
| 		entryConfig["user_lockout_config"] = userLockoutConfig | ||||
| 	} | ||||
|  | ||||
| 	// Add deprecation status only if it exists | ||||
| 	builtinType := b.Core.builtinTypeFromMountEntry(ctx, entry) | ||||
| @@ -1604,6 +1613,13 @@ func (b *SystemBackend) handleTuneReadCommon(ctx context.Context, path string) ( | ||||
| 		resp.Data["allowed_managed_keys"] = rawVal.([]string) | ||||
| 	} | ||||
|  | ||||
| 	if mountEntry.Config.UserLockoutConfig != nil { | ||||
| 		resp.Data["user_lockout_counter_reset_duration"] = int64(mountEntry.Config.UserLockoutConfig.LockoutCounterReset.Seconds()) | ||||
| 		resp.Data["user_lockout_threshold"] = mountEntry.Config.UserLockoutConfig.LockoutThreshold | ||||
| 		resp.Data["user_lockout_duration"] = int64(mountEntry.Config.UserLockoutConfig.LockoutDuration.Seconds()) | ||||
| 		resp.Data["user_lockout_disable"] = mountEntry.Config.UserLockoutConfig.DisableLockout | ||||
| 	} | ||||
|  | ||||
| 	if len(mountEntry.Options) > 0 { | ||||
| 		resp.Data["options"] = mountEntry.Options | ||||
| 	} | ||||
| @@ -1723,6 +1739,111 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// user-lockout config | ||||
| 	{ | ||||
| 		var apiuserLockoutConfig APIUserLockoutConfig | ||||
|  | ||||
| 		userLockoutConfigMap := data.Get("user_lockout_config").(map[string]interface{}) | ||||
| 		var err error | ||||
| 		if userLockoutConfigMap != nil && len(userLockoutConfigMap) != 0 { | ||||
| 			err := mapstructure.Decode(userLockoutConfigMap, &apiuserLockoutConfig) | ||||
| 			if err != nil { | ||||
| 				return logical.ErrorResponse( | ||||
| 						"unable to convert given user lockout config information"), | ||||
| 					logical.ErrInvalidRequest | ||||
| 			} | ||||
|  | ||||
| 			// Supported auth methods for user lockout configuration: ldap, approle, userpass | ||||
| 			switch strings.ToLower(mountEntry.Type) { | ||||
| 			case "ldap", "approle", "userpass": | ||||
| 			default: | ||||
| 				return logical.ErrorResponse("tuning of user lockout configuration for auth type %q not allowed", mountEntry.Type), | ||||
| 					logical.ErrInvalidRequest | ||||
|  | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(userLockoutConfigMap) > 0 && mountEntry.Config.UserLockoutConfig == nil { | ||||
| 			mountEntry.Config.UserLockoutConfig = &UserLockoutConfig{} | ||||
| 		} | ||||
|  | ||||
| 		var oldUserLockoutThreshold uint64 | ||||
| 		var newUserLockoutDuration, oldUserLockoutDuration time.Duration | ||||
| 		var newUserLockoutCounterReset, oldUserLockoutCounterReset time.Duration | ||||
| 		var oldUserLockoutDisable bool | ||||
|  | ||||
| 		if apiuserLockoutConfig.LockoutThreshold != "" { | ||||
| 			userLockoutThreshold, err := strconv.ParseUint(apiuserLockoutConfig.LockoutThreshold, 10, 64) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("unable to parse user lockout threshold: %w", err) | ||||
| 			} | ||||
| 			oldUserLockoutThreshold = mountEntry.Config.UserLockoutConfig.LockoutThreshold | ||||
| 			mountEntry.Config.UserLockoutConfig.LockoutThreshold = userLockoutThreshold | ||||
| 		} | ||||
|  | ||||
| 		if apiuserLockoutConfig.LockoutDuration != "" { | ||||
| 			oldUserLockoutDuration = mountEntry.Config.UserLockoutConfig.LockoutDuration | ||||
| 			switch apiuserLockoutConfig.LockoutDuration { | ||||
| 			case "": | ||||
| 				newUserLockoutDuration = oldUserLockoutDuration | ||||
| 			case "system": | ||||
| 				newUserLockoutDuration = time.Duration(0) | ||||
| 			default: | ||||
| 				tmpUserLockoutDuration, err := parseutil.ParseDurationSecond(apiuserLockoutConfig.LockoutDuration) | ||||
| 				if err != nil { | ||||
| 					return handleError(err) | ||||
| 				} | ||||
| 				newUserLockoutDuration = tmpUserLockoutDuration | ||||
|  | ||||
| 			} | ||||
| 			mountEntry.Config.UserLockoutConfig.LockoutDuration = newUserLockoutDuration | ||||
| 		} | ||||
|  | ||||
| 		if apiuserLockoutConfig.LockoutCounterResetDuration != "" { | ||||
| 			oldUserLockoutCounterReset = mountEntry.Config.UserLockoutConfig.LockoutCounterReset | ||||
| 			switch apiuserLockoutConfig.LockoutCounterResetDuration { | ||||
| 			case "": | ||||
| 				newUserLockoutCounterReset = oldUserLockoutCounterReset | ||||
| 			case "system": | ||||
| 				newUserLockoutCounterReset = time.Duration(0) | ||||
| 			default: | ||||
| 				tmpUserLockoutCounterReset, err := parseutil.ParseDurationSecond(apiuserLockoutConfig.LockoutCounterResetDuration) | ||||
| 				if err != nil { | ||||
| 					return handleError(err) | ||||
| 				} | ||||
| 				newUserLockoutCounterReset = tmpUserLockoutCounterReset | ||||
| 			} | ||||
|  | ||||
| 			mountEntry.Config.UserLockoutConfig.LockoutCounterReset = newUserLockoutCounterReset | ||||
| 		} | ||||
|  | ||||
| 		if apiuserLockoutConfig.DisableLockout != nil { | ||||
| 			oldUserLockoutDisable = mountEntry.Config.UserLockoutConfig.DisableLockout | ||||
| 			userLockoutDisable := apiuserLockoutConfig.DisableLockout | ||||
| 			mountEntry.Config.UserLockoutConfig.DisableLockout = *userLockoutDisable | ||||
| 		} | ||||
|  | ||||
| 		// Update the mount table | ||||
| 		if len(userLockoutConfigMap) > 0 { | ||||
| 			switch { | ||||
| 			case strings.HasPrefix(path, "auth/"): | ||||
| 				err = b.Core.persistAuth(ctx, b.Core.auth, &mountEntry.Local) | ||||
| 			default: | ||||
| 				err = b.Core.persistMounts(ctx, b.Core.mounts, &mountEntry.Local) | ||||
| 			} | ||||
| 			if err != nil { | ||||
| 				mountEntry.Config.UserLockoutConfig.LockoutCounterReset = oldUserLockoutCounterReset | ||||
| 				mountEntry.Config.UserLockoutConfig.LockoutThreshold = oldUserLockoutThreshold | ||||
| 				mountEntry.Config.UserLockoutConfig.LockoutDuration = oldUserLockoutDuration | ||||
| 				mountEntry.Config.UserLockoutConfig.DisableLockout = oldUserLockoutDisable | ||||
| 				return handleError(err) | ||||
| 			} | ||||
| 			if b.Core.logger.IsInfo() { | ||||
| 				b.Core.logger.Info("tuning of user_lockout_config successful", "path", path) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| 	if rawVal, ok := data.GetOk("description"); ok { | ||||
| 		description := rawVal.(string) | ||||
|  | ||||
| @@ -5021,6 +5142,10 @@ in the plugin catalog.`, | ||||
| 		`The options to pass into the backend. Should be a json object with string keys and values.`, | ||||
| 	}, | ||||
|  | ||||
| 	"tune_user_lockout_config": { | ||||
| 		`The user lockout configuration to pass into the backend. Should be a json object with string keys and values.`, | ||||
| 	}, | ||||
|  | ||||
| 	"remount": { | ||||
| 		"Move the mount point of an already-mounted backend, within or across namespaces", | ||||
| 		` | ||||
|   | ||||
| @@ -1542,6 +1542,10 @@ func (b *SystemBackend) authPaths() []*framework.Path { | ||||
| 					Type:        framework.TypeString, | ||||
| 					Description: strings.TrimSpace(sysHelp["token_type"][0]), | ||||
| 				}, | ||||
| 				"user_lockout_config": { | ||||
| 					Type:        framework.TypeMap, | ||||
| 					Description: strings.TrimSpace(sysHelp["tune_user_lockout_config"][0]), | ||||
| 				}, | ||||
| 				"plugin_version": { | ||||
| 					Type:        framework.TypeString, | ||||
| 					Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]), | ||||
| @@ -1929,6 +1933,10 @@ func (b *SystemBackend) mountPaths() []*framework.Path { | ||||
| 					Type:        framework.TypeString, | ||||
| 					Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]), | ||||
| 				}, | ||||
| 				"user_lockout_config": { | ||||
| 					Type:        framework.TypeMap, | ||||
| 					Description: strings.TrimSpace(sysHelp["tune_user_lockout_config"][0]), | ||||
| 				}, | ||||
| 			}, | ||||
|  | ||||
| 			Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||
|   | ||||
| @@ -351,6 +351,7 @@ type MountConfig struct { | ||||
| 	AllowedResponseHeaders    []string              `json:"allowed_response_headers,omitempty" structs:"allowed_response_headers" mapstructure:"allowed_response_headers"` | ||||
| 	TokenType                 logical.TokenType     `json:"token_type,omitempty" structs:"token_type" mapstructure:"token_type"` | ||||
| 	AllowedManagedKeys        []string              `json:"allowed_managed_keys,omitempty" mapstructure:"allowed_managed_keys"` | ||||
| 	UserLockoutConfig         *UserLockoutConfig    `json:"user_lockout_config,omitempty" mapstructure:"user_lockout_config"` | ||||
|  | ||||
| 	// PluginName is the name of the plugin registered in the catalog. | ||||
| 	// | ||||
| @@ -358,6 +359,20 @@ type MountConfig struct { | ||||
| 	PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"` | ||||
| } | ||||
|  | ||||
| type UserLockoutConfig struct { | ||||
| 	LockoutThreshold    uint64        `json:"lockout_threshold,omitempty" structs:"lockout_threshold" mapstructure:"lockout_threshold"` | ||||
| 	LockoutDuration     time.Duration `json:"lockout_duration,omitempty" structs:"lockout_duration" mapstructure:"lockout_duration"` | ||||
| 	LockoutCounterReset time.Duration `json:"lockout_counter_reset,omitempty" structs:"lockout_counter_reset" mapstructure:"lockout_counter_reset"` | ||||
| 	DisableLockout      bool          `json:"disable_lockout,omitempty" structs:"disable_lockout" mapstructure:"disable_lockout"` | ||||
| } | ||||
|  | ||||
| type APIUserLockoutConfig struct { | ||||
| 	LockoutThreshold            string `json:"lockout_threshold,omitempty" structs:"lockout_threshold" mapstructure:"lockout_threshold"` | ||||
| 	LockoutDuration             string `json:"lockout_duration,omitempty" structs:"lockout_duration" mapstructure:"lockout_duration"` | ||||
| 	LockoutCounterResetDuration string `json:"lockout_counter_reset_duration,omitempty" structs:"lockout_counter_reset_duration" mapstructure:"lockout_counter_reset_duration"` | ||||
| 	DisableLockout              *bool  `json:"lockout_disable,omitempty" structs:"lockout_disable" mapstructure:"lockout_disable"` | ||||
| } | ||||
|  | ||||
| // APIMountConfig is an embedded struct of api.MountConfigInput | ||||
| type APIMountConfig struct { | ||||
| 	DefaultLeaseTTL           string                `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` | ||||
| @@ -370,6 +385,7 @@ type APIMountConfig struct { | ||||
| 	AllowedResponseHeaders    []string              `json:"allowed_response_headers,omitempty" structs:"allowed_response_headers" mapstructure:"allowed_response_headers"` | ||||
| 	TokenType                 string                `json:"token_type" structs:"token_type" mapstructure:"token_type"` | ||||
| 	AllowedManagedKeys        []string              `json:"allowed_managed_keys,omitempty" mapstructure:"allowed_managed_keys"` | ||||
| 	UserLockoutConfig         *UserLockoutConfig    `json:"user_lockout_config,omitempty" mapstructure:"user_lockout_config"` | ||||
| 	PluginVersion             string                `json:"plugin_version,omitempty" mapstructure:"plugin_version"` | ||||
|  | ||||
| 	// PluginName is the name of the plugin registered in the catalog. | ||||
| @@ -1275,8 +1291,8 @@ func (c *Core) runMountUpdates(ctx context.Context, needPersist bool) error { | ||||
| 			entry.NamespaceID = namespace.RootNamespaceID | ||||
| 			needPersist = true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	} | ||||
| 	// Done if we have restored the mount table and we don't need | ||||
| 	// to persist | ||||
| 	if !needPersist { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 akshya96
					akshya96