| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| 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 GetSupportedUserLockoutsAuthMethods() []string { |
| return []string{"userpass", "approle", "ldap"} |
| } |
| |
| 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 |
| } |