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
|
||||
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,
|
|||
<div markdown="1">
|
||||
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
|
||||
<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
|
||||
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] -- <password>
|
||||
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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] -- <password>",
|
||||
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 {
|
||||
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()
|
||||
|
||||
_, config, err := configuration.Load(val, configuration.NewDefaultSources(configs, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)...)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
algorithm = authentication.HashingAlgorithmSHA512
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -53,63 +53,68 @@ func validateFileAuthenticationBackend(config *schema.FileAuthenticationBackendC
|
|||
if config.Password == nil {
|
||||
config.Password = &schema.DefaultPasswordConfiguration
|
||||
} else {
|
||||
ValidatePasswordConfiguration(config.Password, validator)
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatePasswordConfiguration validates the file auth backend password configuration.
|
||||
func ValidatePasswordConfiguration(config *schema.PasswordConfiguration, validator *schema.StructValidator) {
|
||||
// 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))
|
||||
case config.SaltLength == 0:
|
||||
config.SaltLength = schema.DefaultPasswordConfiguration.SaltLength
|
||||
case config.SaltLength < 8:
|
||||
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordSaltLength, config.SaltLength))
|
||||
}
|
||||
|
||||
switch config.Password.Algorithm {
|
||||
switch config.Algorithm {
|
||||
case "":
|
||||
config.Password.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
|
||||
config.Algorithm = schema.DefaultPasswordConfiguration.Algorithm
|
||||
fallthrough
|
||||
case hashArgon2id:
|
||||
validateFileAuthenticationBackendArgon2id(config, validator)
|
||||
case hashSHA512:
|
||||
validateFileAuthenticationBackendSHA512(config)
|
||||
default:
|
||||
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Password.Algorithm))
|
||||
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm))
|
||||
}
|
||||
|
||||
if config.Password.Iterations < 1 {
|
||||
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Password.Iterations))
|
||||
}
|
||||
if config.Iterations < 1 {
|
||||
validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidIterations, config.Iterations))
|
||||
}
|
||||
}
|
||||
|
||||
func validateFileAuthenticationBackendSHA512(config *schema.FileAuthenticationBackendConfiguration) {
|
||||
func validateFileAuthenticationBackendSHA512(config *schema.PasswordConfiguration) {
|
||||
// Iterations (time).
|
||||
if config.Password.Iterations == 0 {
|
||||
config.Password.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
|
||||
if config.Iterations == 0 {
|
||||
config.Iterations = schema.DefaultPasswordSHA512Configuration.Iterations
|
||||
}
|
||||
}
|
||||
func validateFileAuthenticationBackendArgon2id(config *schema.FileAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||
func validateFileAuthenticationBackendArgon2id(config *schema.PasswordConfiguration, validator *schema.StructValidator) {
|
||||
// Iterations (time).
|
||||
if config.Password.Iterations == 0 {
|
||||
config.Password.Iterations = schema.DefaultPasswordConfiguration.Iterations
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue