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.
This commit is contained in:
James Elliott 2022-06-02 09:18:45 +10:00 committed by GitHub
parent 0d3ee8e730
commit 2037a0ee4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 199 additions and 122 deletions

View File

@ -23,13 +23,13 @@ authentication_backend:
path: /config/users.yml path: /config/users.yml
password: password:
algorithm: argon2id algorithm: argon2id
iterations: 1 iterations: 3
salt_length: 16 salt_length: 16
parallelism: 8 key_length: 32
parallelism: 4
memory: 64 memory: 64
``` ```
## Format ## Format
The format of the users file is as follows. 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. 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 #### salt_length
@ -123,7 +123,7 @@ and there is no documented reason why you'd set it to anything other than this,
<div markdown="1"> <div markdown="1">
type: integer type: integer
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
default: 8 default: 4
{: .label .label-config .label-blue } {: .label .label-config .label-blue }
required: no required: no
{: .label .label-config .label-green } {: .label .label-config .label-green }
@ -134,13 +134,20 @@ which affects the effective cost of hashing.
#### memory #### memory
<div markdown="1">
type: integer
{: .label .label-config .label-purple }
default: 64
{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
</div>
This setting is specific to `argon2id` and unused with `sha512`. Sets the amount of memory allocated to a single 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 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 may not reclaim it until it needs the memory which may make Authelia appear to be using more memory than it technically
is. is.
## Passwords ## Passwords
The file contains hashed passwords instead of plain text passwords for security reasons. 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 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 `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. 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 +/). 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. 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: 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 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. 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. Hash a password to be used in file-based users database. Default algorithm is argon2id.
Usage: Usage:
authelia hash-password [password] [flags] authelia hash-password [flags] -- <password>
Flags: Flags:
-c, --config strings Configuration files
-h, --help help for hash-password -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) -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) -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 -s, --salt string set the salt string
-l, --salt-length int set the auto-generated salt length (default 16) -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 ### 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 2. adjusting the [memory](#memory) parameter
3. changing the [algorithm](#algorithm) 3. changing the [algorithm](#algorithm)
### Password hash algorithm tuning ### Password hash algorithm tuning
All algorithm tuning for Argon2id is supported. The only configuration variables that affect 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 parameters below, or for a more in depth understanding see the referenced documentation in
[Argon2 links](./file.md#argon2-links). [Argon2 links](./file.md#argon2-links).
#### Recommended Parameters: Argon2id
#### Examples for specific systems This table is adapted from [RFC9106 Parameter Choice]:
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 |
| 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 ## 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) [RFC9106]: https://www.rfc-editor.org/rfc/rfc9106.html
[RFC9106 Parameter Choice]: https://www.rfc-editor.org/rfc/rfc9106.html#section-4
[IETF Draft](https://tools.ietf.org/id/draft-irtf-cfrg-argon2-09.html) [OWASP Password Storage Cheatsheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html

View File

@ -38,7 +38,7 @@ func TestShouldHashArgon2idPassword(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, argon2id, code) assert.Equal(t, argon2id, code)
assert.Equal(t, "BpLnfgDsc2WD8F2q", salt) 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.Iterations, parameters.GetInt("t", HashingDefaultArgon2idTime))
assert.Equal(t, schema.DefaultCIPasswordConfiguration.Memory*1024, parameters.GetInt("m", HashingDefaultArgon2idMemory)) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Memory*1024, parameters.GetInt("m", HashingDefaultArgon2idMemory))
assert.Equal(t, schema.DefaultCIPasswordConfiguration.Parallelism, parameters.GetInt("p", HashingDefaultArgon2idParallelism)) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Parallelism, parameters.GetInt("p", HashingDefaultArgon2idParallelism))
@ -217,7 +217,7 @@ func TestShouldNotParseArgon2idHashWithWrongKeyLength(t *testing.T) {
} }
func TestShouldParseArgon2idHash(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.NoError(t, err)
assert.Equal(t, schema.DefaultCIPasswordConfiguration.Iterations, passwordHash.Iterations) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Iterations, passwordHash.Iterations)
assert.Equal(t, schema.DefaultCIPasswordConfiguration.Parallelism, passwordHash.Parallelism) assert.Equal(t, schema.DefaultCIPasswordConfiguration.Parallelism, passwordHash.Parallelism)

View File

@ -9,16 +9,16 @@ import (
"github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/configuration" "github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema" "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. // NewHashPasswordCmd returns a new Hash Password Cmd.
func NewHashPasswordCmd() (cmd *cobra.Command) { func NewHashPasswordCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "hash-password [password]", Use: "hash-password [flags] -- <password>",
Short: "Hash a password to be used in file-based users database. Default algorithm is argon2id.", Short: "Hash a password to be used in file-based users database. Default algorithm is argon2id.",
Args: cobra.MinimumNArgs(1), 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)) 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 return cmd
} }
func cmdHashPasswordRun(cmd *cobra.Command, args []string) { func cmdHashPasswordRunE(cmd *cobra.Command, args []string) (err error) {
logger := logging.Logger()
sha512, _ := cmd.Flags().GetBool("sha512")
iterations, _ := cmd.Flags().GetInt("iterations")
salt, _ := cmd.Flags().GetString("salt") salt, _ := cmd.Flags().GetString("salt")
keyLength, _ := cmd.Flags().GetInt("key-length") sha512, _ := cmd.Flags().GetBool("sha512")
saltLength, _ := cmd.Flags().GetInt("salt-length")
memory, _ := cmd.Flags().GetInt("memory")
parallelism, _ := cmd.Flags().GetInt("parallelism")
configs, _ := cmd.Flags().GetStringSlice("config") configs, _ := cmd.Flags().GetStringSlice("config")
if len(configs) > 0 { 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,
}
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
}
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() val := schema.NewStructValidator()
_, config, err := configuration.Load(val, configuration.NewDefaultSources(configs, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)...) if _, config, err = configuration.Load(val, sources...); err != nil {
if err != nil { return fmt.Errorf("error occurred loading configuration: %w", err)
logger.Fatalf("Error occurred loading configuration: %v", err)
}
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
}
} }
var ( var (
@ -68,24 +78,41 @@ func cmdHashPasswordRun(cmd *cobra.Command, args []string) {
algorithm authentication.CryptAlgo algorithm authentication.CryptAlgo
) )
if sha512 { p := config.AuthenticationBackend.File.Password
if iterations == schema.DefaultPasswordConfiguration.Iterations {
iterations = schema.DefaultPasswordSHA512Configuration.Iterations switch p.Algorithm {
case "sha512":
algorithm = authentication.HashingAlgorithmSHA512
default:
algorithm = authentication.HashingAlgorithmArgon2id
} }
algorithm = authentication.HashingAlgorithmSHA512 validator.ValidatePasswordConfiguration(p, val)
} else {
algorithm = authentication.HashingAlgorithmArgon2id 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)
}
return fmt.Errorf("errors occurred validating the password configuration: %w", err)
} }
if salt != "" { if salt != "" {
salt = crypt.Base64Encoding.EncodeToString([]byte(salt)) salt = crypt.Base64Encoding.EncodeToString([]byte(salt))
} }
hash, err := authentication.HashPassword(args[0], salt, algorithm, iterations, memory*1024, parallelism, keyLength, saltLength) if hash, err = authentication.HashPassword(args[0], salt, algorithm, p.Iterations, p.Memory*1024, p.Parallelism, p.KeyLength, p.SaltLength); err != nil {
if err != nil { return fmt.Errorf("error during password hashing: %w", err)
logging.Logger().Fatalf("Error occurred during hashing: %v\n", err)
} }
fmt.Printf("Password hash: %s\n", hash) fmt.Printf("Password hash: %s\n", hash)
return nil
} }

View File

@ -166,8 +166,8 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
} }
} }
// StringToRegexpFunc decodes a string into a *regexp.Regexp or regexp.Regexp. // StringToRegexpHookFunc decodes a string into a *regexp.Regexp or regexp.Regexp.
func StringToRegexpFunc() mapstructure.DecodeHookFuncType { func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {
return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) { return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
var ptr bool var ptr bool

View File

@ -585,7 +585,7 @@ func TestStringToRegexpFunc(t *testing.T) {
}, },
} }
hook := configuration.StringToRegexpFunc() hook := configuration.StringToRegexpHookFunc()
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) { 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 { for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {

View File

@ -45,9 +45,9 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o inte
DecodeHook: mapstructure.ComposeDecodeHookFunc( DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToSliceHookFunc(","), mapstructure.StringToSliceHookFunc(","),
StringToMailAddressHookFunc(), StringToMailAddressHookFunc(),
ToTimeDurationHookFunc(),
StringToURLHookFunc(), StringToURLHookFunc(),
StringToRegexpFunc(), StringToRegexpHookFunc(),
ToTimeDurationHookFunc(),
), ),
Metadata: nil, Metadata: nil,
Result: o, Result: o,

View File

@ -66,22 +66,22 @@ type PasswordResetAuthenticationBackendConfiguration struct {
// DefaultPasswordConfiguration represents the default configuration related to Argon2id hashing. // DefaultPasswordConfiguration represents the default configuration related to Argon2id hashing.
var DefaultPasswordConfiguration = PasswordConfiguration{ var DefaultPasswordConfiguration = PasswordConfiguration{
Iterations: 1, Iterations: 3,
KeyLength: 32, KeyLength: 32,
SaltLength: 16, SaltLength: 16,
Algorithm: argon2id, Algorithm: argon2id,
Memory: 64, Memory: 64,
Parallelism: 8, Parallelism: 4,
} }
// DefaultCIPasswordConfiguration represents the default configuration related to Argon2id hashing for CI. // DefaultCIPasswordConfiguration represents the default configuration related to Argon2id hashing for CI.
var DefaultCIPasswordConfiguration = PasswordConfiguration{ var DefaultCIPasswordConfiguration = PasswordConfiguration{
Iterations: 1, Iterations: 3,
KeyLength: 32, KeyLength: 32,
SaltLength: 16, SaltLength: 16,
Algorithm: argon2id, Algorithm: argon2id,
Memory: 64, Memory: 64,
Parallelism: 8, Parallelism: 4,
} }
// DefaultPasswordSHA512Configuration represents the default configuration related to SHA512 hashing. // DefaultPasswordSHA512Configuration represents the default configuration related to SHA512 hashing.

View File

@ -6,6 +6,7 @@ import (
"github.com/knadh/koanf" "github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env" "github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag" "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) 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. // 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) fileSources := NewYAMLFileSources(filePaths)
for _, source := range fileSources { for _, source := range fileSources {
sources = append(sources, source) 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, NewEnvironmentSource(prefix, delimiter))
sources = append(sources, NewSecretsSource(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 return sources
} }

View File

@ -40,3 +40,9 @@ type CommandLineSource struct {
flags *pflag.FlagSet flags *pflag.FlagSet
callback func(flag *pflag.Flag) (string, interface{}) 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
}

View File

@ -53,63 +53,68 @@ func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackendC
if config.Password == nil { if config.Password == nil {
config.Password = &schema.DefaultPasswordConfiguration config.Password = &schema.DefaultPasswordConfiguration
} else { } else {
ValidatePasswordConfiguration(config.Password, validator)
}
}
// ValidatePasswordConfiguration validates the file auth backend password configuration.
func ValidatePasswordConfiguration(config *schema.PasswordConfiguration, validator *schema.StructValidator) {
// Salt Length. // Salt Length.
switch { switch {
case config.Password.SaltLength == 0: case config.SaltLength == 0:
config.Password.SaltLength = schema.DefaultPasswordConfiguration.SaltLength config.SaltLength = schema.DefaultPasswordConfiguration.SaltLength
case config.Password.SaltLength < 8: case config.SaltLength < 8:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordSaltLength, config.Password.SaltLength)) validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordSaltLength, config.SaltLength))
} }
switch config.Password.Algorithm { switch config.Algorithm {
case "": case "":
config.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm config.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
fallthrough fallthrough
case hashArgon2id: case hashArgon2id:
validateFileAuthenticationBackendArgon2id(config, validator) validateFileAuthenticationBackendArgon2id(config, validator)
case hashSHA512: case hashSHA512:
validateFileAuthenticationBackendSHA512(config) validateFileAuthenticationBackendSHA512(config)
default: default:
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Password.Algorithm)) validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm))
} }
if config.Password.Iterations < 1 { if config.Iterations < 1 {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Password.Iterations)) validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Iterations))
}
} }
} }
func validateFileAuthenticationBackendSHA512(config *schema.FileAuthenticationBackendConfiguration) { func validateFileAuthenticationBackendSHA512(config *schema.PasswordConfiguration) {
// Iterations (time). // Iterations (time).
if config.Password.Iterations == 0 { if config.Iterations == 0 {
config.Password.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations config.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
} }
} }
func validateFileAuthenticationBackendArgon2id(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) { func validateFileAuthenticationBackendArgon2id(config *schema.PasswordConfiguration, validator *schema.StructValidator) {
// Iterations (time). // Iterations (time).
if config.Password.Iterations == 0 { if config.Iterations == 0 {
config.Password.Iterations = schema.DefaultPasswordConfiguration.Iterations config.Iterations = schema.DefaultPasswordConfiguration.Iterations
} }
// Parallelism. // Parallelism.
if config.Password.Parallelism == 0 { if config.Parallelism == 0 {
config.Password.Parallelism = schema.DefaultPasswordConfiguration.Parallelism config.Parallelism = schema.DefaultPasswordConfiguration.Parallelism
} else if config.Password.Parallelism < 1 { } else if config.Parallelism < 1 {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidParallelism, config.Password.Parallelism)) validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidParallelism, config.Parallelism))
} }
// Memory. // Memory.
if config.Password.Memory == 0 { if config.Memory == 0 {
config.Password.Memory = schema.DefaultPasswordConfiguration.Memory config.Memory = schema.DefaultPasswordConfiguration.Memory
} else if config.Password.Memory < config.Password.Parallelism*8 { } else if config.Memory < config.Parallelism*8 {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidMemory, config.Password.Parallelism, config.Password.Parallelism*8, config.Password.Memory)) validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidMemory, config.Parallelism, config.Parallelism*8, config.Memory))
} }
// Key Length. // Key Length.
if config.Password.KeyLength == 0 { if config.KeyLength == 0 {
config.Password.KeyLength = schema.DefaultPasswordConfiguration.KeyLength config.KeyLength = schema.DefaultPasswordConfiguration.KeyLength
} else if config.Password.KeyLength < 16 { } else if config.KeyLength < 16 {
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength, config.Password.KeyLength)) validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordArgon2idInvalidKeyLength, config.KeyLength))
} }
} }

View File

@ -85,7 +85,7 @@ func (s *CLISuite) TestShouldFailValidateConfig() {
func (s *CLISuite) TestShouldHashPasswordArgon2id() { func (s *CLISuite) TestShouldHashPasswordArgon2id() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "hash-password", "test", "-m", "32", "-s", "test1234"}) 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().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() { func (s *CLISuite) TestShouldHashPasswordSHA512() {