authelia/internal/configuration/validator/authentication.go

500 lines
21 KiB
Go
Raw Permalink Normal View History

package validator
import (
2019-12-06 08:15:54 +00:00
"fmt"
"net/url"
"strings"
"github.com/go-crypt/crypt/algorithm/argon2"
"github.com/go-crypt/crypt/algorithm/bcrypt"
"github.com/go-crypt/crypt/algorithm/pbkdf2"
"github.com/go-crypt/crypt/algorithm/scrypt"
"github.com/go-crypt/crypt/algorithm/shacrypt"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// ValidateAuthenticationBackend validates and updates the authentication backend configuration.
func ValidateAuthenticationBackend(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP == nil && config.File == nil {
validator.Push(fmt.Errorf(errFmtAuthBackendNotConfigured))
}
if config.RefreshInterval == "" {
config.RefreshInterval = schema.RefreshIntervalDefault
} else {
_, err := utils.ParseDurationString(config.RefreshInterval)
if err != nil && config.RefreshInterval != schema.ProfileRefreshDisabled && config.RefreshInterval != schema.ProfileRefreshAlways {
validator.Push(fmt.Errorf(errFmtAuthBackendRefreshInterval, config.RefreshInterval, err))
}
}
if config.PasswordReset.CustomURL.String() != "" {
switch config.PasswordReset.CustomURL.Scheme {
case schemeHTTP, schemeHTTPS:
config.PasswordReset.Disable = false
default:
validator.Push(fmt.Errorf(errFmtAuthBackendPasswordResetCustomURLScheme, config.PasswordReset.CustomURL.String(), config.PasswordReset.CustomURL.Scheme))
}
}
if config.LDAP != nil && config.File != nil {
validator.Push(fmt.Errorf(errFmtAuthBackendMultipleConfigured))
}
if config.File != nil {
validateFileAuthenticationBackend(config.File, validator)
}
if config.LDAP != nil {
validateLDAPAuthenticationBackend(config, validator)
}
}
// validateFileAuthenticationBackend validates and updates the file authentication backend configuration.
func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackend, validator *schema.StructValidator) {
if config.Path == "" {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPathNotConfigured))
}
[FEATURE] Support Argon2id password hasing and improved entropy (#679) * [FEATURE] Support Argon2id Passwords - Updated go module github.com/simia-tech/crypt - Added Argon2id support for file based authentication backend - Made it the default method - Made it so backwards compatibility with SHA512 exists - Force seeding of the random string generator used for salts to ensure they are all different - Added command params to the authelia hash-password command - Automatically remove {CRYPT} from hashes as they are updated - Automatically change hashes when they are updated to the configured algorithm - Made the hashing algorithm parameters completely configurable - Added reasonably comprehensive test suites - Updated docs - Updated config template * Adjust error output * Fix unit test * Add unit tests and argon2 version check * Fix new unit tests * Update docs, added tests * Implement configurable values and more comprehensive testing * Added cmd params to hash_password, updated docs, misc fixes * More detailed error for cmd, fixed a typo * Fixed cmd flag error, minor refactoring * Requested Changes and Minor refactoring * Increase entropy * Update docs for entropy changes * Refactor to reduce nesting and easier code maintenance * Cleanup Errors (uniformity for the function call) * Check salt length, fix docs * Add Base64 string validation for argon2id * Cleanup and Finalization - Moved RandomString function from ./internal/authentication/password_hash.go to ./internal/utils/strings.go - Added SplitStringToArrayOfStrings func that splits strings into an array with a fixed max string len - Fixed an error in validator that would allow a zero salt length - Added a test to verify the upstream crypt module supports our defined random salt chars - Updated docs - Removed unused "HashingAlgorithm" string type * Update crypt go mod, support argon2id key length and major refactor * Config Template Update, Final Tests * Use schema defaults for hash-password cmd * Iterations check * Docs requested changes * Test Coverage, suggested edits * Wording edit * Doc changes * Default sanity changes * Default sanity changes - docs * CI Sanity changes * Memory in MB
2020-03-06 01:38:02 +00:00
ValidatePasswordConfiguration(&config.Password, validator)
}
[FEATURE] Support Argon2id password hasing and improved entropy (#679) * [FEATURE] Support Argon2id Passwords - Updated go module github.com/simia-tech/crypt - Added Argon2id support for file based authentication backend - Made it the default method - Made it so backwards compatibility with SHA512 exists - Force seeding of the random string generator used for salts to ensure they are all different - Added command params to the authelia hash-password command - Automatically remove {CRYPT} from hashes as they are updated - Automatically change hashes when they are updated to the configured algorithm - Made the hashing algorithm parameters completely configurable - Added reasonably comprehensive test suites - Updated docs - Updated config template * Adjust error output * Fix unit test * Add unit tests and argon2 version check * Fix new unit tests * Update docs, added tests * Implement configurable values and more comprehensive testing * Added cmd params to hash_password, updated docs, misc fixes * More detailed error for cmd, fixed a typo * Fixed cmd flag error, minor refactoring * Requested Changes and Minor refactoring * Increase entropy * Update docs for entropy changes * Refactor to reduce nesting and easier code maintenance * Cleanup Errors (uniformity for the function call) * Check salt length, fix docs * Add Base64 string validation for argon2id * Cleanup and Finalization - Moved RandomString function from ./internal/authentication/password_hash.go to ./internal/utils/strings.go - Added SplitStringToArrayOfStrings func that splits strings into an array with a fixed max string len - Fixed an error in validator that would allow a zero salt length - Added a test to verify the upstream crypt module supports our defined random salt chars - Updated docs - Removed unused "HashingAlgorithm" string type * Update crypt go mod, support argon2id key length and major refactor * Config Template Update, Final Tests * Use schema defaults for hash-password cmd * Iterations check * Docs requested changes * Test Coverage, suggested edits * Wording edit * Doc changes * Default sanity changes * Default sanity changes - docs * CI Sanity changes * Memory in MB
2020-03-06 01:38:02 +00:00
// ValidatePasswordConfiguration validates the file auth backend password configuration.
func ValidatePasswordConfiguration(config *schema.Password, validator *schema.StructValidator) {
validateFileAuthenticationBackendPasswordConfigLegacy(config)
switch {
case config.Algorithm == "":
config.Algorithm = schema.DefaultPasswordConfig.Algorithm
case utils.IsStringInSlice(config.Algorithm, validHashAlgorithms):
break
default:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, strJoinOr(validHashAlgorithms), config.Algorithm))
}
[FEATURE] Support Argon2id password hasing and improved entropy (#679) * [FEATURE] Support Argon2id Passwords - Updated go module github.com/simia-tech/crypt - Added Argon2id support for file based authentication backend - Made it the default method - Made it so backwards compatibility with SHA512 exists - Force seeding of the random string generator used for salts to ensure they are all different - Added command params to the authelia hash-password command - Automatically remove {CRYPT} from hashes as they are updated - Automatically change hashes when they are updated to the configured algorithm - Made the hashing algorithm parameters completely configurable - Added reasonably comprehensive test suites - Updated docs - Updated config template * Adjust error output * Fix unit test * Add unit tests and argon2 version check * Fix new unit tests * Update docs, added tests * Implement configurable values and more comprehensive testing * Added cmd params to hash_password, updated docs, misc fixes * More detailed error for cmd, fixed a typo * Fixed cmd flag error, minor refactoring * Requested Changes and Minor refactoring * Increase entropy * Update docs for entropy changes * Refactor to reduce nesting and easier code maintenance * Cleanup Errors (uniformity for the function call) * Check salt length, fix docs * Add Base64 string validation for argon2id * Cleanup and Finalization - Moved RandomString function from ./internal/authentication/password_hash.go to ./internal/utils/strings.go - Added SplitStringToArrayOfStrings func that splits strings into an array with a fixed max string len - Fixed an error in validator that would allow a zero salt length - Added a test to verify the upstream crypt module supports our defined random salt chars - Updated docs - Removed unused "HashingAlgorithm" string type * Update crypt go mod, support argon2id key length and major refactor * Config Template Update, Final Tests * Use schema defaults for hash-password cmd * Iterations check * Docs requested changes * Test Coverage, suggested edits * Wording edit * Doc changes * Default sanity changes * Default sanity changes - docs * CI Sanity changes * Memory in MB
2020-03-06 01:38:02 +00:00
validateFileAuthenticationBackendPasswordConfigArgon2(config, validator)
validateFileAuthenticationBackendPasswordConfigSHA2Crypt(config, validator)
validateFileAuthenticationBackendPasswordConfigPBKDF2(config, validator)
validateFileAuthenticationBackendPasswordConfigBCrypt(config, validator)
validateFileAuthenticationBackendPasswordConfigSCrypt(config, validator)
}
//nolint:gocyclo // Function is well formed.
func validateFileAuthenticationBackendPasswordConfigArgon2(config *schema.Password, validator *schema.StructValidator) {
switch {
case config.Argon2.Variant == "":
config.Argon2.Variant = schema.DefaultPasswordConfig.Argon2.Variant
case utils.IsStringInSlice(config.Argon2.Variant, validArgon2Variants):
break
default:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashArgon2, strJoinOr(validArgon2Variants), config.Argon2.Variant))
}
switch {
case config.Argon2.Iterations == 0:
config.Argon2.Iterations = schema.DefaultPasswordConfig.Argon2.Iterations
case config.Argon2.Iterations < argon2.IterationsMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "iterations", config.Argon2.Iterations, argon2.IterationsMin))
case config.Argon2.Iterations > argon2.IterationsMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "iterations", config.Argon2.Iterations, argon2.IterationsMax))
}
switch {
case config.Argon2.Parallelism == 0:
config.Argon2.Parallelism = schema.DefaultPasswordConfig.Argon2.Parallelism
case config.Argon2.Parallelism < argon2.ParallelismMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "parallelism", config.Argon2.Parallelism, argon2.ParallelismMin))
case config.Argon2.Parallelism > argon2.ParallelismMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "parallelism", config.Argon2.Parallelism, argon2.ParallelismMax))
}
switch {
case config.Argon2.Memory == 0:
config.Argon2.Memory = schema.DefaultPasswordConfig.Argon2.Memory
case config.Argon2.Memory < argon2.MemoryMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "memory", config.Argon2.Memory, argon2.MemoryMin))
case uint64(config.Argon2.Memory) > uint64(argon2.MemoryMax):
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "memory", config.Argon2.Memory, argon2.MemoryMax))
case config.Argon2.Memory < (config.Argon2.Parallelism * argon2.MemoryMinParallelismMultiplier):
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2MemoryTooLow, config.Argon2.Memory, config.Argon2.Parallelism*argon2.MemoryMinParallelismMultiplier, config.Argon2.Parallelism, argon2.MemoryMinParallelismMultiplier))
}
switch {
case config.Argon2.KeyLength == 0:
config.Argon2.KeyLength = schema.DefaultPasswordConfig.Argon2.KeyLength
case config.Argon2.KeyLength < argon2.KeyLengthMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "key_length", config.Argon2.KeyLength, argon2.KeyLengthMin))
case config.Argon2.KeyLength > argon2.KeyLengthMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "key_length", config.Argon2.KeyLength, argon2.KeyLengthMax))
}
switch {
case config.Argon2.SaltLength == 0:
config.Argon2.SaltLength = schema.DefaultPasswordConfig.Argon2.SaltLength
case config.Argon2.SaltLength < argon2.SaltLengthMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashArgon2, "salt_length", config.Argon2.SaltLength, argon2.SaltLengthMin))
case config.Argon2.SaltLength > argon2.SaltLengthMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashArgon2, "salt_length", config.Argon2.SaltLength, argon2.SaltLengthMax))
[FEATURE] Support Argon2id password hasing and improved entropy (#679) * [FEATURE] Support Argon2id Passwords - Updated go module github.com/simia-tech/crypt - Added Argon2id support for file based authentication backend - Made it the default method - Made it so backwards compatibility with SHA512 exists - Force seeding of the random string generator used for salts to ensure they are all different - Added command params to the authelia hash-password command - Automatically remove {CRYPT} from hashes as they are updated - Automatically change hashes when they are updated to the configured algorithm - Made the hashing algorithm parameters completely configurable - Added reasonably comprehensive test suites - Updated docs - Updated config template * Adjust error output * Fix unit test * Add unit tests and argon2 version check * Fix new unit tests * Update docs, added tests * Implement configurable values and more comprehensive testing * Added cmd params to hash_password, updated docs, misc fixes * More detailed error for cmd, fixed a typo * Fixed cmd flag error, minor refactoring * Requested Changes and Minor refactoring * Increase entropy * Update docs for entropy changes * Refactor to reduce nesting and easier code maintenance * Cleanup Errors (uniformity for the function call) * Check salt length, fix docs * Add Base64 string validation for argon2id * Cleanup and Finalization - Moved RandomString function from ./internal/authentication/password_hash.go to ./internal/utils/strings.go - Added SplitStringToArrayOfStrings func that splits strings into an array with a fixed max string len - Fixed an error in validator that would allow a zero salt length - Added a test to verify the upstream crypt module supports our defined random salt chars - Updated docs - Removed unused "HashingAlgorithm" string type * Update crypt go mod, support argon2id key length and major refactor * Config Template Update, Final Tests * Use schema defaults for hash-password cmd * Iterations check * Docs requested changes * Test Coverage, suggested edits * Wording edit * Doc changes * Default sanity changes * Default sanity changes - docs * CI Sanity changes * Memory in MB
2020-03-06 01:38:02 +00:00
}
}
func validateFileAuthenticationBackendPasswordConfigSHA2Crypt(config *schema.Password, validator *schema.StructValidator) {
switch {
case config.SHA2Crypt.Variant == "":
config.SHA2Crypt.Variant = schema.DefaultPasswordConfig.SHA2Crypt.Variant
case utils.IsStringInSlice(config.SHA2Crypt.Variant, validSHA2CryptVariants):
break
default:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashSHA2Crypt, strJoinOr(validSHA2CryptVariants), config.SHA2Crypt.Variant))
}
switch {
case config.SHA2Crypt.Iterations == 0:
config.SHA2Crypt.Iterations = schema.DefaultPasswordConfig.SHA2Crypt.Iterations
case config.SHA2Crypt.Iterations < shacrypt.IterationsMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSHA2Crypt, "iterations", config.SHA2Crypt.Iterations, shacrypt.IterationsMin))
case config.SHA2Crypt.Iterations > shacrypt.IterationsMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSHA2Crypt, "iterations", config.SHA2Crypt.Iterations, shacrypt.IterationsMax))
}
switch {
case config.SHA2Crypt.SaltLength == 0:
config.SHA2Crypt.SaltLength = schema.DefaultPasswordConfig.SHA2Crypt.SaltLength
case config.SHA2Crypt.SaltLength < shacrypt.SaltLengthMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSHA2Crypt, "salt_length", config.SHA2Crypt.SaltLength, shacrypt.SaltLengthMin))
case config.SHA2Crypt.SaltLength > shacrypt.SaltLengthMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSHA2Crypt, "salt_length", config.SHA2Crypt.SaltLength, shacrypt.SaltLengthMax))
}
}
func validateFileAuthenticationBackendPasswordConfigPBKDF2(config *schema.Password, validator *schema.StructValidator) {
switch {
case config.PBKDF2.Variant == "":
config.PBKDF2.Variant = schema.DefaultPasswordConfig.PBKDF2.Variant
case utils.IsStringInSlice(config.PBKDF2.Variant, validPBKDF2Variants):
break
default:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashPBKDF2, strJoinOr(validPBKDF2Variants), config.PBKDF2.Variant))
}
switch {
case config.PBKDF2.Iterations == 0:
config.PBKDF2.Iterations = schema.DefaultPasswordConfig.PBKDF2.Iterations
case config.PBKDF2.Iterations < pbkdf2.IterationsMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashPBKDF2, "iterations", config.PBKDF2.Iterations, pbkdf2.IterationsMin))
case config.PBKDF2.Iterations > pbkdf2.IterationsMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashPBKDF2, "iterations", config.PBKDF2.Iterations, pbkdf2.IterationsMax))
}
switch {
case config.PBKDF2.SaltLength == 0:
config.PBKDF2.SaltLength = schema.DefaultPasswordConfig.PBKDF2.SaltLength
case config.PBKDF2.SaltLength < pbkdf2.SaltLengthMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashPBKDF2, "salt_length", config.PBKDF2.SaltLength, pbkdf2.SaltLengthMin))
case config.PBKDF2.SaltLength > pbkdf2.SaltLengthMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashPBKDF2, "salt_length", config.PBKDF2.SaltLength, pbkdf2.SaltLengthMax))
}
}
func validateFileAuthenticationBackendPasswordConfigBCrypt(config *schema.Password, validator *schema.StructValidator) {
switch {
case config.BCrypt.Variant == "":
config.BCrypt.Variant = schema.DefaultPasswordConfig.BCrypt.Variant
case utils.IsStringInSlice(config.BCrypt.Variant, validBCryptVariants):
break
default:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashBCrypt, strJoinOr(validBCryptVariants), config.BCrypt.Variant))
}
switch {
case config.BCrypt.Cost == 0:
config.BCrypt.Cost = schema.DefaultPasswordConfig.BCrypt.Cost
case config.BCrypt.Cost < bcrypt.IterationsMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashBCrypt, "cost", config.BCrypt.Cost, bcrypt.IterationsMin))
case config.BCrypt.Cost > bcrypt.IterationsMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashBCrypt, "cost", config.BCrypt.Cost, bcrypt.IterationsMax))
}
}
//nolint:gocyclo
func validateFileAuthenticationBackendPasswordConfigSCrypt(config *schema.Password, validator *schema.StructValidator) {
switch {
case config.SCrypt.Iterations == 0:
config.SCrypt.Iterations = schema.DefaultPasswordConfig.SCrypt.Iterations
case config.SCrypt.Iterations < scrypt.IterationsMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "iterations", config.SCrypt.Iterations, scrypt.IterationsMin))
case config.SCrypt.Iterations > scrypt.IterationsMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSCrypt, "iterations", config.SCrypt.Iterations, scrypt.IterationsMax))
}
switch {
case config.SCrypt.BlockSize == 0:
config.SCrypt.BlockSize = schema.DefaultPasswordConfig.SCrypt.BlockSize
case config.SCrypt.BlockSize < scrypt.BlockSizeMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "block_size", config.SCrypt.BlockSize, scrypt.BlockSizeMin))
case config.SCrypt.BlockSize > scrypt.BlockSizeMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSCrypt, "block_size", config.SCrypt.BlockSize, scrypt.BlockSizeMax))
}
switch {
case config.SCrypt.Parallelism == 0:
config.SCrypt.Parallelism = schema.DefaultPasswordConfig.SCrypt.Parallelism
case config.SCrypt.Parallelism < scrypt.ParallelismMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "parallelism", config.SCrypt.Parallelism, scrypt.ParallelismMin))
case config.SCrypt.Parallelism > scrypt.ParallelismMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSCrypt, "parallelism", config.SCrypt.Parallelism, scrypt.ParallelismMax))
}
switch {
case config.SCrypt.KeyLength == 0:
config.SCrypt.KeyLength = schema.DefaultPasswordConfig.SCrypt.KeyLength
case config.SCrypt.KeyLength < scrypt.KeyLengthMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "key_length", config.SCrypt.KeyLength, scrypt.KeyLengthMin))
case config.SCrypt.KeyLength > scrypt.KeyLengthMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSCrypt, "key_length", config.SCrypt.KeyLength, scrypt.KeyLengthMax))
[FEATURE] Enhance LDAP/SMTP TLS Configuration and Unify Them (#1557) * add new directive in the global scope `certificates_directory` which is used to bulk load certs and trust them in Authelia * this is in ADDITION to system certs and are trusted by both LDAP and SMTP * added a shared TLSConfig struct to be used by both SMTP and LDAP, and anything else in the future that requires tuning the TLS * remove usage of deprecated LDAP funcs Dial and DialTLS in favor of DialURL which is also easier to use * use the server name from LDAP URL or SMTP host when validating the certificate unless otherwise defined in the TLS section * added temporary translations from the old names to the new ones for all deprecated options * added docs * updated example configuration * final deprecations to be done in 4.28.0 * doc updates * fix misc linting issues * uniform deprecation notices for ease of final removal * added additional tests covering previously uncovered areas and the new configuration options * add non-fatal to certificate loading when system certs could not be loaded * adjust timeout of Suite ShortTimeouts * add warnings pusher for the StructValidator * make the schema suites uninform * utilize the warnings in the StructValidator * fix test suite usage for skip_verify * extract LDAP filter parsing into it's own function to make it possible to test * test LDAP filter parsing * update ErrorContainer interface * add tests to the StructValidator * add NewTLSConfig test * move baseDN for users/groups into parsed values * add tests to cover many of the outstanding areas in LDAP * add explicit deferred LDAP conn close to UpdatePassword * add some basic testing to SMTP notifier * suggestions from code review
2021-01-04 10:28:55 +00:00
}
switch {
case config.SCrypt.SaltLength == 0:
config.SCrypt.SaltLength = schema.DefaultPasswordConfig.SCrypt.SaltLength
case config.SCrypt.SaltLength < scrypt.SaltLengthMin:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooSmall, hashSCrypt, "salt_length", config.SCrypt.SaltLength, scrypt.SaltLengthMin))
case config.SCrypt.SaltLength > scrypt.SaltLengthMax:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordOptionTooLarge, hashSCrypt, "salt_length", config.SCrypt.SaltLength, scrypt.SaltLengthMax))
}
}
//nolint:gocyclo // Function is clear enough.
func validateFileAuthenticationBackendPasswordConfigLegacy(config *schema.Password) {
switch config.Algorithm {
case hashLegacySHA512:
config.Algorithm = hashSHA2Crypt
if config.SHA2Crypt.Variant == "" {
config.SHA2Crypt.Variant = schema.DefaultPasswordConfig.SHA2Crypt.Variant
}
if config.Iterations > 0 && config.SHA2Crypt.Iterations == 0 {
config.SHA2Crypt.Iterations = config.Iterations
}
if config.SaltLength > 0 && config.SHA2Crypt.SaltLength == 0 {
if config.SaltLength > 16 {
config.SHA2Crypt.SaltLength = 16
} else {
config.SHA2Crypt.SaltLength = config.SaltLength
}
}
case hashLegacyArgon2id:
config.Algorithm = hashArgon2
if config.Argon2.Variant == "" {
config.Argon2.Variant = schema.DefaultPasswordConfig.Argon2.Variant
}
if config.Iterations > 0 && config.Argon2.Memory == 0 {
config.Argon2.Iterations = config.Iterations
}
if config.Memory > 0 && config.Argon2.Memory == 0 {
config.Argon2.Memory = config.Memory * 1024
}
if config.Parallelism > 0 && config.Argon2.Parallelism == 0 {
config.Argon2.Parallelism = config.Parallelism
}
if config.KeyLength > 0 && config.Argon2.KeyLength == 0 {
config.Argon2.KeyLength = config.KeyLength
}
if config.SaltLength > 0 && config.Argon2.SaltLength == 0 {
config.Argon2.SaltLength = config.SaltLength
}
}
}
func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP.Implementation == "" {
config.LDAP.Implementation = schema.LDAPImplementationCustom
}
defaultTLS := validateLDAPAuthenticationBackendImplementation(config, validator)
if config.LDAP.URL == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "url"))
} else {
defaultTLS.ServerName = validateLDAPAuthenticationBackendURL(config.LDAP, validator)
}
if config.LDAP.TLS == nil {
config.LDAP.TLS = &schema.TLSConfig{}
}
if err := ValidateTLSConfig(config.LDAP.TLS, defaultTLS); err != nil {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSConfigInvalid, err))
}
if strings.Contains(config.LDAP.UsersFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))
}
if strings.Contains(config.LDAP.GroupsFilter, "{0}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{0}", "{input}"))
}
if strings.Contains(config.LDAP.GroupsFilter, "{1}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "groups_filter", "{1}", "{username}"))
}
validateLDAPRequiredParameters(config, validator)
}
func validateLDAPAuthenticationBackendImplementation(config *schema.AuthenticationBackend, validator *schema.StructValidator) *schema.TLSConfig {
var implementation *schema.LDAPAuthenticationBackend
switch config.LDAP.Implementation {
case schema.LDAPImplementationCustom:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom
case schema.LDAPImplementationActiveDirectory:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory
case schema.LDAPImplementationRFC2307bis:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis
case schema.LDAPImplementationFreeIPA:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA
case schema.LDAPImplementationLLDAP:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP
case schema.LDAPImplementationGLAuth:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth
default:
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, strJoinOr(validLDAPImplementations), config.LDAP.Implementation))
}
tlsconfig := &schema.TLSConfig{}
if implementation != nil {
if config.LDAP.Timeout == 0 {
config.LDAP.Timeout = implementation.Timeout
}
tlsconfig = &schema.TLSConfig{
MinimumVersion: implementation.TLS.MinimumVersion,
MaximumVersion: implementation.TLS.MaximumVersion,
}
setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config.LDAP, implementation)
}
return tlsconfig
}
func ldapImplementationShouldSetStr(config, implementation string) bool {
return config == "" && implementation != ""
}
func setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config *schema.LDAPAuthenticationBackend, implementation *schema.LDAPAuthenticationBackend) {
if ldapImplementationShouldSetStr(config.AdditionalUsersDN, implementation.AdditionalUsersDN) {
config.AdditionalUsersDN = implementation.AdditionalUsersDN
}
if ldapImplementationShouldSetStr(config.AdditionalGroupsDN, implementation.AdditionalGroupsDN) {
config.AdditionalGroupsDN = implementation.AdditionalGroupsDN
}
if ldapImplementationShouldSetStr(config.UsersFilter, implementation.UsersFilter) {
config.UsersFilter = implementation.UsersFilter
}
if ldapImplementationShouldSetStr(config.UsernameAttribute, implementation.UsernameAttribute) {
config.UsernameAttribute = implementation.UsernameAttribute
}
if ldapImplementationShouldSetStr(config.DisplayNameAttribute, implementation.DisplayNameAttribute) {
config.DisplayNameAttribute = implementation.DisplayNameAttribute
}
if ldapImplementationShouldSetStr(config.MailAttribute, implementation.MailAttribute) {
config.MailAttribute = implementation.MailAttribute
}
if ldapImplementationShouldSetStr(config.GroupsFilter, implementation.GroupsFilter) {
config.GroupsFilter = implementation.GroupsFilter
}
if ldapImplementationShouldSetStr(config.GroupNameAttribute, implementation.GroupNameAttribute) {
config.GroupNameAttribute = implementation.GroupNameAttribute
}
}
func validateLDAPAuthenticationBackendURL(config *schema.LDAPAuthenticationBackend, validator *schema.StructValidator) (hostname string) {
var (
parsedURL *url.URL
err error
)
if parsedURL, err = url.Parse(config.URL); err != nil {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendURLNotParsable, err))
return
}
if parsedURL.Scheme != schemeLDAP && parsedURL.Scheme != schemeLDAPS {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendURLInvalidScheme, parsedURL.Scheme))
return
}
config.URL = parsedURL.String()
return parsedURL.Hostname()
}
func validateLDAPRequiredParameters(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP.PermitUnauthenticatedBind {
if config.LDAP.Password != "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithPassword))
}
if !config.PasswordReset.Disable {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendUnauthenticatedBindWithResetEnabled))
}
} else {
if config.LDAP.User == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "user"))
}
if config.LDAP.Password == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "password"))
}
}
if config.LDAP.BaseDN == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "base_dn"))
}
if config.LDAP.UsersFilter == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "users_filter"))
} else {
if !strings.HasPrefix(config.LDAP.UsersFilter, "(") || !strings.HasSuffix(config.LDAP.UsersFilter, ")") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "users_filter", config.LDAP.UsersFilter, config.LDAP.UsersFilter))
}
if !strings.Contains(config.LDAP.UsersFilter, "{username_attribute}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "username_attribute"))
}
// This test helps the user know that users_filter is broken after the breaking change induced by this commit.
if !strings.Contains(config.LDAP.UsersFilter, "{input}") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "input"))
}
}
if config.LDAP.GroupsFilter == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "groups_filter"))
} else if !strings.HasPrefix(config.LDAP.GroupsFilter, "(") || !strings.HasSuffix(config.LDAP.GroupsFilter, ")") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter))
}
}