From 2037a0ee4fd24138e57ca31eadfbf6ed15e52070 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Thu, 2 Jun 2022 09:18:45 +1000 Subject: [PATCH] fix(commands): hash-password usage instructions (#3437) This fixes the hash-password usage instructions and ensures it uses mostly a configuration source based config. In addition it updates our recommended argon2id parameters with the RFC recommendations. --- docs/configuration/authentication/file.md | 64 ++++++------ internal/authentication/password_hash_test.go | 4 +- internal/commands/hash.go | 97 ++++++++++++------- internal/configuration/decode_hooks.go | 4 +- internal/configuration/decode_hooks_test.go | 4 +- internal/configuration/provider.go | 4 +- .../configuration/schema/authentication.go | 8 +- internal/configuration/sources.go | 39 +++++++- internal/configuration/types.go | 6 ++ .../configuration/validator/authentication.go | 89 +++++++++-------- internal/suites/suite_cli_test.go | 2 +- 11 files changed, 199 insertions(+), 122 deletions(-) diff --git a/docs/configuration/authentication/file.md b/docs/configuration/authentication/file.md index 076998504..3a8565422 100644 --- a/docs/configuration/authentication/file.md +++ b/docs/configuration/authentication/file.md @@ -23,13 +23,13 @@ authentication_backend: path: /config/users.yml password: algorithm: argon2id - iterations: 1 + iterations: 3 salt_length: 16 - parallelism: 8 + key_length: 32 + parallelism: 4 memory: 64 ``` - ## Format The format of the users file is as follows. @@ -100,9 +100,9 @@ required: no Controls the number of hashing iterations done by the other hashing settings. -When using `argon2id` the minimum is 1, which is also the recommended value. +When using `argon2id` the minimum is 3, which is also the recommended and default value. -When using `sha512` the minimum is 1000, and 50000 is the recommended value. +When using `sha512` the minimum is 1000, and 50000 is the recommended and default value. #### salt_length @@ -123,7 +123,7 @@ and there is no documented reason why you'd set it to anything other than this,
type: integer {: .label .label-config .label-purple } -default: 8 +default: 4 {: .label .label-config .label-blue } required: no {: .label .label-config .label-green } @@ -134,13 +134,20 @@ which affects the effective cost of hashing. #### memory +
+type: integer +{: .label .label-config .label-purple } +default: 64 +{: .label .label-config .label-blue } +required: no +{: .label .label-config .label-green } +
This setting is specific to `argon2id` and unused with `sha512`. Sets the amount of memory allocated to a single password hashing action. This memory is released by go after the hashing process completes, however the operating system may not reclaim it until it needs the memory which may make Authelia appear to be using more memory than it technically is. - ## Passwords The file contains hashed passwords instead of plain text passwords for security reasons. @@ -149,13 +156,13 @@ You can use Authelia binary or docker image to generate the hash of any password hash-password command has many tunable options, you can view them with the `authelia hash-password --help` command. For example if you wanted to improve the entropy you could generate a 16 byte salt and provide it with the `--salt` flag. -Example: `authelia hash-password --salt abcdefghijklhijl`. For argon2id the salt must +Example: `authelia hash-password --salt abcdefghijklhijl -- 'yourpassword'`. For argon2id the salt must always be valid for base64 decoding (characters a through z, A through Z, 0 through 9, and +/). Passwords passed to `hash-password` should be single quoted if using special characters to prevent parameter substitution. For instance to generate a hash with the docker image just run: - $ docker run authelia/authelia:latest authelia hash-password 'yourpassword' + $ docker run authelia/authelia:latest authelia hash-password -- 'yourpassword' Password hash: $argon2id$v=19$m=65536$3oc26byQuSkQqksq$zM1QiTvVPrMfV6BVLs2t4gM+af5IN7euO0VB6+Q8ZFs You may also use the `--config` flag to point to your existing configuration. When used, the values defined in the config will be used instead. @@ -166,17 +173,18 @@ Full CLI Help Documentation: Hash a password to be used in file-based users database. Default algorithm is argon2id. Usage: - authelia hash-password [password] [flags] + authelia hash-password [flags] -- Flags: + -c, --config strings Configuration files -h, --help help for hash-password - -i, --iterations int set the number of hashing iterations (default 1) + -i, --iterations int set the number of hashing iterations (default 3) -k, --key-length int [argon2id] set the key length param (default 32) -m, --memory int [argon2id] set the amount of memory param (in MB) (default 64) - -p, --parallelism int [argon2id] set the parallelism param (default 8) + -p, --parallelism int [argon2id] set the parallelism param (default 4) -s, --salt string set the salt string -l, --salt-length int set the auto-generated salt length (default 16) - -z, --sha512 use sha512 as the algorithm (defaults iterations to 50000, change with -i) + -z, --sha512 use sha512 as the algorithm (changes iterations to 50000, change with -i) ``` ### Password hash algorithm @@ -209,7 +217,6 @@ If this is not desirable we recommend investigating the following options in ord 2. adjusting the [memory](#memory) parameter 3. changing the [algorithm](#algorithm) - ### Password hash algorithm tuning All algorithm tuning for Argon2id is supported. The only configuration variables that affect @@ -220,26 +227,21 @@ to cater for a reasonable system, if you're unsure about which settings to tune, parameters below, or for a more in depth understanding see the referenced documentation in [Argon2 links](./file.md#argon2-links). +#### Recommended Parameters: Argon2id -#### Examples for specific systems - -These examples have been tested against a single system to make sure they roughly take -0.5 seconds each. Your results may vary depending on individual specification and -utilization, but they are a good guide to get started. You should however read the -linked documents in [Argon2 links](./file.md#argon2-links). - -| System |Iterations|Parallelism|Memory | -|:------------: |:--------:|:---------:|:-----:| -|Raspberry Pi 2 | 1 | 8 | 64 | -|Raspberry Pi 3 | 1 | 8 | 128 | -|Raspberry Pi 4 | 1 | 8 | 128 | -|Intel G5 i5 NUC| 1 | 8 | 1024 | +This table is adapted from [RFC9106 Parameter Choice]: +| Situation | Iterations (t) | Parallelism (p) | Memory (m) | Salt Size | Key Size | +|:-----------:|:--------------:|:---------------:|:----------:|:---------:|:--------:| +| Low Memory | 3 | 4 | 64 | 16 | 32 | +| Recommended | 1 | 4 | 2048 | 16 | 32 | ## Argon2 Links -[How to choose the right parameters for Argon2](https://www.twelve21.io/how-to-choose-the-right-parameters-for-argon2/) +- [Go Documentation](https://godoc.org/golang.org/x/crypto/argon2) +- Argon2 Specification [RFC9106] +- [OWASP Password Storage Cheatsheet] -[Go Documentation](https://godoc.org/golang.org/x/crypto/argon2) - -[IETF Draft](https://tools.ietf.org/id/draft-irtf-cfrg-argon2-09.html) +[RFC9106]: https://www.rfc-editor.org/rfc/rfc9106.html +[RFC9106 Parameter Choice]: https://www.rfc-editor.org/rfc/rfc9106.html#section-4 +[OWASP Password Storage Cheatsheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html diff --git a/internal/authentication/password_hash_test.go b/internal/authentication/password_hash_test.go index 79156ca16..3cd3ade7a 100644 --- a/internal/authentication/password_hash_test.go +++ b/internal/authentication/password_hash_test.go @@ -38,7 +38,7 @@ func TestShouldHashArgon2idPassword(t *testing.T) { assert.NoError(t, err) assert.Equal(t, argon2id, code) assert.Equal(t, "BpLnfgDsc2WD8F2q", salt) - assert.Equal(t, "f+Y+KaS12gkNHN0Llc9kqDZuk1OYvoXj8t+5DcPbgY4", key) + assert.Equal(t, "kYempka60N8ETZ+EedP+Fn3z83mEPMl08RQEXTwY6u0", key) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Iterations, parameters.GetInt("t", HashingDefaultArgon2idTime)) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Memory*1024, parameters.GetInt("m", HashingDefaultArgon2idMemory)) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Parallelism, parameters.GetInt("p", HashingDefaultArgon2idParallelism)) @@ -217,7 +217,7 @@ func TestShouldNotParseArgon2idHashWithWrongKeyLength(t *testing.T) { } func TestShouldParseArgon2idHash(t *testing.T) { - passwordHash, err := ParseHash("$argon2id$v=19$m=65536,t=1,p=8$NEwwcVNuQWlQMFpkMndxdg$LlHjiLxPB94pdmOiNwr7Bgy+uy3huSv6y9phCQ+mLls") + passwordHash, err := ParseHash("$argon2id$v=19$m=65536,t=3,p=4$NEwwcVNuQWlQMFpkMndxdg$LlHjiLxPB94pdmOiNwr7Bgy+uy3huSv6y9phCQ+mLls") assert.NoError(t, err) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Iterations, passwordHash.Iterations) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Parallelism, passwordHash.Parallelism) diff --git a/internal/commands/hash.go b/internal/commands/hash.go index a0bf67208..cf1d45790 100644 --- a/internal/commands/hash.go +++ b/internal/commands/hash.go @@ -9,16 +9,16 @@ import ( "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/configuration" "github.com/authelia/authelia/v4/internal/configuration/schema" - "github.com/authelia/authelia/v4/internal/logging" + "github.com/authelia/authelia/v4/internal/configuration/validator" ) // NewHashPasswordCmd returns a new Hash Password Cmd. func NewHashPasswordCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "hash-password [password]", + Use: "hash-password [flags] -- ", Short: "Hash a password to be used in file-based users database. Default algorithm is argon2id.", Args: cobra.MinimumNArgs(1), - Run: cmdHashPasswordRun, + RunE: cmdHashPasswordRunE, } cmd.Flags().BoolP("sha512", "z", false, fmt.Sprintf("use sha512 as the algorithm (changes iterations to %d, change with -i)", schema.DefaultPasswordSHA512Configuration.Iterations)) @@ -33,34 +33,44 @@ func NewHashPasswordCmd() (cmd *cobra.Command) { return cmd } -func cmdHashPasswordRun(cmd *cobra.Command, args []string) { - logger := logging.Logger() - - sha512, _ := cmd.Flags().GetBool("sha512") - iterations, _ := cmd.Flags().GetInt("iterations") +func cmdHashPasswordRunE(cmd *cobra.Command, args []string) (err error) { salt, _ := cmd.Flags().GetString("salt") - keyLength, _ := cmd.Flags().GetInt("key-length") - saltLength, _ := cmd.Flags().GetInt("salt-length") - memory, _ := cmd.Flags().GetInt("memory") - parallelism, _ := cmd.Flags().GetInt("parallelism") + sha512, _ := cmd.Flags().GetBool("sha512") configs, _ := cmd.Flags().GetStringSlice("config") - if len(configs) > 0 { - val := schema.NewStructValidator() + mapDefaults := map[string]interface{}{ + "authentication_backend.file.password.algorithm": schema.DefaultPasswordConfiguration.Algorithm, + "authentication_backend.file.password.iterations": schema.DefaultPasswordConfiguration.Iterations, + "authentication_backend.file.password.key_length": schema.DefaultPasswordConfiguration.KeyLength, + "authentication_backend.file.password.salt_length": schema.DefaultPasswordConfiguration.SaltLength, + "authentication_backend.file.password.parallelism": schema.DefaultPasswordConfiguration.Parallelism, + "authentication_backend.file.password.memory": schema.DefaultPasswordConfiguration.Memory, + } - _, config, err := configuration.Load(val, configuration.NewDefaultSources(configs, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)...) - if err != nil { - logger.Fatalf("Error occurred loading configuration: %v", err) - } + if sha512 { + mapDefaults["authentication_backend.file.password.algorithm"] = schema.DefaultPasswordSHA512Configuration.Algorithm + mapDefaults["authentication_backend.file.password.iterations"] = schema.DefaultPasswordSHA512Configuration.Iterations + mapDefaults["authentication_backend.file.password.salt_length"] = schema.DefaultPasswordSHA512Configuration.SaltLength + } - if config.AuthenticationBackend.File != nil && config.AuthenticationBackend.File.Password != nil { - sha512 = config.AuthenticationBackend.File.Password.Algorithm == "sha512" - iterations = config.AuthenticationBackend.File.Password.Iterations - keyLength = config.AuthenticationBackend.File.Password.KeyLength - saltLength = config.AuthenticationBackend.File.Password.SaltLength - memory = config.AuthenticationBackend.File.Password.Memory - parallelism = config.AuthenticationBackend.File.Password.Parallelism - } + mapCLI := map[string]string{ + "iterations": "authentication_backend.file.password.iterations", + "key-length": "authentication_backend.file.password.key_length", + "salt-length": "authentication_backend.file.password.salt_length", + "parallelism": "authentication_backend.file.password.parallelism", + "memory": "authentication_backend.file.password.memory", + } + + sources := configuration.NewDefaultSourcesWithDefaults(configs, + configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter, + configuration.NewMapSource(mapDefaults), + configuration.NewCommandLineSourceWithMapping(cmd.Flags(), mapCLI, false, false), + ) + + val := schema.NewStructValidator() + + if _, config, err = configuration.Load(val, sources...); err != nil { + return fmt.Errorf("error occurred loading configuration: %w", err) } var ( @@ -68,24 +78,41 @@ func cmdHashPasswordRun(cmd *cobra.Command, args []string) { algorithm authentication.CryptAlgo ) - if sha512 { - if iterations == schema.DefaultPasswordConfiguration.Iterations { - iterations = schema.DefaultPasswordSHA512Configuration.Iterations + p := config.AuthenticationBackend.File.Password + + switch p.Algorithm { + case "sha512": + algorithm = authentication.HashingAlgorithmSHA512 + default: + algorithm = authentication.HashingAlgorithmArgon2id + } + + validator.ValidatePasswordConfiguration(p, val) + + errs := val.Errors() + + if len(errs) != 0 { + for i, e := range errs { + if i == 0 { + err = e + continue + } + + err = fmt.Errorf("%v, %w", err, e) } - algorithm = authentication.HashingAlgorithmSHA512 - } else { - algorithm = authentication.HashingAlgorithmArgon2id + return fmt.Errorf("errors occurred validating the password configuration: %w", err) } if salt != "" { salt = crypt.Base64Encoding.EncodeToString([]byte(salt)) } - hash, err := authentication.HashPassword(args[0], salt, algorithm, iterations, memory*1024, parallelism, keyLength, saltLength) - if err != nil { - logging.Logger().Fatalf("Error occurred during hashing: %v\n", err) + if hash, err = authentication.HashPassword(args[0], salt, algorithm, p.Iterations, p.Memory*1024, p.Parallelism, p.KeyLength, p.SaltLength); err != nil { + return fmt.Errorf("error during password hashing: %w", err) } fmt.Printf("Password hash: %s\n", hash) + + return nil } diff --git a/internal/configuration/decode_hooks.go b/internal/configuration/decode_hooks.go index e7fa14988..cd1dd2ee6 100644 --- a/internal/configuration/decode_hooks.go +++ b/internal/configuration/decode_hooks.go @@ -166,8 +166,8 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType { } } -// StringToRegexpFunc decodes a string into a *regexp.Regexp or regexp.Regexp. -func StringToRegexpFunc() mapstructure.DecodeHookFuncType { +// StringToRegexpHookFunc decodes a string into a *regexp.Regexp or regexp.Regexp. +func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType { return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) { var ptr bool diff --git a/internal/configuration/decode_hooks_test.go b/internal/configuration/decode_hooks_test.go index c34011221..4e815b2e4 100644 --- a/internal/configuration/decode_hooks_test.go +++ b/internal/configuration/decode_hooks_test.go @@ -585,7 +585,7 @@ func TestStringToRegexpFunc(t *testing.T) { }, } - hook := configuration.StringToRegexpFunc() + hook := configuration.StringToRegexpHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { @@ -698,7 +698,7 @@ func TestStringToRegexpFuncPointers(t *testing.T) { }, } - hook := configuration.StringToRegexpFunc() + hook := configuration.StringToRegexpHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go index 529329fd7..caec00297 100644 --- a/internal/configuration/provider.go +++ b/internal/configuration/provider.go @@ -45,9 +45,9 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o inte DecodeHook: mapstructure.ComposeDecodeHookFunc( mapstructure.StringToSliceHookFunc(","), StringToMailAddressHookFunc(), - ToTimeDurationHookFunc(), StringToURLHookFunc(), - StringToRegexpFunc(), + StringToRegexpHookFunc(), + ToTimeDurationHookFunc(), ), Metadata: nil, Result: o, diff --git a/internal/configuration/schema/authentication.go b/internal/configuration/schema/authentication.go index 1ada6f63a..4677b9eb6 100644 --- a/internal/configuration/schema/authentication.go +++ b/internal/configuration/schema/authentication.go @@ -66,22 +66,22 @@ type PasswordResetAuthenticationBackendConfiguration struct { // DefaultPasswordConfiguration represents the default configuration related to Argon2id hashing. var DefaultPasswordConfiguration = PasswordConfiguration{ - Iterations: 1, + Iterations: 3, KeyLength: 32, SaltLength: 16, Algorithm: argon2id, Memory: 64, - Parallelism: 8, + Parallelism: 4, } // DefaultCIPasswordConfiguration represents the default configuration related to Argon2id hashing for CI. var DefaultCIPasswordConfiguration = PasswordConfiguration{ - Iterations: 1, + Iterations: 3, KeyLength: 32, SaltLength: 16, Algorithm: argon2id, Memory: 64, - Parallelism: 8, + Parallelism: 4, } // DefaultPasswordSHA512Configuration represents the default configuration related to SHA512 hashing. diff --git a/internal/configuration/sources.go b/internal/configuration/sources.go index 22876e5b8..8798083aa 100644 --- a/internal/configuration/sources.go +++ b/internal/configuration/sources.go @@ -6,6 +6,7 @@ import ( "github.com/knadh/koanf" "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/confmap" "github.com/knadh/koanf/providers/env" "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/providers/posflag" @@ -144,8 +145,31 @@ func (s *CommandLineSource) Load(_ *schema.StructValidator) (err error) { return s.koanf.Load(posflag.Provider(s.flags, ".", s.koanf), nil) } +// NewMapSource returns a new map[string]interface{} source. +func NewMapSource(m map[string]interface{}) (source *MapSource) { + return &MapSource{ + m: m, + koanf: koanf.New(constDelimiter), + } +} + +// Name of the Source. +func (s MapSource) Name() (name string) { + return "map" +} + +// Merge the CommandLineSource koanf.Koanf into the provided one. +func (s *MapSource) Merge(ko *koanf.Koanf, val *schema.StructValidator) (err error) { + return ko.Merge(s.koanf) +} + +// Load the Source into the YAMLFileSource koanf.Koanf. +func (s *MapSource) Load(_ *schema.StructValidator) (err error) { + return s.koanf.Load(confmap.Provider(s.m, constDelimiter), nil) +} + // NewDefaultSources returns a slice of Source configured to load from specified YAML files. -func NewDefaultSources(filePaths []string, prefix, delimiter string) (sources []Source) { +func NewDefaultSources(filePaths []string, prefix, delimiter string, additionalSources ...Source) (sources []Source) { fileSources := NewYAMLFileSources(filePaths) for _, source := range fileSources { sources = append(sources, source) @@ -154,5 +178,18 @@ func NewDefaultSources(filePaths []string, prefix, delimiter string) (sources [] sources = append(sources, NewEnvironmentSource(prefix, delimiter)) sources = append(sources, NewSecretsSource(prefix, delimiter)) + if len(additionalSources) != 0 { + sources = append(sources, additionalSources...) + } + + return sources +} + +// NewDefaultSourcesWithDefaults returns a slice of Source configured to load from specified YAML files with additional sources. +func NewDefaultSourcesWithDefaults(filePaths []string, prefix, delimiter string, defaults Source, additionalSources ...Source) (sources []Source) { + sources = []Source{defaults} + + sources = append(sources, NewDefaultSources(filePaths, prefix, delimiter, additionalSources...)...) + return sources } diff --git a/internal/configuration/types.go b/internal/configuration/types.go index fe5787be5..90dbaa63c 100644 --- a/internal/configuration/types.go +++ b/internal/configuration/types.go @@ -40,3 +40,9 @@ type CommandLineSource struct { flags *pflag.FlagSet callback func(flag *pflag.Flag) (string, interface{}) } + +// MapSource loads configuration from the command line flags. +type MapSource struct { + m map[string]interface{} + koanf *koanf.Koanf +} diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go index 7dd307e4c..83f646c08 100644 --- a/internal/configuration/validator/authentication.go +++ b/internal/configuration/validator/authentication.go @@ -53,63 +53,68 @@ func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackendC if config.Password == nil { config.Password = &schema.DefaultPasswordConfiguration } else { - // Salt Length. - switch { - case config.Password.SaltLength == 0: - config.Password.SaltLength = schema.DefaultPasswordConfiguration.SaltLength - case config.Password.SaltLength < 8: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordSaltLength, config.Password.SaltLength)) - } - - switch config.Password.Algorithm { - case "": - config.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm - fallthrough - case hashArgon2id: - validateFileAuthenticationBackendArgon2id(config, validator) - case hashSHA512: - validateFileAuthenticationBackendSHA512(config) - default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Password.Algorithm)) - } - - if config.Password.Iterations < 1 { - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Password.Iterations)) - } + ValidatePasswordConfiguration(config.Password, validator) } } -func validateFileAuthenticationBackendSHA512(config *schema.FileAuthenticationBackendConfiguration) { - // Iterations (time). - if config.Password.Iterations == 0 { - config.Password.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations +// ValidatePasswordConfiguration validates the file auth backend password configuration. +func ValidatePasswordConfiguration(config *schema.PasswordConfiguration, validator *schema.StructValidator) { + // Salt Length. + switch { + case config.SaltLength == 0: + config.SaltLength = schema.DefaultPasswordConfiguration.SaltLength + case config.SaltLength < 8: + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordSaltLength, config.SaltLength)) + } + + switch config.Algorithm { + case "": + config.Algorithm = schema.DefaultPasswordConfiguration.Algorithm + fallthrough + case hashArgon2id: + validateFileAuthenticationBackendArgon2id(config, validator) + case hashSHA512: + validateFileAuthenticationBackendSHA512(config) + default: + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm)) + } + + if config.Iterations < 1 { + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Iterations)) } } -func validateFileAuthenticationBackendArgon2id(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) { + +func validateFileAuthenticationBackendSHA512(config *schema.PasswordConfiguration) { // Iterations (time). - if config.Password.Iterations == 0 { - config.Password.Iterations = schema.DefaultPasswordConfiguration.Iterations + if config.Iterations == 0 { + config.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations + } +} +func validateFileAuthenticationBackendArgon2id(config *schema.PasswordConfiguration, validator *schema.StructValidator) { + // Iterations (time). + if config.Iterations == 0 { + config.Iterations = schema.DefaultPasswordConfiguration.Iterations } // Parallelism. - if config.Password.Parallelism == 0 { - config.Password.Parallelism = schema.DefaultPasswordConfiguration.Parallelism - } else if config.Password.Parallelism < 1 { - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidParallelism, config.Password.Parallelism)) + if config.Parallelism == 0 { + config.Parallelism = schema.DefaultPasswordConfiguration.Parallelism + } else if config.Parallelism < 1 { + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidParallelism, config.Parallelism)) } // Memory. - if config.Password.Memory == 0 { - config.Password.Memory = schema.DefaultPasswordConfiguration.Memory - } else if config.Password.Memory < config.Password.Parallelism*8 { - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidMemory, config.Password.Parallelism, config.Password.Parallelism*8, config.Password.Memory)) + if config.Memory == 0 { + config.Memory = schema.DefaultPasswordConfiguration.Memory + } else if config.Memory < config.Parallelism*8 { + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidMemory, config.Parallelism, config.Parallelism*8, config.Memory)) } // Key Length. - if config.Password.KeyLength == 0 { - config.Password.KeyLength = schema.DefaultPasswordConfiguration.KeyLength - } else if config.Password.KeyLength < 16 { - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength, config.Password.KeyLength)) + if config.KeyLength == 0 { + config.KeyLength = schema.DefaultPasswordConfiguration.KeyLength + } else if config.KeyLength < 16 { + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength, config.KeyLength)) } } diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go index 9ba3893da..74edcf016 100644 --- a/internal/suites/suite_cli_test.go +++ b/internal/suites/suite_cli_test.go @@ -85,7 +85,7 @@ func (s *CLISuite) TestShouldFailValidateConfig() { func (s *CLISuite) TestShouldHashPasswordArgon2id() { output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "hash-password", "test", "-m", "32", "-s", "test1234"}) s.Assert().NoError(err) - s.Assert().Contains(output, "Password hash: $argon2id$v=19$m=32768,t=1,p=8") + s.Assert().Contains(output, "Password hash: $argon2id$v=19$m=32768,t=3,p=4$") } func (s *CLISuite) TestShouldHashPasswordSHA512() {