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.pull/3460/head
parent
0d3ee8e730
commit
2037a0ee4f
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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{}{
|
||||||
val := schema.NewStructValidator()
|
"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 sha512 {
|
||||||
if err != nil {
|
mapDefaults["authentication_backend.file.password.algorithm"] = schema.DefaultPasswordSHA512Configuration.Algorithm
|
||||||
logger.Fatalf("Error occurred loading configuration: %v", err)
|
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 {
|
mapCLI := map[string]string{
|
||||||
sha512 = config.AuthenticationBackend.File.Password.Algorithm == "sha512"
|
"iterations": "authentication_backend.file.password.iterations",
|
||||||
iterations = config.AuthenticationBackend.File.Password.Iterations
|
"key-length": "authentication_backend.file.password.key_length",
|
||||||
keyLength = config.AuthenticationBackend.File.Password.KeyLength
|
"salt-length": "authentication_backend.file.password.salt_length",
|
||||||
saltLength = config.AuthenticationBackend.File.Password.SaltLength
|
"parallelism": "authentication_backend.file.password.parallelism",
|
||||||
memory = config.AuthenticationBackend.File.Password.Memory
|
"memory": "authentication_backend.file.password.memory",
|
||||||
parallelism = config.AuthenticationBackend.File.Password.Parallelism
|
}
|
||||||
}
|
|
||||||
|
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 (
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
return fmt.Errorf("errors occurred validating the password configuration: %w", err)
|
||||||
} else {
|
|
||||||
algorithm = authentication.HashingAlgorithmArgon2id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
// Salt Length.
|
ValidatePasswordConfiguration(config.Password, validator)
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateFileAuthenticationBackendSHA512(config *schema.FileAuthenticationBackendConfiguration) {
|
// ValidatePasswordConfiguration validates the file auth backend password configuration.
|
||||||
// Iterations (time).
|
func ValidatePasswordConfiguration(config *schema.PasswordConfiguration, validator *schema.StructValidator) {
|
||||||
if config.Password.Iterations == 0 {
|
// Salt Length.
|
||||||
config.Password.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
|
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).
|
// Iterations (time).
|
||||||
if config.Password.Iterations == 0 {
|
if config.Iterations == 0 {
|
||||||
config.Password.Iterations = schema.DefaultPasswordConfiguration.Iterations
|
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.
|
// 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue