refactor(commands): command context (#4539)

This moves a lot of machinery for commands into a context.Context with other struct values. This allows for PreRunE's to reliably load the configuration and avoids use of global vars.
pull/4614/head^2
James Elliott 2022-12-22 11:21:29 +11:00 committed by GitHub
parent 0732e60e16
commit e3e31e3cbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1566 additions and 1416 deletions

View File

@ -26,6 +26,7 @@ body:
description: What version(s) of Authelia can you reproduce this bug on? description: What version(s) of Authelia can you reproduce this bug on?
multiple: true multiple: true
options: options:
- v4.37.5
- v4.37.4 - v4.37.4
- v4.37.3 - v4.37.3
- v4.37.2 - v4.37.2

View File

@ -232,9 +232,9 @@ Example:
```yaml ```yaml
{{ if contains (env "DOMAIN") "https://" }} {{ if contains (env "DOMAIN") "https://" }}
default_redirection_url: '{{ env "DOMAIN" }}' default_redirection_url: '{{ env "DOMAIN" }}'
{{ else}} {{ else }}
default_redirection_url: 'https://{{ env "DOMAIN" }}' default_redirection_url: 'https://{{ env "DOMAIN" }}'
{{ end }} {{ end }}
``` ```
##### hasPrefix ##### hasPrefix
@ -246,7 +246,7 @@ Example:
```yaml ```yaml
{{ if hasPrefix (env "DOMAIN") "https://" }} {{ if hasPrefix (env "DOMAIN") "https://" }}
default_redirection_url: '{{ env "DOMAIN" }}' default_redirection_url: '{{ env "DOMAIN" }}'
{{ else}} {{ else }}
default_redirection_url: 'https://{{ env "DOMAIN" }}' default_redirection_url: 'https://{{ env "DOMAIN" }}'
{{ end }} {{ end }}
``` ```
@ -260,7 +260,7 @@ Example:
```yaml ```yaml
{{ if hasSuffix (env "DOMAIN") "/" }} {{ if hasSuffix (env "DOMAIN") "/" }}
default_redirection_url: 'https://{{ env "DOMAIN" }}' default_redirection_url: 'https://{{ env "DOMAIN" }}'
{{ else}} {{ else }}
default_redirection_url: 'https://{{ env "DOMAIN" }}/' default_redirection_url: 'https://{{ env "DOMAIN" }}/'
{{ end }} {{ end }}
``` ```

View File

@ -1,7 +1,7 @@
--- ---
title: "Versioning Policy" title: "Versioning Policy"
description: "The Authelia Versioning Policy which is important reading for administrators" description: "The Authelia Versioning Policy which is important reading for administrators"
date: 2022-12-21T16:46:42+11:00 date: 2022-12-21T20:48:14+11:00
draft: false draft: false
images: [] images: []
aliases: aliases:

View File

@ -41,8 +41,8 @@ authelia --config /etc/authelia/config/
### Options ### Options
``` ```
-c, --config strings configuration files to load -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings Applies filters in order to the configuration file before the YAML parser. Options are 'template', 'expand-env' --config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
-h, --help help for authelia -h, --help help for authelia
``` ```
@ -51,7 +51,6 @@ authelia --config /etc/authelia/config/
* [authelia access-control](authelia_access-control.md) - Helpers for the access control system * [authelia access-control](authelia_access-control.md) - Helpers for the access control system
* [authelia build-info](authelia_build-info.md) - Show the build information of Authelia * [authelia build-info](authelia_build-info.md) - Show the build information of Authelia
* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations * [authelia crypto](authelia_crypto.md) - Perform cryptographic operations
* [authelia hash-password](authelia_hash-password.md) - Hash a password to be used in file-based users database
* [authelia storage](authelia_storage.md) - Manage the Authelia storage * [authelia storage](authelia_storage.md) - Manage the Authelia storage
* [authelia validate-config](authelia_validate-config.md) - Check a configuration against the internal configuration validation mechanisms * [authelia validate-config](authelia_validate-config.md) - Check a configuration against the internal configuration validation mechanisms

View File

@ -32,6 +32,13 @@ authelia access-control --help
-h, --help help for access-control -h, --help help for access-control
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown) * [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown)

View File

@ -53,7 +53,6 @@ authelia access-control check-policy --config config.yml --url https://example.c
### Options ### Options
``` ```
-c, --config strings configuration files to load (default [configuration.yml])
--groups strings the groups of the subject --groups strings the groups of the subject
-h, --help help for check-policy -h, --help help for check-policy
--ip string the ip of the subject --ip string the ip of the subject
@ -63,6 +62,13 @@ authelia access-control check-policy --config config.yml --url https://example.c
--verbose enables verbose output --verbose enables verbose output
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia access-control](authelia_access-control.md) - Helpers for the access control system * [authelia access-control](authelia_access-control.md) - Helpers for the access control system

View File

@ -45,6 +45,13 @@ authelia build-info
-h, --help help for build-info -h, --help help for build-info
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown) * [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown)

View File

@ -34,6 +34,13 @@ authelia crypto --help
-h, --help help for crypto -h, --help help for crypto
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown) * [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown)

View File

@ -34,6 +34,13 @@ authelia crypto certificate --help
-h, --help help for certificate -h, --help help for certificate
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations * [authelia crypto](authelia_crypto.md) - Perform cryptographic operations

View File

@ -34,6 +34,13 @@ authelia crypto certificate ecdsa --help
-h, --help help for ecdsa -h, --help help for ecdsa
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate](authelia_crypto_certificate.md) - Perform certificate cryptographic operations * [authelia crypto certificate](authelia_crypto_certificate.md) - Perform certificate cryptographic operations

View File

@ -36,7 +36,7 @@ authelia crypto certificate ecdsa generate --help
``` ```
--ca create the certificate as a certificate authority certificate --ca create the certificate as a certificate authority certificate
-c, --common-name string certificate common name -n, --common-name string certificate common name
--country strings certificate country --country strings certificate country
-b, --curve string Sets the elliptic curve which can be P224, P256, P384, or P521 (default "P256") -b, --curve string Sets the elliptic curve which can be P224, P256, P384, or P521 (default "P256")
-d, --directory string directory where the generated keys, certificates, etc will be stored -d, --directory string directory where the generated keys, certificates, etc will be stored
@ -59,6 +59,13 @@ authelia crypto certificate ecdsa generate --help
-s, --street-address strings certificate street address -s, --street-address strings certificate street address
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate ecdsa](authelia_crypto_certificate_ecdsa.md) - Perform ECDSA certificate cryptographic operations * [authelia crypto certificate ecdsa](authelia_crypto_certificate_ecdsa.md) - Perform ECDSA certificate cryptographic operations

View File

@ -35,7 +35,7 @@ authelia crypto certificate ecdsa request --help
### Options ### Options
``` ```
-c, --common-name string certificate common name -n, --common-name string certificate common name
--country strings certificate country --country strings certificate country
-b, --curve string Sets the elliptic curve which can be P224, P256, P384, or P521 (default "P256") -b, --curve string Sets the elliptic curve which can be P224, P256, P384, or P521 (default "P256")
-d, --directory string directory where the generated keys, certificates, etc will be stored -d, --directory string directory where the generated keys, certificates, etc will be stored
@ -54,6 +54,13 @@ authelia crypto certificate ecdsa request --help
-s, --street-address strings certificate street address -s, --street-address strings certificate street address
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate ecdsa](authelia_crypto_certificate_ecdsa.md) - Perform ECDSA certificate cryptographic operations * [authelia crypto certificate ecdsa](authelia_crypto_certificate_ecdsa.md) - Perform ECDSA certificate cryptographic operations

View File

@ -34,6 +34,13 @@ authelia crypto certificate ed25519 --help
-h, --help help for ed25519 -h, --help help for ed25519
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate](authelia_crypto_certificate.md) - Perform certificate cryptographic operations * [authelia crypto certificate](authelia_crypto_certificate.md) - Perform certificate cryptographic operations

View File

@ -36,7 +36,7 @@ authelia crypto certificate ed25519 request --help
``` ```
--ca create the certificate as a certificate authority certificate --ca create the certificate as a certificate authority certificate
-c, --common-name string certificate common name -n, --common-name string certificate common name
--country strings certificate country --country strings certificate country
-d, --directory string directory where the generated keys, certificates, etc will be stored -d, --directory string directory where the generated keys, certificates, etc will be stored
--duration duration duration of time the certificate is valid for (default 8760h0m0s) --duration duration duration of time the certificate is valid for (default 8760h0m0s)
@ -58,6 +58,13 @@ authelia crypto certificate ed25519 request --help
-s, --street-address strings certificate street address -s, --street-address strings certificate street address
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate ed25519](authelia_crypto_certificate_ed25519.md) - Perform Ed25519 certificate cryptographic operations * [authelia crypto certificate ed25519](authelia_crypto_certificate_ed25519.md) - Perform Ed25519 certificate cryptographic operations

View File

@ -35,7 +35,7 @@ authelia crypto certificate ed25519 request --help
### Options ### Options
``` ```
-c, --common-name string certificate common name -n, --common-name string certificate common name
--country strings certificate country --country strings certificate country
-d, --directory string directory where the generated keys, certificates, etc will be stored -d, --directory string directory where the generated keys, certificates, etc will be stored
--duration duration duration of time the certificate is valid for (default 8760h0m0s) --duration duration duration of time the certificate is valid for (default 8760h0m0s)
@ -53,6 +53,13 @@ authelia crypto certificate ed25519 request --help
-s, --street-address strings certificate street address -s, --street-address strings certificate street address
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate ed25519](authelia_crypto_certificate_ed25519.md) - Perform Ed25519 certificate cryptographic operations * [authelia crypto certificate ed25519](authelia_crypto_certificate_ed25519.md) - Perform Ed25519 certificate cryptographic operations

View File

@ -34,6 +34,13 @@ authelia crypto certificate rsa --help
-h, --help help for rsa -h, --help help for rsa
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate](authelia_crypto_certificate.md) - Perform certificate cryptographic operations * [authelia crypto certificate](authelia_crypto_certificate.md) - Perform certificate cryptographic operations

View File

@ -37,7 +37,7 @@ authelia crypto certificate rsa generate --help
``` ```
-b, --bits int number of RSA bits for the certificate (default 2048) -b, --bits int number of RSA bits for the certificate (default 2048)
--ca create the certificate as a certificate authority certificate --ca create the certificate as a certificate authority certificate
-c, --common-name string certificate common name -n, --common-name string certificate common name
--country strings certificate country --country strings certificate country
-d, --directory string directory where the generated keys, certificates, etc will be stored -d, --directory string directory where the generated keys, certificates, etc will be stored
--duration duration duration of time the certificate is valid for (default 8760h0m0s) --duration duration duration of time the certificate is valid for (default 8760h0m0s)
@ -59,6 +59,13 @@ authelia crypto certificate rsa generate --help
-s, --street-address strings certificate street address -s, --street-address strings certificate street address
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate rsa](authelia_crypto_certificate_rsa.md) - Perform RSA certificate cryptographic operations * [authelia crypto certificate rsa](authelia_crypto_certificate_rsa.md) - Perform RSA certificate cryptographic operations

View File

@ -36,7 +36,7 @@ authelia crypto certificate rsa request --help
``` ```
-b, --bits int number of RSA bits for the certificate (default 2048) -b, --bits int number of RSA bits for the certificate (default 2048)
-c, --common-name string certificate common name -n, --common-name string certificate common name
--country strings certificate country --country strings certificate country
-d, --directory string directory where the generated keys, certificates, etc will be stored -d, --directory string directory where the generated keys, certificates, etc will be stored
--duration duration duration of time the certificate is valid for (default 8760h0m0s) --duration duration duration of time the certificate is valid for (default 8760h0m0s)
@ -54,6 +54,13 @@ authelia crypto certificate rsa request --help
-s, --street-address strings certificate street address -s, --street-address strings certificate street address
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto certificate rsa](authelia_crypto_certificate_rsa.md) - Perform RSA certificate cryptographic operations * [authelia crypto certificate rsa](authelia_crypto_certificate_rsa.md) - Perform RSA certificate cryptographic operations

View File

@ -34,6 +34,13 @@ authelia crypto hash --help
-h, --help help for hash -h, --help help for hash
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations * [authelia crypto](authelia_crypto.md) - Perform cryptographic operations

View File

@ -37,7 +37,6 @@ authelia crypto hash generate --help
### Options ### Options
``` ```
-c, --config strings configuration files to load (default [configuration.yml])
-h, --help help for generate -h, --help help for generate
--no-confirm skip the password confirmation prompt --no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt --password string manually supply the password rather than using the terminal prompt
@ -47,6 +46,13 @@ authelia crypto hash generate --help
--random.length int sets the character length for the random string (default 72) --random.length int sets the character length for the random string (default 72)
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto hash](authelia_crypto_hash.md) - Perform cryptographic hash operations * [authelia crypto hash](authelia_crypto_hash.md) - Perform cryptographic hash operations

View File

@ -48,13 +48,14 @@ authelia crypto hash generate argon2 --help
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt --config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--password string manually supply the password rather than using the terminal prompt --no-confirm skip the password confirmation prompt
--random uses a randomly generated password --password string manually supply the password rather than using the terminal prompt
--random.characters string sets the explicit characters for the random string --random uses a randomly generated password
--random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric") --random.characters string sets the explicit characters for the random string
--random.length int sets the character length for the random string (default 72) --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
--random.length int sets the character length for the random string (default 72)
``` ```
### SEE ALSO ### SEE ALSO

View File

@ -43,13 +43,14 @@ authelia crypto hash generate bcrypt --help
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt --config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--password string manually supply the password rather than using the terminal prompt --no-confirm skip the password confirmation prompt
--random uses a randomly generated password --password string manually supply the password rather than using the terminal prompt
--random.characters string sets the explicit characters for the random string --random uses a randomly generated password
--random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric") --random.characters string sets the explicit characters for the random string
--random.length int sets the character length for the random string (default 72) --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
--random.length int sets the character length for the random string (default 72)
``` ```
### SEE ALSO ### SEE ALSO

View File

@ -44,13 +44,14 @@ authelia crypto hash generate pbkdf2 --help
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt --config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--password string manually supply the password rather than using the terminal prompt --no-confirm skip the password confirmation prompt
--random uses a randomly generated password --password string manually supply the password rather than using the terminal prompt
--random.characters string sets the explicit characters for the random string --random uses a randomly generated password
--random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric") --random.characters string sets the explicit characters for the random string
--random.length int sets the character length for the random string (default 72) --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
--random.length int sets the character length for the random string (default 72)
``` ```
### SEE ALSO ### SEE ALSO

View File

@ -46,13 +46,14 @@ authelia crypto hash generate scrypt --help
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt --config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--password string manually supply the password rather than using the terminal prompt --no-confirm skip the password confirmation prompt
--random uses a randomly generated password --password string manually supply the password rather than using the terminal prompt
--random.characters string sets the explicit characters for the random string --random uses a randomly generated password
--random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric") --random.characters string sets the explicit characters for the random string
--random.length int sets the character length for the random string (default 72) --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
--random.length int sets the character length for the random string (default 72)
``` ```
### SEE ALSO ### SEE ALSO

View File

@ -44,13 +44,14 @@ authelia crypto hash generate sha2crypt --help
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt --config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--password string manually supply the password rather than using the terminal prompt --no-confirm skip the password confirmation prompt
--random uses a randomly generated password --password string manually supply the password rather than using the terminal prompt
--random.characters string sets the explicit characters for the random string --random uses a randomly generated password
--random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric") --random.characters string sets the explicit characters for the random string
--random.length int sets the character length for the random string (default 72) --random.charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
--random.length int sets the character length for the random string (default 72)
``` ```
### SEE ALSO ### SEE ALSO

View File

@ -40,6 +40,13 @@ authelia crypto hash validate '$5$rounds=500000$WFjMpdCQxIkbNl0k$M0qZaZoK8Gwdh8C
--password string manually supply the password rather than using the terminal prompt --password string manually supply the password rather than using the terminal prompt
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto hash](authelia_crypto_hash.md) - Perform cryptographic hash operations * [authelia crypto hash](authelia_crypto_hash.md) - Perform cryptographic hash operations

View File

@ -34,6 +34,13 @@ authelia crypto pair --help
-h, --help help for pair -h, --help help for pair
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations * [authelia crypto](authelia_crypto.md) - Perform cryptographic operations

View File

@ -38,6 +38,13 @@ authelia crypto pair ecdsa --help
-h, --help help for ecdsa -h, --help help for ecdsa
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto pair](authelia_crypto_pair.md) - Perform key pair cryptographic operations * [authelia crypto pair](authelia_crypto_pair.md) - Perform key pair cryptographic operations

View File

@ -43,6 +43,13 @@ authelia crypto pair ecdsa generate --help
--pkcs8 force PKCS #8 ASN.1 format --pkcs8 force PKCS #8 ASN.1 format
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto pair ecdsa](authelia_crypto_pair_ecdsa.md) - Perform ECDSA key pair cryptographic operations * [authelia crypto pair ecdsa](authelia_crypto_pair_ecdsa.md) - Perform ECDSA key pair cryptographic operations

View File

@ -38,6 +38,13 @@ authelia crypto pair ed25519 --help
-h, --help help for ed25519 -h, --help help for ed25519
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto pair](authelia_crypto_pair.md) - Perform key pair cryptographic operations * [authelia crypto pair](authelia_crypto_pair.md) - Perform key pair cryptographic operations

View File

@ -42,6 +42,13 @@ authelia crypto pair ed25519 generate --help
--pkcs8 force PKCS #8 ASN.1 format --pkcs8 force PKCS #8 ASN.1 format
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto pair ed25519](authelia_crypto_pair_ed25519.md) - Perform Ed25519 key pair cryptographic operations * [authelia crypto pair ed25519](authelia_crypto_pair_ed25519.md) - Perform Ed25519 key pair cryptographic operations

View File

@ -38,6 +38,13 @@ authelia crypto pair rsa --help
-h, --help help for rsa -h, --help help for rsa
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto pair](authelia_crypto_pair.md) - Perform key pair cryptographic operations * [authelia crypto pair](authelia_crypto_pair.md) - Perform key pair cryptographic operations

View File

@ -43,6 +43,13 @@ authelia crypto pair rsa generate --help
--pkcs8 force PKCS #8 ASN.1 format --pkcs8 force PKCS #8 ASN.1 format
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto pair rsa](authelia_crypto_pair_rsa.md) - Perform RSA key pair cryptographic operations * [authelia crypto pair rsa](authelia_crypto_pair_rsa.md) - Perform RSA key pair cryptographic operations

View File

@ -44,11 +44,18 @@ authelia crypto rand --characters 0123456789ABCDEF
``` ```
--characters string sets the explicit characters for the random string --characters string sets the explicit characters for the random string
-c, --charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric") -x, --charset string sets the charset for the random password, options are 'ascii', 'alphanumeric', 'alphabetic', 'numeric', 'numeric-hex', and 'rfc3986' (default "alphanumeric")
-h, --help help for rand -h, --help help for rand
-n, --length int sets the character length for the random string (default 72) -n, --length int sets the character length for the random string (default 72)
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations * [authelia crypto](authelia_crypto.md) - Perform cryptographic operations

View File

@ -1,55 +0,0 @@
---
title: "authelia hash-password"
description: "Reference for the authelia hash-password command."
lead: ""
date: 2022-06-15T17:51:47+10:00
draft: false
images: []
menu:
reference:
parent: "cli-authelia"
weight: 905
toc: true
---
## authelia hash-password
Hash a password to be used in file-based users database
### Synopsis
Hash a password to be used in file-based users database.
```
authelia hash-password [flags] -- [password]
```
### Examples
```
authelia hash-password -- 'mypass'
authelia hash-password --sha512 -- 'mypass'
authelia hash-password --iterations=4 -- 'mypass'
authelia hash-password --memory=128 -- 'mypass'
authelia hash-password --parallelism=1 -- 'mypass'
authelia hash-password --key-length=64 -- 'mypass'
```
### Options
```
-c, --config strings configuration files to load (default [configuration.yml])
-h, --help help for hash-password
-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 65536)
--no-confirm skip the password confirmation prompt
-p, --parallelism int [argon2id] set the parallelism param (default 4)
-l, --salt-length int set the auto-generated salt length (default 16)
-z, --sha512 use sha512 as the algorithm (changes iterations to 50000, change with -i)
```
### SEE ALSO
* [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown)

View File

@ -33,7 +33,6 @@ authelia storage --help
### Options ### Options
``` ```
-c, --config strings configuration files to load (default [configuration.yml])
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
-h, --help help for storage -h, --help help for storage
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
@ -54,6 +53,13 @@ authelia storage --help
--sqlite.path string the SQLite database path --sqlite.path string the SQLite database path
``` ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
```
### SEE ALSO ### SEE ALSO
* [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown) * [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown)

View File

@ -38,6 +38,7 @@ authelia storage encryption --help
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -44,6 +44,7 @@ authelia storage encryption change-key --encryption-key b3453fde-ecc2-4a1f-9422-
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -46,6 +46,7 @@ authelia storage encryption check --verbose --encryption-key b3453fde-ecc2-4a1f-
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -38,6 +38,7 @@ authelia storage migrate --help
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -47,6 +47,7 @@ authelia storage migrate down --target 20 --encryption-key b3453fde-ecc2-4a1f-94
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -44,6 +44,7 @@ authelia storage migrate history --encryption-key b3453fde-ecc2-4a1f-9422-2707dd
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -45,6 +45,7 @@ authelia storage migrate list-down --encryption-key b3453fde-ecc2-4a1f-9422-2707
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -45,6 +45,7 @@ authelia storage migrate list-up --encryption-key b3453fde-ecc2-4a1f-9422-2707dd
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -47,6 +47,7 @@ authelia storage migrate up --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed49
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -44,6 +44,7 @@ authelia storage schema-info --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed4
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -38,6 +38,7 @@ authelia storage user --help
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -38,6 +38,7 @@ authelia storage user identifiers --help
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -47,6 +47,7 @@ authelia storage user identifiers add john --identifier f0919359-9d15-4e15-bcba-
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -46,6 +46,7 @@ authelia storage user identifiers export --file export.yaml --encryption-key b34
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -49,6 +49,7 @@ authelia storage user identifiers generate --users john,mary --services openid -
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -49,6 +49,7 @@ authelia storage user identifiers import --file export.yaml --encryption-key b34
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -38,6 +38,7 @@ authelia storage user totp --help
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -44,6 +44,7 @@ authelia storage user totp delete john --encryption-key b3453fde-ecc2-4a1f-9422-
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -47,6 +47,7 @@ authelia storage user totp export --format png --dir ./totp-qr --encryption-key
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -56,6 +56,7 @@ authelia storage user totp generate john --algorithm SHA512 --config config.yml
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -38,6 +38,7 @@ authelia storage user webauthn --help
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -53,6 +53,7 @@ authelia storage user webauthn delete --kid abc123 --encryption-key b3453fde-ecc
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -47,6 +47,7 @@ authelia storage user webauthn list john --encryption-key b3453fde-ecc2-4a1f-942
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
--encryption-key string the storage encryption key to use --encryption-key string the storage encryption key to use
--mysql.database string the MySQL database name (default "authelia") --mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname --mysql.host string the MySQL hostname

View File

@ -37,8 +37,14 @@ authelia validate-config --config config.yml
### Options ### Options
``` ```
-c, --config strings configuration files to load (default [configuration.yml]) -h, --help help for validate-config
-h, --help help for validate-config ```
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--config.experimental.filters strings applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'
``` ```
### SEE ALSO ### SEE ALSO

View File

@ -8,5 +8,5 @@
{{ else }} {{ else }}
{{ errorf "No valid text variable or Inner content given"}} {{ errorf "No valid text variable or Inner content given"}}
{{ end }} {{ end }}
{{ end}} {{ end }}
</div> </div>

View File

@ -10,12 +10,10 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator" "github.com/authelia/authelia/v4/internal/configuration/validator"
) )
func newAccessControlCommand() (cmd *cobra.Command) { func newAccessControlCommand(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "access-control", Use: "access-control",
Short: cmdAutheliaAccessControlShort, Short: cmdAutheliaAccessControlShort,
@ -26,25 +24,26 @@ func newAccessControlCommand() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newAccessControlCheckCommand(), newAccessControlCheckCommand(ctx),
) )
return cmd return cmd
} }
func newAccessControlCheckCommand() (cmd *cobra.Command) { func newAccessControlCheckCommand(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "check-policy", Use: "check-policy",
Short: cmdAutheliaAccessControlCheckPolicyShort, Short: cmdAutheliaAccessControlCheckPolicyShort,
Long: cmdAutheliaAccessControlCheckPolicyLong, Long: cmdAutheliaAccessControlCheckPolicyLong,
Example: cmdAutheliaAccessControlCheckPolicyExample, Example: cmdAutheliaAccessControlCheckPolicyExample,
RunE: accessControlCheckRunE, PreRunE: ctx.ChainRunE(
ctx.ConfigLoadRunE,
),
RunE: ctx.AccessControlCheckRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
cmdWithConfigFlags(cmd, false, []string{"configuration.yml"})
cmd.Flags().String("url", "", "the url of the object") cmd.Flags().String("url", "", "the url of the object")
cmd.Flags().String("method", "GET", "the HTTP method of the object") cmd.Flags().String("method", "GET", "the HTTP method of the object")
cmd.Flags().String("username", "", "the username of the subject") cmd.Flags().String("username", "", "the username of the subject")
@ -55,36 +54,14 @@ func newAccessControlCheckCommand() (cmd *cobra.Command) {
return cmd return cmd
} }
func accessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) { func (ctx *CmdCtx) AccessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
configs, err := cmd.Flags().GetStringSlice(cmdFlagNameConfig) validator.ValidateAccessControl(ctx.config, ctx.cconfig.validator)
if err != nil {
return err
}
sources := make([]configuration.Source, len(configs)+2) if ctx.cconfig.validator.HasErrors() || ctx.cconfig.validator.HasWarnings() {
for i, path := range configs {
sources[i] = configuration.NewYAMLFileSource(path)
}
sources[0+len(configs)] = configuration.NewEnvironmentSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)
sources[1+len(configs)] = configuration.NewSecretsSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)
val := schema.NewStructValidator()
accessControlConfig := &schema.Configuration{}
if _, err = configuration.LoadAdvanced(val, "access_control", &accessControlConfig.AccessControl, sources...); err != nil {
return err
}
validator.ValidateAccessControl(accessControlConfig, val)
if val.HasErrors() || val.HasWarnings() {
return errors.New("your configuration has errors") return errors.New("your configuration has errors")
} }
authorizer := authorization.NewAuthorizer(accessControlConfig) authorizer := authorization.NewAuthorizer(ctx.config)
subject, object, err := getSubjectAndObjectFromFlags(cmd) subject, object, err := getSubjectAndObjectFromFlags(cmd)
if err != nil { if err != nil {
@ -94,7 +71,7 @@ func accessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
results := authorizer.GetRuleMatchResults(subject, object) results := authorizer.GetRuleMatchResults(subject, object)
if len(results) == 0 { if len(results) == 0 {
fmt.Printf("\nThe default policy '%s' will be applied to ALL requests as no rules are configured.\n\n", accessControlConfig.AccessControl.DefaultPolicy) fmt.Printf("\nThe default policy '%s' will be applied to ALL requests as no rules are configured.\n\n", ctx.config.AccessControl.DefaultPolicy)
return nil return nil
} }
@ -104,7 +81,7 @@ func accessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
return err return err
} }
accessControlCheckWriteOutput(object, subject, results, accessControlConfig.AccessControl.DefaultPolicy, verbose) accessControlCheckWriteOutput(object, subject, results, ctx.config.AccessControl.DefaultPolicy, verbose)
return nil return nil
} }

View File

@ -9,13 +9,13 @@ import (
"github.com/authelia/authelia/v4/internal/utils" "github.com/authelia/authelia/v4/internal/utils"
) )
func newBuildInfoCmd() (cmd *cobra.Command) { func newBuildInfoCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "build-info", Use: "build-info",
Short: cmdAutheliaBuildInfoShort, Short: cmdAutheliaBuildInfoShort,
Long: cmdAutheliaBuildInfoLong, Long: cmdAutheliaBuildInfoLong,
Example: cmdAutheliaBuildInfoExample, Example: cmdAutheliaBuildInfoExample,
RunE: cmdBuildInfoRunE, RunE: ctx.BuildInfoRunE,
Args: cobra.NoArgs, Args: cobra.NoArgs,
DisableAutoGenTag: true, DisableAutoGenTag: true,
@ -24,7 +24,8 @@ func newBuildInfoCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func cmdBuildInfoRunE(_ *cobra.Command, _ []string) (err error) { // BuildInfoRunE is the RunE for the authelia build-info command.
func (ctx *CmdCtx) BuildInfoRunE(_ *cobra.Command, _ []string) (err error) {
_, err = fmt.Printf(fmtAutheliaBuild, utils.BuildTag, utils.BuildState, utils.BuildBranch, utils.BuildCommit, _, err = fmt.Printf(fmtAutheliaBuild, utils.BuildTag, utils.BuildState, utils.BuildBranch, utils.BuildCommit,
utils.BuildNumber, runtime.GOOS, runtime.GOARCH, utils.BuildDate, utils.BuildExtra) utils.BuildNumber, runtime.GOOS, runtime.GOARCH, utils.BuildDate, utils.BuildExtra)

View File

@ -1,113 +0,0 @@
package commands
import (
"os"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator"
"github.com/authelia/authelia/v4/internal/logging"
)
// cmdWithConfigFlags is used for commands which require access to the configuration to add the flag to the command.
func cmdWithConfigFlags(cmd *cobra.Command, persistent bool, configs []string) {
if persistent {
cmd.PersistentFlags().StringSliceP(cmdFlagNameConfig, "c", configs, "configuration files to load")
} else {
cmd.Flags().StringSliceP(cmdFlagNameConfig, "c", configs, "configuration files to load")
}
}
var config *schema.Configuration
func newCmdWithConfigPreRun(ensureConfigExists, validateKeys, validateConfiguration bool) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, _ []string) {
var (
logger *logrus.Logger
err error
configs, filterNames []string
filters []configuration.FileFilter
)
logger = logging.Logger()
if configs, err = cmd.Flags().GetStringSlice(cmdFlagNameConfig); err != nil {
logger.Fatalf("Error reading flags: %v", err)
}
if filterNames, err = cmd.Flags().GetStringSlice(cmdFlagNameConfigExpFilters); err != nil {
logger.Fatalf("Error reading flags: %v", err)
}
if filters, err = configuration.NewFileFilters(filterNames); err != nil {
logger.Fatalf("Error occurred loading configuration: flag '--%s' is invalid: %v", cmdFlagNameConfigExpFilters, err)
}
if ensureConfigExists && len(configs) == 1 {
created, err := configuration.EnsureConfigurationExists(configs[0])
if err != nil {
logger.Fatal(err)
}
if created {
logger.Warnf("Configuration did not exist so a default one has been generated at %s, you will need to configure this", configs[0])
os.Exit(0)
}
}
var (
val *schema.StructValidator
)
config, val, err = loadConfig(configs, validateKeys, validateConfiguration, filters...)
if err != nil {
logger.Fatalf("Error occurred loading configuration: %v", err)
}
warnings := val.Warnings()
if len(warnings) != 0 {
for _, warning := range warnings {
logger.Warnf("Configuration: %+v", warning)
}
}
errs := val.Errors()
if len(errs) != 0 {
for _, err := range errs {
logger.Errorf("Configuration: %+v", err)
}
logger.Fatalf("Can't continue due to the errors loading the configuration")
}
}
}
func loadConfig(configs []string, validateKeys, validateConfiguration bool, filters ...configuration.FileFilter) (c *schema.Configuration, val *schema.StructValidator, err error) {
var keys []string
val = schema.NewStructValidator()
if keys, c, err = configuration.Load(val,
configuration.NewDefaultSourcesFiltered(
configs,
filters,
configuration.DefaultEnvPrefix,
configuration.DefaultEnvDelimiter)...); err != nil {
return nil, nil, err
}
if validateKeys {
validator.ValidateKeys(keys, configuration.DefaultEnvPrefix, val)
}
if validateConfiguration {
validator.ValidateConfiguration(c, val)
}
return c, val, nil
}

View File

@ -463,18 +463,6 @@ This subcommand allows generating an %s key pair.`
cmdAutheliaCryptoPairECDSAGenerateExample = `authelia crypto pair ecdsa generate --help` cmdAutheliaCryptoPairECDSAGenerateExample = `authelia crypto pair ecdsa generate --help`
cmdAutheliaCryptoPairEd25519GenerateExample = `authelia crypto pair ed25519 generate --help` cmdAutheliaCryptoPairEd25519GenerateExample = `authelia crypto pair ed25519 generate --help`
cmdAutheliaHashPasswordShort = "Hash a password to be used in file-based users database"
cmdAutheliaHashPasswordLong = `Hash a password to be used in file-based users database.`
//nolint:gosec // This is an example.
cmdAutheliaHashPasswordExample = `authelia hash-password -- 'mypass'
authelia hash-password --sha512 -- 'mypass'
authelia hash-password --iterations=4 -- 'mypass'
authelia hash-password --memory=128 -- 'mypass'
authelia hash-password --parallelism=1 -- 'mypass'
authelia hash-password --key-length=64 -- 'mypass'`
) )
const ( const (
@ -544,8 +532,8 @@ const (
cmdFlagNameKeySize = "key-size" cmdFlagNameKeySize = "key-size"
cmdFlagNameSaltSize = "salt-size" cmdFlagNameSaltSize = "salt-size"
cmdFlagNameProfile = "profile" cmdFlagNameProfile = "profile"
cmdFlagNameSHA512 = "sha512"
cmdFlagNameConfig = "config" cmdFlagNameConfig = "config"
cmdFlagNameConfigExpFilters = "config.experimental.filters" cmdFlagNameConfigExpFilters = "config.experimental.filters"
@ -598,7 +586,6 @@ const (
) )
const ( const (
cmdUseHashPassword = "hash-password [flags] -- [password]"
cmdUseHash = "hash" cmdUseHash = "hash"
cmdUseHashArgon2 = "argon2" cmdUseHashArgon2 = "argon2"
cmdUseHashSHA2Crypt = "sha2crypt" cmdUseHashSHA2Crypt = "sha2crypt"
@ -627,7 +614,8 @@ const (
) )
var ( var (
errNoStorageProvider = errors.New("no storage provider configured") errStorageSchemaOutdated = errors.New("storage schema outdated")
errStorageSchemaIncompatible = errors.New("storage schema incompatible")
) )
const ( const (

View File

@ -0,0 +1,365 @@
package commands
import (
"crypto/x509"
"fmt"
"os"
"strings"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/net/context"
"golang.org/x/sync/errgroup"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator"
"github.com/authelia/authelia/v4/internal/logging"
"github.com/authelia/authelia/v4/internal/metrics"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/notification"
"github.com/authelia/authelia/v4/internal/ntp"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/regulation"
"github.com/authelia/authelia/v4/internal/session"
"github.com/authelia/authelia/v4/internal/templates"
"github.com/authelia/authelia/v4/internal/totp"
"github.com/authelia/authelia/v4/internal/utils"
)
// NewCmdCtx returns a new CmdCtx.
func NewCmdCtx() *CmdCtx {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
group, ctx := errgroup.WithContext(ctx)
return &CmdCtx{
Context: ctx,
cancel: cancel,
group: group,
log: logging.Logger(),
config: &schema.Configuration{},
}
}
// CmdCtx is a context.Context used for the root command.
type CmdCtx struct {
context.Context
cancel context.CancelFunc
group *errgroup.Group
log *logrus.Logger
config *schema.Configuration
providers middlewares.Providers
trusted *x509.CertPool
cconfig *CmdCtxConfig
}
// NewCmdCtxConfig returns a new CmdCtxConfig.
func NewCmdCtxConfig() *CmdCtxConfig {
return &CmdCtxConfig{
validator: schema.NewStructValidator(),
}
}
// CmdCtxConfig is the configuration for the CmdCtx.
type CmdCtxConfig struct {
defaults configuration.Source
sources []configuration.Source
keys []string
validator *schema.StructValidator
}
// CobraRunECmd describes a function that can be used as a *cobra.Command RunE, PreRunE, or PostRunE.
type CobraRunECmd func(cmd *cobra.Command, args []string) (err error)
// CheckSchemaVersion is a utility function which checks the schema version.
func (ctx *CmdCtx) CheckSchemaVersion() (err error) {
if ctx.providers.StorageProvider == nil {
return fmt.Errorf("storage not loaded")
}
var version, latest int
if version, err = ctx.providers.StorageProvider.SchemaVersion(ctx); err != nil {
return err
}
if latest, err = ctx.providers.StorageProvider.SchemaLatestVersion(); err != nil {
return err
}
switch {
case version > latest:
return fmt.Errorf("%w: version %d is not compatible with this version of the binary as the latest compatible version is %d", errStorageSchemaIncompatible, version, latest)
case version < latest:
return fmt.Errorf("%w: version %d is outdated please migrate to version %d in order to use this command or use an older binary", errStorageSchemaOutdated, version, latest)
default:
return nil
}
}
// LoadTrustedCertificates loads the trusted certificates into the CmdCtx.
func (ctx *CmdCtx) LoadTrustedCertificates() (warns, errs []error) {
ctx.trusted, warns, errs = utils.NewX509CertPool(ctx.config.CertificatesDirectory)
return warns, errs
}
// LoadProviders loads all providers into the CmdCtx.
func (ctx *CmdCtx) LoadProviders() (warns, errs []error) {
// TODO: Adjust this so the CertPool can be used like a provider.
if warns, errs = ctx.LoadTrustedCertificates(); len(warns) != 0 || len(errs) != 0 {
return warns, errs
}
storage := getStorageProvider(ctx)
providers := middlewares.Providers{
Authorizer: authorization.NewAuthorizer(ctx.config),
NTP: ntp.NewProvider(&ctx.config.NTP),
PasswordPolicy: middlewares.NewPasswordPolicyProvider(ctx.config.PasswordPolicy),
Regulator: regulation.NewRegulator(ctx.config.Regulation, storage, utils.RealClock{}),
SessionProvider: session.NewProvider(ctx.config.Session, ctx.trusted),
StorageProvider: storage,
TOTP: totp.NewTimeBasedProvider(ctx.config.TOTP),
}
var err error
switch {
case ctx.config.AuthenticationBackend.File != nil:
providers.UserProvider = authentication.NewFileUserProvider(ctx.config.AuthenticationBackend.File)
case ctx.config.AuthenticationBackend.LDAP != nil:
providers.UserProvider = authentication.NewLDAPUserProvider(ctx.config.AuthenticationBackend, ctx.trusted)
}
if providers.Templates, err = templates.New(templates.Config{EmailTemplatesPath: ctx.config.Notifier.TemplatePath}); err != nil {
errs = append(errs, err)
}
switch {
case ctx.config.Notifier.SMTP != nil:
providers.Notifier = notification.NewSMTPNotifier(ctx.config.Notifier.SMTP, ctx.trusted, providers.Templates)
case ctx.config.Notifier.FileSystem != nil:
providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
}
if providers.OpenIDConnect, err = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, storage); err != nil {
errs = append(errs, err)
}
if ctx.config.Telemetry.Metrics.Enabled {
providers.Metrics = metrics.NewPrometheus()
}
ctx.providers = providers
return warns, errs
}
// ChainRunE runs multiple CobraRunECmd funcs one after the other returning errors.
func (ctx *CmdCtx) ChainRunE(cmdRunEs ...CobraRunECmd) CobraRunECmd {
return func(cmd *cobra.Command, args []string) (err error) {
for _, cmdRunE := range cmdRunEs {
if err = cmdRunE(cmd, args); err != nil {
return err
}
}
return nil
}
}
// ConfigSetFlagsMapRunE adds a command line source with flags mapping.
func (ctx *CmdCtx) ConfigSetFlagsMapRunE(flags *pflag.FlagSet, flagsMap map[string]string, includeInvalidKeys, includeUnchangedKeys bool) (err error) {
if ctx.cconfig == nil {
ctx.cconfig = NewCmdCtxConfig()
}
ctx.cconfig.sources = append(ctx.cconfig.sources, configuration.NewCommandLineSourceWithMapping(flags, flagsMap, includeInvalidKeys, includeUnchangedKeys))
return nil
}
// ConfigSetDefaultsRunE adds a defaults configuration source.
func (ctx *CmdCtx) ConfigSetDefaultsRunE(defaults map[string]any) CobraRunECmd {
return func(cmd *cobra.Command, args []string) (err error) {
if ctx.cconfig == nil {
ctx.cconfig = NewCmdCtxConfig()
}
ctx.cconfig.defaults = configuration.NewMapSource(defaults)
return nil
}
}
// ConfigValidateKeysRunE validates the configuration (keys).
func (ctx *CmdCtx) ConfigValidateKeysRunE(_ *cobra.Command, _ []string) (err error) {
if ctx.cconfig == nil {
return fmt.Errorf("config validate keys must be used with ConfigLoadRunE")
}
validator.ValidateKeys(ctx.cconfig.keys, configuration.DefaultEnvPrefix, ctx.cconfig.validator)
return nil
}
// ConfigValidateRunE validates the configuration (structure).
func (ctx *CmdCtx) ConfigValidateRunE(_ *cobra.Command, _ []string) (err error) {
validator.ValidateConfiguration(ctx.config, ctx.cconfig.validator)
return nil
}
// ConfigValidateLogRunE logs the warnings and errors detected during the validations that have ran.
func (ctx *CmdCtx) ConfigValidateLogRunE(_ *cobra.Command, _ []string) (err error) {
warnings := ctx.cconfig.validator.Warnings()
if len(warnings) != 0 {
for _, warning := range warnings {
ctx.log.Warnf("Configuration: %+v", warning)
}
}
errs := ctx.cconfig.validator.Errors()
if len(errs) != 0 {
for _, err = range errs {
ctx.log.Errorf("Configuration: %+v", err)
}
ctx.log.Fatalf("Can't continue due to the errors loading the configuration")
}
return nil
}
// ConfigValidateSectionPasswordRunE validates the configuration (structure, password section).
func (ctx *CmdCtx) ConfigValidateSectionPasswordRunE(cmd *cobra.Command, _ []string) (err error) {
if ctx.config.AuthenticationBackend.File == nil {
return fmt.Errorf("password configuration was not initialized")
}
val := &schema.StructValidator{}
validator.ValidatePasswordConfiguration(&ctx.config.AuthenticationBackend.File.Password, val)
errs := val.Errors()
if len(errs) == 0 {
return nil
}
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)
}
// ConfigEnsureExistsRunE logs the warnings and errors detected during the validations that have ran.
func (ctx *CmdCtx) ConfigEnsureExistsRunE(cmd *cobra.Command, _ []string) (err error) {
var (
configs []string
created bool
)
if configs, _, err = loadEnvCLIStringSliceValue(cmd, "", cmdFlagNameConfig); err != nil {
return err
}
if len(configs) != 1 {
return nil
}
if created, err = configuration.EnsureConfigurationExists(configs[0]); err != nil {
ctx.log.Fatal(err)
}
if created {
ctx.log.Warnf("Configuration did not exist so a default one has been generated at %s, you will need to configure this", configs[0])
os.Exit(0)
}
return nil
}
// ConfigLoadRunE loads the configuration into the CmdCtx.
func (ctx *CmdCtx) ConfigLoadRunE(cmd *cobra.Command, _ []string) (err error) {
var (
configs, filterNames []string
filters []configuration.FileFilter
)
if configs, _, err = loadEnvCLIStringSliceValue(cmd, "", cmdFlagNameConfig); err != nil {
return err
}
if filterNames, _, err = loadEnvCLIStringSliceValue(cmd, "", cmdFlagNameConfigExpFilters); err != nil {
return err
}
if filters, err = configuration.NewFileFilters(filterNames); err != nil {
return fmt.Errorf("error occurred loading configuration: flag '--%s' is invalid: %w", cmdFlagNameConfigExpFilters, err)
}
if ctx.cconfig == nil {
ctx.cconfig = NewCmdCtxConfig()
}
if ctx.cconfig.keys, err = configuration.LoadAdvanced(
ctx.cconfig.validator,
"",
ctx.config,
configuration.NewDefaultSourcesWithDefaults(
configs,
filters,
configuration.DefaultEnvPrefix,
configuration.DefaultEnvDelimiter,
ctx.cconfig.defaults,
ctx.cconfig.sources...)...); err != nil {
return err
}
return nil
}
func loadEnvCLIStringSliceValue(cmd *cobra.Command, envKey, flagName string) (value []string, explicit bool, err error) { //nolint:unparam
if cmd.Flags().Changed(flagName) {
value, err = cmd.Flags().GetStringSlice(flagName)
return value, true, err
}
var (
env string
ok bool
)
if envKey != "" {
env, ok = os.LookupEnv(envKey)
}
switch {
case ok && env != "":
return strings.Split(env, ","), true, nil
default:
value, err = cmd.Flags().GetStringSlice(flagName)
return value, false, err
}
}

View File

@ -15,7 +15,7 @@ import (
"github.com/authelia/authelia/v4/internal/utils" "github.com/authelia/authelia/v4/internal/utils"
) )
func newCryptoCmd() (cmd *cobra.Command) { func newCryptoCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: cmdUseCrypto, Use: cmdUseCrypto,
Short: cmdAutheliaCryptoShort, Short: cmdAutheliaCryptoShort,
@ -27,47 +27,35 @@ func newCryptoCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newCryptoRandCmd(), newCryptoRandCmd(ctx),
newCryptoCertificateCmd(), newCryptoCertificateCmd(ctx),
newCryptoHashCmd(), newCryptoHashCmd(ctx),
newCryptoPairCmd(), newCryptoPairCmd(ctx),
) )
return cmd return cmd
} }
func newCryptoRandCmd() (cmd *cobra.Command) { func newCryptoRandCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: cmdUseRand, Use: cmdUseRand,
Short: cmdAutheliaCryptoRandShort, Short: cmdAutheliaCryptoRandShort,
Long: cmdAutheliaCryptoRandLong, Long: cmdAutheliaCryptoRandLong,
Example: cmdAutheliaCryptoRandExample, Example: cmdAutheliaCryptoRandExample,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) (err error) { RunE: ctx.CryptoRandRunE,
var (
random string
)
if random, err = flagsGetRandomCharacters(cmd.Flags(), cmdFlagNameLength, cmdFlagNameCharSet, cmdFlagNameCharacters); err != nil {
return err
}
fmt.Printf("Random Value: %s\n", random)
return nil
},
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
cmd.Flags().StringP(cmdFlagNameCharSet, "c", cmdFlagValueCharSet, cmdFlagUsageCharset) cmd.Flags().StringP(cmdFlagNameCharSet, "x", cmdFlagValueCharSet, cmdFlagUsageCharset)
cmd.Flags().String(cmdFlagNameCharacters, "", cmdFlagUsageCharacters) cmd.Flags().String(cmdFlagNameCharacters, "", cmdFlagUsageCharacters)
cmd.Flags().IntP(cmdFlagNameLength, "n", 72, cmdFlagUsageLength) cmd.Flags().IntP(cmdFlagNameLength, "n", 72, cmdFlagUsageLength)
return cmd return cmd
} }
func newCryptoCertificateCmd() (cmd *cobra.Command) { func newCryptoCertificateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: cmdUseCertificate, Use: cmdUseCertificate,
Short: cmdAutheliaCryptoCertificateShort, Short: cmdAutheliaCryptoCertificateShort,
@ -79,15 +67,15 @@ func newCryptoCertificateCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newCryptoCertificateSubCmd(cmdUseRSA), newCryptoCertificateSubCmd(ctx, cmdUseRSA),
newCryptoCertificateSubCmd(cmdUseECDSA), newCryptoCertificateSubCmd(ctx, cmdUseECDSA),
newCryptoCertificateSubCmd(cmdUseEd25519), newCryptoCertificateSubCmd(ctx, cmdUseEd25519),
) )
return cmd return cmd
} }
func newCryptoCertificateSubCmd(use string) (cmd *cobra.Command) { func newCryptoCertificateSubCmd(ctx *CmdCtx, use string) (cmd *cobra.Command) {
useFmt := fmtCryptoCertificateUse(use) useFmt := fmtCryptoCertificateUse(use)
cmd = &cobra.Command{ cmd = &cobra.Command{
@ -100,16 +88,16 @@ func newCryptoCertificateSubCmd(use string) (cmd *cobra.Command) {
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
cmd.AddCommand(newCryptoGenerateCmd(cmdUseCertificate, use), newCryptoCertificateRequestCmd(use)) cmd.AddCommand(newCryptoGenerateCmd(ctx, cmdUseCertificate, use), newCryptoCertificateRequestCmd(ctx, use))
return cmd return cmd
} }
func newCryptoCertificateRequestCmd(algorithm string) (cmd *cobra.Command) { func newCryptoCertificateRequestCmd(ctx *CmdCtx, algorithm string) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: cmdUseRequest, Use: cmdUseRequest,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: cryptoCertificateRequestRunE, RunE: ctx.CryptoCertificateRequestRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -141,7 +129,7 @@ func newCryptoCertificateRequestCmd(algorithm string) (cmd *cobra.Command) {
return cmd return cmd
} }
func newCryptoPairCmd() (cmd *cobra.Command) { func newCryptoPairCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: cmdUsePair, Use: cmdUsePair,
Short: cmdAutheliaCryptoPairShort, Short: cmdAutheliaCryptoPairShort,
@ -153,15 +141,15 @@ func newCryptoPairCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newCryptoPairSubCmd(cmdUseRSA), newCryptoPairSubCmd(ctx, cmdUseRSA),
newCryptoPairSubCmd(cmdUseECDSA), newCryptoPairSubCmd(ctx, cmdUseECDSA),
newCryptoPairSubCmd(cmdUseEd25519), newCryptoPairSubCmd(ctx, cmdUseEd25519),
) )
return cmd return cmd
} }
func newCryptoPairSubCmd(use string) (cmd *cobra.Command) { func newCryptoPairSubCmd(ctx *CmdCtx, use string) (cmd *cobra.Command) {
var ( var (
example, useFmt string example, useFmt string
) )
@ -183,21 +171,21 @@ func newCryptoPairSubCmd(use string) (cmd *cobra.Command) {
Long: fmt.Sprintf(cmdAutheliaCryptoPairSubLong, useFmt, useFmt), Long: fmt.Sprintf(cmdAutheliaCryptoPairSubLong, useFmt, useFmt),
Example: example, Example: example,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: cryptoGenerateRunE, RunE: ctx.CryptoGenerateRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
cmd.AddCommand(newCryptoGenerateCmd(cmdUsePair, use)) cmd.AddCommand(newCryptoGenerateCmd(ctx, cmdUsePair, use))
return cmd return cmd
} }
func newCryptoGenerateCmd(category, algorithm string) (cmd *cobra.Command) { func newCryptoGenerateCmd(ctx *CmdCtx, category, algorithm string) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: cmdUseGenerate, Use: cmdUseGenerate,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: cryptoGenerateRunE, RunE: ctx.CryptoGenerateRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -253,7 +241,23 @@ func newCryptoGenerateCmd(category, algorithm string) (cmd *cobra.Command) {
return cmd return cmd
} }
func cryptoGenerateRunE(cmd *cobra.Command, args []string) (err error) { // CryptoRandRunE is the RunE for the authelia crypto rand command.
func (ctx *CmdCtx) CryptoRandRunE(cmd *cobra.Command, args []string) (err error) {
var (
random string
)
if random, err = flagsGetRandomCharacters(cmd.Flags(), cmdFlagNameLength, cmdFlagNameCharSet, cmdFlagNameCharacters); err != nil {
return err
}
fmt.Printf("Random Value: %s\n", random)
return nil
}
// CryptoGenerateRunE is the RunE for the authelia crypto [pair|certificate] [rsa|ecdsa|ed25519] commands.
func (ctx *CmdCtx) CryptoGenerateRunE(cmd *cobra.Command, args []string) (err error) {
var ( var (
privateKey any privateKey any
) )
@ -263,13 +267,14 @@ func cryptoGenerateRunE(cmd *cobra.Command, args []string) (err error) {
} }
if cmd.Parent().Parent().Use == cmdUseCertificate { if cmd.Parent().Parent().Use == cmdUseCertificate {
return cryptoCertificateGenerateRunE(cmd, args, privateKey) return ctx.CryptoCertificateGenerateRunE(cmd, args, privateKey)
} }
return cryptoPairGenerateRunE(cmd, args, privateKey) return ctx.CryptoPairGenerateRunE(cmd, args, privateKey)
} }
func cryptoCertificateRequestRunE(cmd *cobra.Command, _ []string) (err error) { // CryptoCertificateRequestRunE is the RunE for the authelia crypto certificate request command.
func (ctx *CmdCtx) CryptoCertificateRequestRunE(cmd *cobra.Command, _ []string) (err error) {
var ( var (
privateKey any privateKey any
) )
@ -340,7 +345,8 @@ func cryptoCertificateRequestRunE(cmd *cobra.Command, _ []string) (err error) {
return nil return nil
} }
func cryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string, privateKey any) (err error) { // CryptoCertificateGenerateRunE is the RunE for the authelia crypto certificate [rsa|ecdsa|ed25519] commands.
func (ctx *CmdCtx) CryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string, privateKey any) (err error) {
var ( var (
template, caCertificate, parent *x509.Certificate template, caCertificate, parent *x509.Certificate
publicKey, caPrivateKey, signatureKey any publicKey, caPrivateKey, signatureKey any
@ -432,7 +438,8 @@ func cryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string, privateKey an
return nil return nil
} }
func cryptoPairGenerateRunE(cmd *cobra.Command, _ []string, privateKey any) (err error) { // CryptoPairGenerateRunE is the RunE for the authelia crypto pair [rsa|ecdsa|ed25519] commands.
func (ctx *CmdCtx) CryptoPairGenerateRunE(cmd *cobra.Command, _ []string, privateKey any) (err error) {
var ( var (
privateKeyPath, publicKeyPath string privateKeyPath, publicKeyPath string
pkcs8 bool pkcs8 bool

View File

@ -7,69 +7,13 @@ import (
"github.com/go-crypt/crypt" "github.com/go-crypt/crypt"
"github.com/go-crypt/crypt/algorithm" "github.com/go-crypt/crypt/algorithm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"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/configuration/validator"
) )
func newHashPasswordCmd() (cmd *cobra.Command) { func newCryptoHashCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseHashPassword,
Short: cmdAutheliaHashPasswordShort,
Long: cmdAutheliaHashPasswordLong,
Example: cmdAutheliaHashPasswordExample,
Args: cobra.MaximumNArgs(1),
RunE: cmdHashPasswordRunE,
DisableAutoGenTag: true,
}
cmdFlagConfig(cmd)
cmd.Flags().BoolP(cmdFlagNameSHA512, "z", false, fmt.Sprintf("use sha512 as the algorithm (changes iterations to %d, change with -i)", schema.DefaultPasswordConfig.SHA2Crypt.Iterations))
cmd.Flags().IntP(cmdFlagNameIterations, "i", schema.DefaultPasswordConfig.Argon2.Iterations, "set the number of hashing iterations")
cmd.Flags().IntP(cmdFlagNameMemory, "m", schema.DefaultPasswordConfig.Argon2.Memory, "[argon2id] set the amount of memory param (in MB)")
cmd.Flags().IntP(cmdFlagNameParallelism, "p", schema.DefaultPasswordConfig.Argon2.Parallelism, "[argon2id] set the parallelism param")
cmd.Flags().IntP("key-length", "k", schema.DefaultPasswordConfig.Argon2.KeyLength, "[argon2id] set the key length param")
cmd.Flags().IntP("salt-length", "l", schema.DefaultPasswordConfig.Argon2.SaltLength, "set the auto-generated salt length")
cmd.Flags().Bool(cmdFlagNameNoConfirm, false, "skip the password confirmation prompt")
return cmd
}
func cmdHashPasswordRunE(cmd *cobra.Command, args []string) (err error) {
var (
flagsMap map[string]string
sha512 bool
)
if sha512, err = cmd.Flags().GetBool(cmdFlagNameSHA512); err != nil {
return err
}
switch {
case sha512:
flagsMap = map[string]string{
cmdFlagNameIterations: prefixFilePassword + ".sha2crypt.iterations",
"salt-length": prefixFilePassword + ".sha2crypt.salt_length",
}
default:
flagsMap = map[string]string{
cmdFlagNameIterations: prefixFilePassword + ".argon2.iterations",
"key-length": prefixFilePassword + ".argon2.key_length",
"salt-length": prefixFilePassword + ".argon2.salt_length",
cmdFlagNameParallelism: prefixFilePassword + ".argon2.parallelism",
cmdFlagNameMemory: prefixFilePassword + ".argon2.memory",
}
}
return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
}
func newCryptoHashCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: cmdUseHash, Use: cmdUseHash,
Short: cmdAutheliaCryptoHashShort, Short: cmdAutheliaCryptoHashShort,
@ -81,270 +25,15 @@ func newCryptoHashCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newCryptoHashValidateCmd(), newCryptoHashValidateCmd(ctx),
newCryptoHashGenerateCmd(), newCryptoHashGenerateCmd(ctx),
) )
return cmd return cmd
} }
func newCryptoHashGenerateCmd() (cmd *cobra.Command) { func newCryptoHashGenerateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ defaults := map[string]any{
Use: cmdUseGenerate,
Short: cmdAutheliaCryptoHashGenerateShort,
Long: cmdAutheliaCryptoHashGenerateLong,
Example: cmdAutheliaCryptoHashGenerateExample,
RunE: func(cmd *cobra.Command, args []string) error {
return cmdCryptoHashGenerateFinish(cmd, args, map[string]string{})
},
DisableAutoGenTag: true,
}
cmdFlagConfig(cmd)
cmdFlagPassword(cmd, true)
cmdFlagRandomPassword(cmd)
for _, use := range []string{cmdUseHashArgon2, cmdUseHashSHA2Crypt, cmdUseHashPBKDF2, cmdUseHashBCrypt, cmdUseHashSCrypt} {
cmd.AddCommand(newCryptoHashGenerateSubCmd(use))
}
return cmd
}
func newCryptoHashGenerateSubCmd(use string) (cmd *cobra.Command) {
useFmt := fmtCryptoHashUse(use)
cmd = &cobra.Command{
Use: use,
Short: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubShort, useFmt),
Long: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubLong, useFmt, useFmt),
Example: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubExample, use),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
DisableAutoGenTag: true,
}
switch use {
case cmdUseHashArgon2:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.Argon2.Iterations)
cmdFlagParallelism(cmd, schema.DefaultPasswordConfig.Argon2.Parallelism)
cmdFlagKeySize(cmd, schema.DefaultPasswordConfig.Argon2.KeyLength)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.Argon2.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.Argon2.Variant, "variant, options are 'argon2id', 'argon2i', and 'argon2d'")
cmd.Flags().IntP(cmdFlagNameMemory, "m", schema.DefaultPasswordConfig.Argon2.Memory, "memory in kibibytes")
cmd.Flags().String(cmdFlagNameProfile, "", "profile to use, options are low-memory and recommended")
cmd.RunE = cryptoHashGenerateArgon2RunE
case cmdUseHashSHA2Crypt:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.SHA2Crypt.Iterations)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.SHA2Crypt.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.SHA2Crypt.Variant, "variant, options are sha256 and sha512")
cmd.RunE = cryptoHashGenerateSHA2CryptRunE
case cmdUseHashPBKDF2:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.PBKDF2.Iterations)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.PBKDF2.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.PBKDF2.Variant, "variant, options are 'sha1', 'sha224', 'sha256', 'sha384', and 'sha512'")
cmd.RunE = cryptoHashGeneratePBKDF2RunE
case cmdUseHashBCrypt:
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.BCrypt.Variant, "variant, options are 'standard' and 'sha256'")
cmd.Flags().IntP(cmdFlagNameCost, "i", schema.DefaultPasswordConfig.BCrypt.Cost, "hashing cost")
cmd.RunE = cryptoHashGenerateBCryptRunE
case cmdUseHashSCrypt:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.SCrypt.Iterations)
cmdFlagKeySize(cmd, schema.DefaultPasswordConfig.SCrypt.KeyLength)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.SCrypt.SaltLength)
cmdFlagParallelism(cmd, schema.DefaultPasswordConfig.SCrypt.Parallelism)
cmd.Flags().IntP(cmdFlagNameBlockSize, "r", schema.DefaultPasswordConfig.SCrypt.BlockSize, "block size")
cmd.RunE = cryptoHashGenerateSCryptRunE
}
return cmd
}
func cryptoHashGenerateArgon2RunE(cmd *cobra.Command, args []string) (err error) {
flagsMap := map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".argon2.variant",
cmdFlagNameIterations: prefixFilePassword + ".argon2.iterations",
cmdFlagNameMemory: prefixFilePassword + ".argon2.memory",
cmdFlagNameParallelism: prefixFilePassword + ".argon2.parallelism",
cmdFlagNameKeySize: prefixFilePassword + ".argon2.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".argon2.salt_length",
}
return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
}
func cryptoHashGenerateSHA2CryptRunE(cmd *cobra.Command, args []string) (err error) {
flagsMap := map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".sha2crypt.variant",
cmdFlagNameIterations: prefixFilePassword + ".sha2crypt.iterations",
cmdFlagNameSaltSize: prefixFilePassword + ".sha2crypt.salt_length",
}
return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
}
func cryptoHashGeneratePBKDF2RunE(cmd *cobra.Command, args []string) (err error) {
flagsMap := map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".pbkdf2.variant",
cmdFlagNameIterations: prefixFilePassword + ".pbkdf2.iterations",
cmdFlagNameKeySize: prefixFilePassword + ".pbkdf2.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".pbkdf2.salt_length",
}
return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
}
func cryptoHashGenerateBCryptRunE(cmd *cobra.Command, args []string) (err error) {
flagsMap := map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".bcrypt.variant",
cmdFlagNameCost: prefixFilePassword + ".bcrypt.cost",
}
return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
}
func cryptoHashGenerateSCryptRunE(cmd *cobra.Command, args []string) (err error) {
flagsMap := map[string]string{
cmdFlagNameIterations: prefixFilePassword + ".scrypt.iterations",
cmdFlagNameBlockSize: prefixFilePassword + ".scrypt.block_size",
cmdFlagNameParallelism: prefixFilePassword + ".scrypt.parallelism",
cmdFlagNameKeySize: prefixFilePassword + ".scrypt.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".scrypt.salt_length",
}
return cmdCryptoHashGenerateFinish(cmd, args, flagsMap)
}
func newCryptoHashValidateCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate),
Short: cmdAutheliaCryptoHashValidateShort,
Long: cmdAutheliaCryptoHashValidateLong,
Example: cmdAutheliaCryptoHashValidateExample,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
var (
password string
valid bool
)
if password, _, err = cmdCryptoHashGetPassword(cmd, args, false, false); err != nil {
return fmt.Errorf("error occurred trying to obtain the password: %w", err)
}
if len(password) == 0 {
return fmt.Errorf("no password provided")
}
if valid, err = crypt.CheckPassword(password, args[0]); err != nil {
return fmt.Errorf("error occurred trying to validate the password against the digest: %w", err)
}
switch {
case valid:
fmt.Println("The password matches the digest.")
default:
fmt.Println("The password does not match the digest.")
}
return nil
},
DisableAutoGenTag: true,
}
cmdFlagPassword(cmd, false)
return cmd
}
func cmdCryptoHashGenerateFinish(cmd *cobra.Command, args []string, flagsMap map[string]string) (err error) {
var (
algName string
configs []string
c schema.Password
)
if configs, err = cmd.Flags().GetStringSlice(cmdFlagNameConfig); err != nil {
return err
}
// Skip config if the flag wasn't set and the default is non-existent.
if !cmd.Flags().Changed(cmdFlagNameConfig) {
configs = configFilterExisting(configs)
}
legacy := cmd.Use == cmdUseHashPassword
switch {
case cmd.Use == cmdUseGenerate:
break
case legacy:
if sha512, _ := cmd.Flags().GetBool(cmdFlagNameSHA512); sha512 {
algName = cmdUseHashSHA2Crypt
} else {
algName = cmdUseHashArgon2
}
default:
algName = cmd.Use
}
if c, err = cmdCryptoHashGetConfig(algName, configs, cmd.Flags(), flagsMap); err != nil {
return err
}
if legacy && algName == cmdUseHashArgon2 && cmd.Flags().Changed(cmdFlagNameMemory) {
c.Argon2.Memory *= 1024
}
var (
hash algorithm.Hash
digest algorithm.Digest
password string
random bool
)
if password, random, err = cmdCryptoHashGetPassword(cmd, args, legacy, !legacy); err != nil {
return err
}
if len(password) == 0 {
return fmt.Errorf("no password provided")
}
if hash, err = authentication.NewFileCryptoHashFromConfig(c); err != nil {
return err
}
if digest, err = hash.Hash(password); err != nil {
return err
}
if random {
fmt.Printf("Random Password: %s\n", password)
}
fmt.Printf("Digest: %s\n", digest.Encode())
return nil
}
func cmdCryptoHashGetConfig(algorithm string, configs []string, flags *pflag.FlagSet, flagsMap map[string]string) (c schema.Password, err error) {
mapDefaults := map[string]interface{}{
prefixFilePassword + ".algorithm": schema.DefaultPasswordConfig.Algorithm, prefixFilePassword + ".algorithm": schema.DefaultPasswordConfig.Algorithm,
prefixFilePassword + ".argon2.variant": schema.DefaultPasswordConfig.Argon2.Variant, prefixFilePassword + ".argon2.variant": schema.DefaultPasswordConfig.Argon2.Variant,
prefixFilePassword + ".argon2.iterations": schema.DefaultPasswordConfig.Argon2.Iterations, prefixFilePassword + ".argon2.iterations": schema.DefaultPasswordConfig.Argon2.Iterations,
@ -367,44 +56,246 @@ func cmdCryptoHashGetConfig(algorithm string, configs []string, flags *pflag.Fla
prefixFilePassword + ".scrypt.salt_length": schema.DefaultPasswordConfig.SCrypt.SaltLength, prefixFilePassword + ".scrypt.salt_length": schema.DefaultPasswordConfig.SCrypt.SaltLength,
} }
sources := configuration.NewDefaultSourcesWithDefaults( cmd = &cobra.Command{
configs, Use: cmdUseGenerate,
nil, Short: cmdAutheliaCryptoHashGenerateShort,
configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter, Long: cmdAutheliaCryptoHashGenerateLong,
configuration.NewMapSource(mapDefaults), Example: cmdAutheliaCryptoHashGenerateExample,
configuration.NewCommandLineSourceWithMapping(flags, flagsMap, false, false), PreRunE: ctx.ChainRunE(
ctx.ConfigSetDefaultsRunE(defaults),
ctx.CryptoHashGenerateMapFlagsPreRunE,
ctx.ConfigLoadRunE,
ctx.ConfigValidateSectionPasswordRunE,
),
RunE: ctx.CryptoHashGenerateRunE,
DisableAutoGenTag: true,
}
cmdFlagPassword(cmd, true)
cmdFlagRandomPassword(cmd)
for _, use := range []string{cmdUseHashArgon2, cmdUseHashSHA2Crypt, cmdUseHashPBKDF2, cmdUseHashBCrypt, cmdUseHashSCrypt} {
cmd.AddCommand(newCryptoHashGenerateSubCmd(ctx, use))
}
return cmd
}
func newCryptoHashGenerateSubCmd(ctx *CmdCtx, use string) (cmd *cobra.Command) {
defaults := map[string]any{
prefixFilePassword + ".algorithm": schema.DefaultPasswordConfig.Algorithm,
prefixFilePassword + ".argon2.variant": schema.DefaultPasswordConfig.Argon2.Variant,
prefixFilePassword + ".argon2.iterations": schema.DefaultPasswordConfig.Argon2.Iterations,
prefixFilePassword + ".argon2.memory": schema.DefaultPasswordConfig.Argon2.Memory,
prefixFilePassword + ".argon2.parallelism": schema.DefaultPasswordConfig.Argon2.Parallelism,
prefixFilePassword + ".argon2.key_length": schema.DefaultPasswordConfig.Argon2.KeyLength,
prefixFilePassword + ".argon2.salt_length": schema.DefaultPasswordConfig.Argon2.SaltLength,
prefixFilePassword + ".sha2crypt.variant": schema.DefaultPasswordConfig.SHA2Crypt.Variant,
prefixFilePassword + ".sha2crypt.iterations": schema.DefaultPasswordConfig.SHA2Crypt.Iterations,
prefixFilePassword + ".sha2crypt.salt_length": schema.DefaultPasswordConfig.SHA2Crypt.SaltLength,
prefixFilePassword + ".pbkdf2.variant": schema.DefaultPasswordConfig.PBKDF2.Variant,
prefixFilePassword + ".pbkdf2.iterations": schema.DefaultPasswordConfig.PBKDF2.Iterations,
prefixFilePassword + ".pbkdf2.salt_length": schema.DefaultPasswordConfig.PBKDF2.SaltLength,
prefixFilePassword + ".bcrypt.variant": schema.DefaultPasswordConfig.BCrypt.Variant,
prefixFilePassword + ".bcrypt.cost": schema.DefaultPasswordConfig.BCrypt.Cost,
prefixFilePassword + ".scrypt.iterations": schema.DefaultPasswordConfig.SCrypt.Iterations,
prefixFilePassword + ".scrypt.block_size": schema.DefaultPasswordConfig.SCrypt.BlockSize,
prefixFilePassword + ".scrypt.parallelism": schema.DefaultPasswordConfig.SCrypt.Parallelism,
prefixFilePassword + ".scrypt.key_length": schema.DefaultPasswordConfig.SCrypt.KeyLength,
prefixFilePassword + ".scrypt.salt_length": schema.DefaultPasswordConfig.SCrypt.SaltLength,
}
useFmt := fmtCryptoHashUse(use)
cmd = &cobra.Command{
Use: use,
Short: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubShort, useFmt),
Long: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubLong, useFmt, useFmt),
Example: fmt.Sprintf(fmtCmdAutheliaCryptoHashGenerateSubExample, use),
Args: cobra.NoArgs,
PersistentPreRunE: ctx.ChainRunE(
ctx.ConfigSetDefaultsRunE(defaults),
ctx.CryptoHashGenerateMapFlagsPreRunE,
ctx.ConfigLoadRunE,
ctx.ConfigValidateSectionPasswordRunE,
),
RunE: ctx.CryptoHashGenerateRunE,
DisableAutoGenTag: true,
}
switch use {
case cmdUseHashArgon2:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.Argon2.Iterations)
cmdFlagParallelism(cmd, schema.DefaultPasswordConfig.Argon2.Parallelism)
cmdFlagKeySize(cmd, schema.DefaultPasswordConfig.Argon2.KeyLength)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.Argon2.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.Argon2.Variant, "variant, options are 'argon2id', 'argon2i', and 'argon2d'")
cmd.Flags().IntP(cmdFlagNameMemory, "m", schema.DefaultPasswordConfig.Argon2.Memory, "memory in kibibytes")
cmd.Flags().String(cmdFlagNameProfile, "", "profile to use, options are low-memory and recommended")
case cmdUseHashSHA2Crypt:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.SHA2Crypt.Iterations)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.SHA2Crypt.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.SHA2Crypt.Variant, "variant, options are sha256 and sha512")
cmd.PreRunE = ctx.ChainRunE()
case cmdUseHashPBKDF2:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.PBKDF2.Iterations)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.PBKDF2.SaltLength)
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.PBKDF2.Variant, "variant, options are 'sha1', 'sha224', 'sha256', 'sha384', and 'sha512'")
case cmdUseHashBCrypt:
cmd.Flags().StringP(cmdFlagNameVariant, "v", schema.DefaultPasswordConfig.BCrypt.Variant, "variant, options are 'standard' and 'sha256'")
cmd.Flags().IntP(cmdFlagNameCost, "i", schema.DefaultPasswordConfig.BCrypt.Cost, "hashing cost")
case cmdUseHashSCrypt:
cmdFlagIterations(cmd, schema.DefaultPasswordConfig.SCrypt.Iterations)
cmdFlagKeySize(cmd, schema.DefaultPasswordConfig.SCrypt.KeyLength)
cmdFlagSaltSize(cmd, schema.DefaultPasswordConfig.SCrypt.SaltLength)
cmdFlagParallelism(cmd, schema.DefaultPasswordConfig.SCrypt.Parallelism)
cmd.Flags().IntP(cmdFlagNameBlockSize, "r", schema.DefaultPasswordConfig.SCrypt.BlockSize, "block size")
}
return cmd
}
func newCryptoHashValidateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate),
Short: cmdAutheliaCryptoHashValidateShort,
Long: cmdAutheliaCryptoHashValidateLong,
Example: cmdAutheliaCryptoHashValidateExample,
Args: cobra.ExactArgs(1),
RunE: ctx.CryptoHashValidateRunE,
DisableAutoGenTag: true,
}
cmdFlagPassword(cmd, false)
return cmd
}
// CryptoHashValidateRunE is the RunE for the authelia crypto hash validate command.
func (ctx *CmdCtx) CryptoHashValidateRunE(cmd *cobra.Command, args []string) (err error) {
var (
password string
valid bool
) )
if algorithm != "" { if password, _, err = cmdCryptoHashGetPassword(cmd, args, false, false); err != nil {
alg := map[string]interface{}{prefixFilePassword + ".algorithm": algorithm} return fmt.Errorf("error occurred trying to obtain the password: %w", err)
sources = append(sources, configuration.NewMapSource(alg))
} }
val := schema.NewStructValidator() if len(password) == 0 {
return fmt.Errorf("no password provided")
if _, err = configuration.LoadAdvanced(val, prefixFilePassword, &c, sources...); err != nil {
return schema.Password{}, fmt.Errorf("error occurred loading configuration: %w", err)
} }
validator.ValidatePasswordConfiguration(&c, val) if valid, err = crypt.CheckPassword(password, args[0]); err != nil {
return fmt.Errorf("error occurred trying to validate the password against the digest: %w", err)
}
errs := val.Errors() switch {
case valid:
fmt.Println("The password matches the digest.")
default:
fmt.Println("The password does not match the digest.")
}
if len(errs) != 0 { return nil
for i, e := range errs { }
if i == 0 {
err = e
continue
}
err = fmt.Errorf("%v, %w", err, e) // CryptoHashGenerateMapFlagsPreRunE is the RunE which configures the flags map configuration source for the
// authelia crypto hash generate commands.
func (ctx *CmdCtx) CryptoHashGenerateMapFlagsPreRunE(cmd *cobra.Command, args []string) (err error) {
var flagsMap map[string]string
switch cmd.Use {
case cmdUseHashArgon2:
flagsMap = map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".argon2.variant",
cmdFlagNameIterations: prefixFilePassword + ".argon2.iterations",
cmdFlagNameMemory: prefixFilePassword + ".argon2.memory",
cmdFlagNameParallelism: prefixFilePassword + ".argon2.parallelism",
cmdFlagNameKeySize: prefixFilePassword + ".argon2.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".argon2.salt_length",
}
case cmdUseHashSHA2Crypt:
flagsMap = map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".sha2crypt.variant",
cmdFlagNameIterations: prefixFilePassword + ".sha2crypt.iterations",
cmdFlagNameSaltSize: prefixFilePassword + ".sha2crypt.salt_length",
}
case cmdUseHashPBKDF2:
flagsMap = map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".pbkdf2.variant",
cmdFlagNameIterations: prefixFilePassword + ".pbkdf2.iterations",
cmdFlagNameKeySize: prefixFilePassword + ".pbkdf2.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".pbkdf2.salt_length",
}
case cmdUseHashBCrypt:
flagsMap = map[string]string{
cmdFlagNameVariant: prefixFilePassword + ".bcrypt.variant",
cmdFlagNameCost: prefixFilePassword + ".bcrypt.cost",
}
case cmdUseHashSCrypt:
flagsMap = map[string]string{
cmdFlagNameIterations: prefixFilePassword + ".scrypt.iterations",
cmdFlagNameBlockSize: prefixFilePassword + ".scrypt.block_size",
cmdFlagNameParallelism: prefixFilePassword + ".scrypt.parallelism",
cmdFlagNameKeySize: prefixFilePassword + ".scrypt.key_length",
cmdFlagNameSaltSize: prefixFilePassword + ".scrypt.salt_length",
} }
return schema.Password{}, fmt.Errorf("errors occurred validating the password configuration: %w", err)
} }
return c, nil if flagsMap != nil {
ctx.cconfig.sources = append(ctx.cconfig.sources, configuration.NewCommandLineSourceWithMapping(cmd.Flags(), flagsMap, false, false))
}
return nil
}
// CryptoHashGenerateRunE is the RunE for the authelia crypto hash generate commands.
func (ctx *CmdCtx) CryptoHashGenerateRunE(cmd *cobra.Command, args []string) (err error) {
var (
hash algorithm.Hash
digest algorithm.Digest
password string
random bool
)
if password, random, err = cmdCryptoHashGetPassword(cmd, args, false, true); err != nil {
return err
}
if len(password) == 0 {
return fmt.Errorf("no password provided")
}
switch cmd.Use {
case cmdUseGenerate:
break
default:
ctx.config.AuthenticationBackend.File.Password.Algorithm = cmd.Use
}
if hash, err = authentication.NewFileCryptoHashFromConfig(ctx.config.AuthenticationBackend.File.Password); err != nil {
return err
}
if digest, err = hash.Hash(password); err != nil {
return err
}
if random {
fmt.Printf("Random Password: %s\n", password)
}
fmt.Printf("Digest: %s\n", digest.Encode())
return nil
} }
func cmdCryptoHashGetPassword(cmd *cobra.Command, args []string, useArgs, useRandom bool) (password string, random bool, err error) { func cmdCryptoHashGetPassword(cmd *cobra.Command, args []string, useArgs, useRandom bool) (password string, random bool, err error) {
@ -430,18 +321,15 @@ func cmdCryptoHashGetPassword(cmd *cobra.Command, args []string, useArgs, useRan
} }
var ( var (
data []byte
noConfirm bool noConfirm bool
) )
if data, err = termReadPasswordWithPrompt("Enter Password: ", "password"); err != nil { if password, err = termReadPasswordWithPrompt("Enter Password: ", "password"); err != nil {
err = fmt.Errorf("failed to read the password from the terminal: %w", err) err = fmt.Errorf("failed to read the password from the terminal: %w", err)
return return
} }
password = string(data)
if cmd.Use == fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate) { if cmd.Use == fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate) {
fmt.Println("") fmt.Println("")
@ -449,11 +337,13 @@ func cmdCryptoHashGetPassword(cmd *cobra.Command, args []string, useArgs, useRan
} }
if noConfirm, err = cmd.Flags().GetBool(cmdFlagNameNoConfirm); err == nil && !noConfirm { if noConfirm, err = cmd.Flags().GetBool(cmdFlagNameNoConfirm); err == nil && !noConfirm {
if data, err = termReadPasswordWithPrompt("Confirm Password: ", ""); err != nil { var confirm string
if confirm, err = termReadPasswordWithPrompt("Confirm Password: ", ""); err != nil {
return return
} }
if password != string(data) { if password != confirm {
fmt.Println("") fmt.Println("")
err = fmt.Errorf("the password did not match the confirmation password") err = fmt.Errorf("the password did not match the confirmation password")
@ -467,10 +357,6 @@ func cmdCryptoHashGetPassword(cmd *cobra.Command, args []string, useArgs, useRan
return return
} }
func cmdFlagConfig(cmd *cobra.Command) {
cmd.PersistentFlags().StringSliceP(cmdFlagNameConfig, "c", []string{"configuration.yml"}, "configuration files to load")
}
func cmdFlagPassword(cmd *cobra.Command, noConfirm bool) { func cmdFlagPassword(cmd *cobra.Command, noConfirm bool) {
cmd.PersistentFlags().String(cmdFlagNamePassword, "", "manually supply the password rather than using the terminal prompt") cmd.PersistentFlags().String(cmdFlagNamePassword, "", "manually supply the password rather than using the terminal prompt")

View File

@ -24,7 +24,7 @@ import (
func cmdFlagsCryptoCertificateCommon(cmd *cobra.Command) { func cmdFlagsCryptoCertificateCommon(cmd *cobra.Command) {
cmd.Flags().String(cmdFlagNameSignature, "SHA256", "signature algorithm for the certificate") cmd.Flags().String(cmdFlagNameSignature, "SHA256", "signature algorithm for the certificate")
cmd.Flags().StringP(cmdFlagNameCommonName, "c", "", "certificate common name") cmd.Flags().StringP(cmdFlagNameCommonName, "n", "", "certificate common name")
cmd.Flags().StringSliceP(cmdFlagNameOrganization, "o", []string{"Authelia"}, "certificate organization") cmd.Flags().StringSliceP(cmdFlagNameOrganization, "o", []string{"Authelia"}, "certificate organization")
cmd.Flags().StringSlice(cmdFlagNameOrganizationalUnit, nil, "certificate organizational unit") cmd.Flags().StringSlice(cmdFlagNameOrganizationalUnit, nil, "certificate organizational unit")
cmd.Flags().StringSlice(cmdFlagNameCountry, nil, "certificate country") cmd.Flags().StringSlice(cmdFlagNameCountry, nil, "certificate country")

View File

@ -6,3 +6,6 @@ import (
// ErrStdinIsNotTerminal is returned when Stdin is not an interactive terminal. // ErrStdinIsNotTerminal is returned when Stdin is not an interactive terminal.
var ErrStdinIsNotTerminal = errors.New("stdin is not a terminal") var ErrStdinIsNotTerminal = errors.New("stdin is not a terminal")
// ErrConfirmationMismatch is returned when user input does not match the confirmation prompt.
var ErrConfirmationMismatch = errors.New("user input didn't match the confirmation prompt")

View File

@ -1,115 +1,121 @@
package commands package commands
import ( import (
"crypto/x509" "encoding/base32"
"errors"
"fmt"
"github.com/authelia/authelia/v4/internal/authentication" "github.com/spf13/pflag"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/metrics" "github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/notification"
"github.com/authelia/authelia/v4/internal/ntp"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/regulation"
"github.com/authelia/authelia/v4/internal/session"
"github.com/authelia/authelia/v4/internal/storage" "github.com/authelia/authelia/v4/internal/storage"
"github.com/authelia/authelia/v4/internal/templates"
"github.com/authelia/authelia/v4/internal/totp"
"github.com/authelia/authelia/v4/internal/utils"
) )
func getStorageProvider() (provider storage.Provider) { func getStorageProvider(ctx *CmdCtx) (provider storage.Provider) {
switch { switch {
case config.Storage.Local == nil: case ctx.config.Storage.PostgreSQL != nil:
return getStorageProviderWithPool(nil) return storage.NewPostgreSQLProvider(ctx.config, ctx.trusted)
default: case ctx.config.Storage.MySQL != nil:
caCertPool, _, _ := utils.NewX509CertPool(config.CertificatesDirectory) return storage.NewMySQLProvider(ctx.config, ctx.trusted)
case ctx.config.Storage.Local != nil:
return getStorageProviderWithPool(caCertPool) return storage.NewSQLiteProvider(ctx.config)
}
}
func getStorageProviderWithPool(caCertPool *x509.CertPool) (provider storage.Provider) {
switch {
case config.Storage.PostgreSQL != nil:
return storage.NewPostgreSQLProvider(config, caCertPool)
case config.Storage.MySQL != nil:
return storage.NewMySQLProvider(config, caCertPool)
case config.Storage.Local != nil:
return storage.NewSQLiteProvider(config)
default: default:
return nil return nil
} }
} }
func getProviders() (providers middlewares.Providers, warnings []error, errors []error) { func containsIdentifier(identifier model.UserOpaqueIdentifier, identifiers []model.UserOpaqueIdentifier) bool {
// TODO: Adjust this so the CertPool can be used like a provider. for i := 0; i < len(identifiers); i++ {
caCertPool, warnings, errors := utils.NewX509CertPool(config.CertificatesDirectory) if identifier.Service == identifiers[i].Service && identifier.SectorID == identifiers[i].SectorID && identifier.Username == identifiers[i].Username {
if len(warnings) != 0 || len(errors) != 0 { return true
return providers, warnings, errors }
} }
storageProvider := getStorageProviderWithPool(caCertPool) return false
}
var (
userProvider authentication.UserProvider func storageWrapCheckSchemaErr(err error) error {
err error switch {
) case errors.Is(err, errStorageSchemaIncompatible):
return fmt.Errorf("command requires the use of a compatibe schema version: %w", err)
switch { case errors.Is(err, errStorageSchemaOutdated):
case config.AuthenticationBackend.File != nil: return fmt.Errorf("command requires the use of a up to date schema version: %w", err)
userProvider = authentication.NewFileUserProvider(config.AuthenticationBackend.File) default:
case config.AuthenticationBackend.LDAP != nil: return err
userProvider = authentication.NewLDAPUserProvider(config.AuthenticationBackend, caCertPool) }
} }
templatesProvider, err := templates.New(templates.Config{EmailTemplatesPath: config.Notifier.TemplatePath}) func storageTOTPGenerateRunEOptsFromFlags(flags *pflag.FlagSet) (force bool, filename, secret string, err error) {
if err != nil { if force, err = flags.GetBool("force"); err != nil {
errors = append(errors, err) return force, filename, secret, err
} }
var notifier notification.Notifier if filename, err = flags.GetString("path"); err != nil {
return force, filename, secret, err
switch { }
case config.Notifier.SMTP != nil:
notifier = notification.NewSMTPNotifier(config.Notifier.SMTP, caCertPool, templatesProvider) if secret, err = flags.GetString("secret"); err != nil {
case config.Notifier.FileSystem != nil: return force, filename, secret, err
notifier = notification.NewFileNotifier(*config.Notifier.FileSystem) }
}
secretLength := base32.StdEncoding.WithPadding(base32.NoPadding).DecodedLen(len(secret))
ntpProvider := ntp.NewProvider(&config.NTP) if secret != "" && secretLength < schema.TOTPSecretSizeMinimum {
return force, filename, secret, fmt.Errorf("decoded length of the base32 secret must have "+
clock := utils.RealClock{} "a length of more than %d but '%s' has a decoded length of %d", schema.TOTPSecretSizeMinimum, secret, secretLength)
authorizer := authorization.NewAuthorizer(config) }
sessionProvider := session.NewProvider(config.Session, caCertPool)
regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock) return force, filename, secret, nil
}
oidcProvider, err := oidc.NewOpenIDConnectProvider(config.IdentityProviders.OIDC, storageProvider)
if err != nil { func storageWebauthnDeleteRunEOptsFromFlags(flags *pflag.FlagSet, args []string) (all, byKID bool, description, kid, user string, err error) {
errors = append(errors, err) if len(args) != 0 {
} user = args[0]
}
totpProvider := totp.NewTimeBasedProvider(config.TOTP)
f := 0
ppolicyProvider := middlewares.NewPasswordPolicyProvider(config.PasswordPolicy)
if flags.Changed(cmdFlagNameAll) {
var metricsProvider metrics.Provider if all, err = flags.GetBool(cmdFlagNameAll); err != nil {
if config.Telemetry.Metrics.Enabled { return
metricsProvider = metrics.NewPrometheus() }
}
f++
return middlewares.Providers{ }
Authorizer: authorizer,
UserProvider: userProvider, if flags.Changed(cmdFlagNameDescription) {
Regulator: regulator, if description, err = flags.GetString(cmdFlagNameDescription); err != nil {
OpenIDConnect: oidcProvider, return
StorageProvider: storageProvider, }
Metrics: metricsProvider,
NTP: ntpProvider, f++
Notifier: notifier, }
SessionProvider: sessionProvider,
Templates: templatesProvider, if byKID = flags.Changed(cmdFlagNameKeyID); byKID {
TOTP: totpProvider, if kid, err = flags.GetString(cmdFlagNameKeyID); err != nil {
PasswordPolicy: ppolicyProvider, return
}, warnings, errors }
f++
}
if f > 1 {
err = fmt.Errorf("must only supply one of the flags --all, --description, and --kid but %d were specified", f)
return
}
if f == 0 {
err = fmt.Errorf("must supply one of the flags --all, --description, or --kid")
return
}
if !byKID && len(user) == 0 {
err = fmt.Errorf("must supply the username or the --kid flag")
return
}
return
} }

View File

@ -4,12 +4,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/authelia/authelia/v4/internal/configuration/schema"
) )
func TestGetStorageProvider(t *testing.T) { func TestGetStorageProvider(t *testing.T) {
config = &schema.Configuration{} assert.Nil(t, getStorageProvider(NewCmdCtx()))
assert.Nil(t, getStorageProvider())
} }

View File

@ -1,7 +1,6 @@
package commands package commands
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"os" "os"
@ -11,15 +10,11 @@ import (
"syscall" "syscall"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"golang.org/x/sync/errgroup"
"github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/logging" "github.com/authelia/authelia/v4/internal/logging"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/server" "github.com/authelia/authelia/v4/internal/server"
"github.com/authelia/authelia/v4/internal/utils" "github.com/authelia/authelia/v4/internal/utils"
@ -27,6 +22,8 @@ import (
// NewRootCmd returns a new Root Cmd. // NewRootCmd returns a new Root Cmd.
func NewRootCmd() (cmd *cobra.Command) { func NewRootCmd() (cmd *cobra.Command) {
ctx := NewCmdCtx()
version := utils.Version() version := utils.Version()
cmd = &cobra.Command{ cmd = &cobra.Command{
@ -36,68 +33,70 @@ func NewRootCmd() (cmd *cobra.Command) {
Example: cmdAutheliaExample, Example: cmdAutheliaExample,
Version: version, Version: version,
Args: cobra.NoArgs, Args: cobra.NoArgs,
PreRun: newCmdWithConfigPreRun(true, true, true), PreRunE: ctx.ChainRunE(
Run: cmdRootRun, ctx.ConfigEnsureExistsRunE,
ctx.ConfigLoadRunE,
ctx.ConfigValidateKeysRunE,
ctx.ConfigValidateRunE,
ctx.ConfigValidateLogRunE,
),
RunE: ctx.RootRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
cmdWithConfigFlags(cmd, false, []string{}) cmd.PersistentFlags().StringSliceP(cmdFlagNameConfig, "c", []string{"configuration.yml"}, "configuration files to load")
cmd.Flags().StringSlice(cmdFlagNameConfigExpFilters, nil, "Applies filters in order to the configuration file before the YAML parser. Options are 'template', 'expand-env'") cmd.PersistentFlags().StringSlice(cmdFlagNameConfigExpFilters, nil, "applies filters in order to the configuration file before the YAML parser, options are 'template', 'expand-env'")
cmd.AddCommand( cmd.AddCommand(
newAccessControlCommand(), newAccessControlCommand(ctx),
newBuildInfoCmd(), newBuildInfoCmd(ctx),
newCryptoCmd(), newCryptoCmd(ctx),
newHashPasswordCmd(), newStorageCmd(ctx),
newStorageCmd(), newValidateConfigCmd(ctx),
newValidateConfigCmd(),
) )
return cmd return cmd
} }
func cmdRootRun(_ *cobra.Command, _ []string) { func (ctx *CmdCtx) RootRunE(_ *cobra.Command, _ []string) (err error) {
logger := logging.Logger() ctx.log.Infof("Authelia %s is starting", utils.Version())
logger.Infof("Authelia %s is starting", utils.Version())
if os.Getenv("ENVIRONMENT") == "dev" { if os.Getenv("ENVIRONMENT") == "dev" {
logger.Info("===> Authelia is running in development mode. <===") ctx.log.Info("===> Authelia is running in development mode. <===")
} }
if err := logging.InitializeLogger(config.Log, true); err != nil { if err = logging.InitializeLogger(ctx.config.Log, true); err != nil {
logger.Fatalf("Cannot initialize logger: %v", err) ctx.log.Fatalf("Cannot initialize logger: %v", err)
} }
providers, warnings, errors := getProviders() warns, errs := ctx.LoadProviders()
if len(warnings) != 0 {
for _, err := range warnings { if len(warns) != 0 {
logger.Warn(err) for _, err = range warns {
ctx.log.Warn(err)
} }
} }
if len(errors) != 0 { if len(errs) != 0 {
for _, err := range errors { for _, err = range errs {
logger.Error(err) ctx.log.Error(err)
} }
logger.Fatalf("Errors occurred provisioning providers.") ctx.log.Fatalf("Errors occurred provisioning providers.")
} }
doStartupChecks(config, &providers, logger) doStartupChecks(ctx)
runServices(config, providers, logger) runServices(ctx)
return nil
} }
//nolint:gocyclo // Complexity is required in this function. //nolint:gocyclo // Complexity is required in this function.
func runServices(config *schema.Configuration, providers middlewares.Providers, log *logrus.Logger) { func runServices(ctx *CmdCtx) {
ctx := context.Background() defer ctx.cancel()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
quit := make(chan os.Signal, 1) quit := make(chan os.Signal, 1)
@ -105,26 +104,24 @@ func runServices(config *schema.Configuration, providers middlewares.Providers,
defer signal.Stop(quit) defer signal.Stop(quit)
g, ctx := errgroup.WithContext(ctx)
var ( var (
mainServer, metricsServer *fasthttp.Server mainServer, metricsServer *fasthttp.Server
mainListener, metricsListener net.Listener mainListener, metricsListener net.Listener
) )
g.Go(func() (err error) { ctx.group.Go(func() (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.WithError(recoverErr(r)).Errorf("Critical error in server caught (recovered)") ctx.log.WithError(recoverErr(r)).Errorf("Critical error in server caught (recovered)")
} }
}() }()
if mainServer, mainListener, err = server.CreateDefaultServer(*config, providers); err != nil { if mainServer, mainListener, err = server.CreateDefaultServer(*ctx.config, ctx.providers); err != nil {
return err return err
} }
if err = mainServer.Serve(mainListener); err != nil { if err = mainServer.Serve(mainListener); err != nil {
log.WithError(err).Error("Server (main) returned error") ctx.log.WithError(err).Error("Server (main) returned error")
return err return err
} }
@ -132,23 +129,23 @@ func runServices(config *schema.Configuration, providers middlewares.Providers,
return nil return nil
}) })
g.Go(func() (err error) { ctx.group.Go(func() (err error) {
if providers.Metrics == nil { if ctx.providers.Metrics == nil {
return nil return nil
} }
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.WithError(recoverErr(r)).Errorf("Critical error in metrics server caught (recovered)") ctx.log.WithError(recoverErr(r)).Errorf("Critical error in metrics server caught (recovered)")
} }
}() }()
if metricsServer, metricsListener, err = server.CreateMetricsServer(config.Telemetry.Metrics); err != nil { if metricsServer, metricsListener, err = server.CreateMetricsServer(ctx.config.Telemetry.Metrics); err != nil {
return err return err
} }
if err = metricsServer.Serve(metricsListener); err != nil { if err = metricsServer.Serve(metricsListener); err != nil {
log.WithError(err).Error("Server (metrics) returned error") ctx.log.WithError(err).Error("Server (metrics) returned error")
return err return err
} }
@ -156,10 +153,10 @@ func runServices(config *schema.Configuration, providers middlewares.Providers,
return nil return nil
}) })
if config.AuthenticationBackend.File != nil && config.AuthenticationBackend.File.Watch { if ctx.config.AuthenticationBackend.File != nil && ctx.config.AuthenticationBackend.File.Watch {
provider := providers.UserProvider.(*authentication.FileUserProvider) provider := ctx.providers.UserProvider.(*authentication.FileUserProvider)
if watcher, err := runServiceFileWatcher(g, log, config.AuthenticationBackend.File.Path, provider); err != nil { if watcher, err := runServiceFileWatcher(ctx, ctx.config.AuthenticationBackend.File.Path, provider); err != nil {
log.WithError(err).Errorf("Error opening file watcher") ctx.log.WithError(err).Errorf("Error opening file watcher")
} else { } else {
defer watcher.Close() defer watcher.Close()
} }
@ -169,38 +166,38 @@ func runServices(config *schema.Configuration, providers middlewares.Providers,
case s := <-quit: case s := <-quit:
switch s { switch s {
case syscall.SIGINT: case syscall.SIGINT:
log.Debugf("Shutdown started due to SIGINT") ctx.log.Debugf("Shutdown started due to SIGINT")
case syscall.SIGQUIT: case syscall.SIGQUIT:
log.Debugf("Shutdown started due to SIGQUIT") ctx.log.Debugf("Shutdown started due to SIGQUIT")
} }
case <-ctx.Done(): case <-ctx.Done():
log.Debugf("Shutdown started due to context completion") ctx.log.Debugf("Shutdown started due to context completion")
} }
cancel() ctx.cancel()
log.Infof("Shutting down") ctx.log.Infof("Shutting down")
var err error var err error
if mainServer != nil { if mainServer != nil {
if err = mainServer.Shutdown(); err != nil { if err = mainServer.Shutdown(); err != nil {
log.WithError(err).Errorf("Error occurred shutting down the server") ctx.log.WithError(err).Errorf("Error occurred shutting down the server")
} }
} }
if metricsServer != nil { if metricsServer != nil {
if err = metricsServer.Shutdown(); err != nil { if err = metricsServer.Shutdown(); err != nil {
log.WithError(err).Errorf("Error occurred shutting down the metrics server") ctx.log.WithError(err).Errorf("Error occurred shutting down the metrics server")
} }
} }
if err = providers.StorageProvider.Close(); err != nil { if err = ctx.providers.StorageProvider.Close(); err != nil {
log.WithError(err).Errorf("Error occurred closing the database connection") ctx.log.WithError(err).Errorf("Error occurred closing the database connection")
} }
if err = g.Wait(); err != nil { if err = ctx.group.Wait(); err != nil {
log.WithError(err).Errorf("Error occurred waiting for shutdown") ctx.log.WithError(err).Errorf("Error occurred waiting for shutdown")
} }
} }
@ -210,7 +207,7 @@ type ProviderReload interface {
Reload() (reloaded bool, err error) Reload() (reloaded bool, err error)
} }
func runServiceFileWatcher(g *errgroup.Group, log *logrus.Logger, path string, reload ProviderReload) (watcher *fsnotify.Watcher, err error) { func runServiceFileWatcher(ctx *CmdCtx, path string, reload ProviderReload) (watcher *fsnotify.Watcher, err error) {
if watcher, err = fsnotify.NewWatcher(); err != nil { if watcher, err = fsnotify.NewWatcher(); err != nil {
return nil, err return nil, err
} }
@ -223,7 +220,7 @@ func runServiceFileWatcher(g *errgroup.Group, log *logrus.Logger, path string, r
directory, filename = filepath.Dir(path), filepath.Base(path) directory, filename = filepath.Dir(path), filepath.Base(path)
} }
g.Go(func() error { ctx.group.Go(func() error {
for { for {
select { select {
case <-failed: case <-failed:
@ -234,30 +231,30 @@ func runServiceFileWatcher(g *errgroup.Group, log *logrus.Logger, path string, r
} }
if filename != filepath.Base(event.Name) { if filename != filepath.Base(event.Name) {
log.WithField("file", event.Name).WithField("op", event.Op).Tracef("File modification detected to irrelevant file") ctx.log.WithField("file", event.Name).WithField("op", event.Op).Tracef("File modification detected to irrelevant file")
break break
} }
switch { switch {
case event.Op&fsnotify.Write == fsnotify.Write, event.Op&fsnotify.Create == fsnotify.Create: case event.Op&fsnotify.Write == fsnotify.Write, event.Op&fsnotify.Create == fsnotify.Create:
log.WithField("file", event.Name).WithField("op", event.Op).Debug("File modification detected") ctx.log.WithField("file", event.Name).WithField("op", event.Op).Debug("File modification detected")
switch reloaded, err := reload.Reload(); { switch reloaded, err := reload.Reload(); {
case err != nil: case err != nil:
log.WithField("file", event.Name).WithField("op", event.Op).WithError(err).Error("Error occurred reloading file") ctx.log.WithField("file", event.Name).WithField("op", event.Op).WithError(err).Error("Error occurred reloading file")
case reloaded: case reloaded:
log.WithField("file", event.Name).Info("Reloaded file successfully") ctx.log.WithField("file", event.Name).Info("Reloaded file successfully")
default: default:
log.WithField("file", event.Name).Debug("Reload of file was triggered but it was skipped") ctx.log.WithField("file", event.Name).Debug("Reload of file was triggered but it was skipped")
} }
case event.Op&fsnotify.Remove == fsnotify.Remove: case event.Op&fsnotify.Remove == fsnotify.Remove:
log.WithField("file", event.Name).WithField("op", event.Op).Debug("Remove of file was detected") ctx.log.WithField("file", event.Name).WithField("op", event.Op).Debug("Remove of file was detected")
} }
case err, ok := <-watcher.Errors: case err, ok := <-watcher.Errors:
if !ok { if !ok {
return nil return nil
} }
log.WithError(err).Errorf("Error while watching files") ctx.log.WithError(err).Errorf("Error while watching files")
} }
} }
}) })
@ -268,53 +265,53 @@ func runServiceFileWatcher(g *errgroup.Group, log *logrus.Logger, path string, r
return nil, err return nil, err
} }
log.WithField("directory", directory).WithField("file", filename).Debug("Directory is being watched for changes to the file") ctx.log.WithField("directory", directory).WithField("file", filename).Debug("Directory is being watched for changes to the file")
return watcher, nil return watcher, nil
} }
func doStartupChecks(config *schema.Configuration, providers *middlewares.Providers, log *logrus.Logger) { func doStartupChecks(ctx *CmdCtx) {
var ( var (
failures []string failures []string
err error err error
) )
if err = doStartupCheck(log, "storage", providers.StorageProvider, false); err != nil { if err = doStartupCheck(ctx, "storage", ctx.providers.StorageProvider, false); err != nil {
log.Errorf("Failure running the storage provider startup check: %+v", err) ctx.log.Errorf("Failure running the storage provider startup check: %+v", err)
failures = append(failures, "storage") failures = append(failures, "storage")
} }
if err = doStartupCheck(log, "user", providers.UserProvider, false); err != nil { if err = doStartupCheck(ctx, "user", ctx.providers.UserProvider, false); err != nil {
log.Errorf("Failure running the user provider startup check: %+v", err) ctx.log.Errorf("Failure running the user provider startup check: %+v", err)
failures = append(failures, "user") failures = append(failures, "user")
} }
if err = doStartupCheck(log, "notification", providers.Notifier, config.Notifier.DisableStartupCheck); err != nil { if err = doStartupCheck(ctx, "notification", ctx.providers.Notifier, ctx.config.Notifier.DisableStartupCheck); err != nil {
log.Errorf("Failure running the notification provider startup check: %+v", err) ctx.log.Errorf("Failure running the notification provider startup check: %+v", err)
failures = append(failures, "notification") failures = append(failures, "notification")
} }
if !config.NTP.DisableStartupCheck && !providers.Authorizer.IsSecondFactorEnabled() { if !ctx.config.NTP.DisableStartupCheck && !ctx.providers.Authorizer.IsSecondFactorEnabled() {
log.Debug("The NTP startup check was skipped due to there being no configured 2FA access control rules") ctx.log.Debug("The NTP startup check was skipped due to there being no configured 2FA access control rules")
} else if err = doStartupCheck(log, "ntp", providers.NTP, config.NTP.DisableStartupCheck); err != nil { } else if err = doStartupCheck(ctx, "ntp", ctx.providers.NTP, ctx.config.NTP.DisableStartupCheck); err != nil {
log.Errorf("Failure running the ntp provider startup check: %+v", err) ctx.log.Errorf("Failure running the ntp provider startup check: %+v", err)
if !config.NTP.DisableFailure { if !ctx.config.NTP.DisableFailure {
failures = append(failures, "ntp") failures = append(failures, "ntp")
} }
} }
if len(failures) != 0 { if len(failures) != 0 {
log.Fatalf("The following providers had fatal failures during startup: %s", strings.Join(failures, ", ")) ctx.log.Fatalf("The following providers had fatal failures during startup: %s", strings.Join(failures, ", "))
} }
} }
func doStartupCheck(logger *logrus.Logger, name string, provider model.StartupCheck, disabled bool) error { func doStartupCheck(ctx *CmdCtx, name string, provider model.StartupCheck, disabled bool) error {
if disabled { if disabled {
logger.Debugf("%s provider: startup check skipped as it is disabled", name) ctx.log.Debugf("%s provider: startup check skipped as it is disabled", name)
return nil return nil
} }

View File

@ -9,20 +9,23 @@ import (
"github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/configuration/schema"
) )
func newStorageCmd() (cmd *cobra.Command) { func newStorageCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "storage", Use: "storage",
Short: cmdAutheliaStorageShort, Short: cmdAutheliaStorageShort,
Long: cmdAutheliaStorageLong, Long: cmdAutheliaStorageLong,
Example: cmdAutheliaStorageExample, Example: cmdAutheliaStorageExample,
Args: cobra.NoArgs, Args: cobra.NoArgs,
PersistentPreRunE: storagePersistentPreRunE, PersistentPreRunE: ctx.ChainRunE(
ctx.ConfigStorageCommandLineConfigPersistentPreRunE,
ctx.ConfigLoadRunE,
ctx.ConfigValidateStoragePersistentPreRunE,
ctx.LoadProvidersStorageRunE,
),
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
cmdWithConfigFlags(cmd, true, []string{"configuration.yml"})
cmd.PersistentFlags().String(cmdFlagNameEncryptionKey, "", "the storage encryption key to use") cmd.PersistentFlags().String(cmdFlagNameEncryptionKey, "", "the storage encryption key to use")
cmd.PersistentFlags().String(cmdFlagNameSQLite3Path, "", "the SQLite database path") cmd.PersistentFlags().String(cmdFlagNameSQLite3Path, "", "the SQLite database path")
@ -45,16 +48,16 @@ func newStorageCmd() (cmd *cobra.Command) {
cmd.PersistentFlags().String("postgres.ssl.key", "", "the PostgreSQL ssl key file location") cmd.PersistentFlags().String("postgres.ssl.key", "", "the PostgreSQL ssl key file location")
cmd.AddCommand( cmd.AddCommand(
newStorageMigrateCmd(), newStorageMigrateCmd(ctx),
newStorageSchemaInfoCmd(), newStorageSchemaInfoCmd(ctx),
newStorageEncryptionCmd(), newStorageEncryptionCmd(ctx),
newStorageUserCmd(), newStorageUserCmd(ctx),
) )
return cmd return cmd
} }
func newStorageEncryptionCmd() (cmd *cobra.Command) { func newStorageEncryptionCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "encryption", Use: "encryption",
Short: cmdAutheliaStorageEncryptionShort, Short: cmdAutheliaStorageEncryptionShort,
@ -65,20 +68,20 @@ func newStorageEncryptionCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newStorageEncryptionChangeKeyCmd(), newStorageEncryptionChangeKeyCmd(ctx),
newStorageEncryptionCheckCmd(), newStorageEncryptionCheckCmd(ctx),
) )
return cmd return cmd
} }
func newStorageEncryptionCheckCmd() (cmd *cobra.Command) { func newStorageEncryptionCheckCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "check", Use: "check",
Short: cmdAutheliaStorageEncryptionCheckShort, Short: cmdAutheliaStorageEncryptionCheckShort,
Long: cmdAutheliaStorageEncryptionCheckLong, Long: cmdAutheliaStorageEncryptionCheckLong,
Example: cmdAutheliaStorageEncryptionCheckExample, Example: cmdAutheliaStorageEncryptionCheckExample,
RunE: storageSchemaEncryptionCheckRunE, RunE: ctx.StorageSchemaEncryptionCheckRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -88,13 +91,13 @@ func newStorageEncryptionCheckCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageEncryptionChangeKeyCmd() (cmd *cobra.Command) { func newStorageEncryptionChangeKeyCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "change-key", Use: "change-key",
Short: cmdAutheliaStorageEncryptionChangeKeyShort, Short: cmdAutheliaStorageEncryptionChangeKeyShort,
Long: cmdAutheliaStorageEncryptionChangeKeyLong, Long: cmdAutheliaStorageEncryptionChangeKeyLong,
Example: cmdAutheliaStorageEncryptionChangeKeyExample, Example: cmdAutheliaStorageEncryptionChangeKeyExample,
RunE: storageSchemaEncryptionChangeKeyRunE, RunE: ctx.StorageSchemaEncryptionChangeKeyRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -104,7 +107,7 @@ func newStorageEncryptionChangeKeyCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserCmd() (cmd *cobra.Command) { func newStorageUserCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "user", Use: "user",
Short: cmdAutheliaStorageUserShort, Short: cmdAutheliaStorageUserShort,
@ -115,15 +118,15 @@ func newStorageUserCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newStorageUserIdentifiersCmd(), newStorageUserIdentifiersCmd(ctx),
newStorageUserTOTPCmd(), newStorageUserTOTPCmd(ctx),
newStorageUserWebAuthnCmd(), newStorageUserWebAuthnCmd(ctx),
) )
return cmd return cmd
} }
func newStorageUserIdentifiersCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "identifiers", Use: "identifiers",
Short: cmdAutheliaStorageUserIdentifiersShort, Short: cmdAutheliaStorageUserIdentifiersShort,
@ -134,22 +137,22 @@ func newStorageUserIdentifiersCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newStorageUserIdentifiersExportCmd(), newStorageUserIdentifiersExportCmd(ctx),
newStorageUserIdentifiersImportCmd(), newStorageUserIdentifiersImportCmd(ctx),
newStorageUserIdentifiersGenerateCmd(), newStorageUserIdentifiersGenerateCmd(ctx),
newStorageUserIdentifiersAddCmd(), newStorageUserIdentifiersAddCmd(ctx),
) )
return cmd return cmd
} }
func newStorageUserIdentifiersExportCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersExportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "export", Use: "export",
Short: cmdAutheliaStorageUserIdentifiersExportShort, Short: cmdAutheliaStorageUserIdentifiersExportShort,
Long: cmdAutheliaStorageUserIdentifiersExportLong, Long: cmdAutheliaStorageUserIdentifiersExportLong,
Example: cmdAutheliaStorageUserIdentifiersExportExample, Example: cmdAutheliaStorageUserIdentifiersExportExample,
RunE: storageUserIdentifiersExport, RunE: ctx.StorageUserIdentifiersExportRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -159,13 +162,13 @@ func newStorageUserIdentifiersExportCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserIdentifiersImportCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersImportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "import", Use: "import",
Short: cmdAutheliaStorageUserIdentifiersImportShort, Short: cmdAutheliaStorageUserIdentifiersImportShort,
Long: cmdAutheliaStorageUserIdentifiersImportLong, Long: cmdAutheliaStorageUserIdentifiersImportLong,
Example: cmdAutheliaStorageUserIdentifiersImportExample, Example: cmdAutheliaStorageUserIdentifiersImportExample,
RunE: storageUserIdentifiersImport, RunE: ctx.StorageUserIdentifiersImportRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -175,13 +178,13 @@ func newStorageUserIdentifiersImportCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserIdentifiersGenerateCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersGenerateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "generate", Use: "generate",
Short: cmdAutheliaStorageUserIdentifiersGenerateShort, Short: cmdAutheliaStorageUserIdentifiersGenerateShort,
Long: cmdAutheliaStorageUserIdentifiersGenerateLong, Long: cmdAutheliaStorageUserIdentifiersGenerateLong,
Example: cmdAutheliaStorageUserIdentifiersGenerateExample, Example: cmdAutheliaStorageUserIdentifiersGenerateExample,
RunE: storageUserIdentifiersGenerate, RunE: ctx.StorageUserIdentifiersGenerateRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -193,14 +196,14 @@ func newStorageUserIdentifiersGenerateCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserIdentifiersAddCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersAddCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "add <username>", Use: "add <username>",
Short: cmdAutheliaStorageUserIdentifiersAddShort, Short: cmdAutheliaStorageUserIdentifiersAddShort,
Long: cmdAutheliaStorageUserIdentifiersAddLong, Long: cmdAutheliaStorageUserIdentifiersAddLong,
Example: cmdAutheliaStorageUserIdentifiersAddExample, Example: cmdAutheliaStorageUserIdentifiersAddExample,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: storageUserIdentifiersAdd, RunE: ctx.StorageUserIdentifiersAddRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -212,7 +215,7 @@ func newStorageUserIdentifiersAddCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserWebAuthnCmd() (cmd *cobra.Command) { func newStorageUserWebAuthnCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "webauthn", Use: "webauthn",
Short: cmdAutheliaStorageUserWebAuthnShort, Short: cmdAutheliaStorageUserWebAuthnShort,
@ -223,20 +226,20 @@ func newStorageUserWebAuthnCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newStorageUserWebAuthnListCmd(), newStorageUserWebAuthnListCmd(ctx),
newStorageUserWebAuthnDeleteCmd(), newStorageUserWebAuthnDeleteCmd(ctx),
) )
return cmd return cmd
} }
func newStorageUserWebAuthnListCmd() (cmd *cobra.Command) { func newStorageUserWebAuthnListCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "list [username]", Use: "list [username]",
Short: cmdAutheliaStorageUserWebAuthnListShort, Short: cmdAutheliaStorageUserWebAuthnListShort,
Long: cmdAutheliaStorageUserWebAuthnListLong, Long: cmdAutheliaStorageUserWebAuthnListLong,
Example: cmdAutheliaStorageUserWebAuthnListExample, Example: cmdAutheliaStorageUserWebAuthnListExample,
RunE: storageWebAuthnListRunE, RunE: ctx.StorageWebauthnListRunE,
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
DisableAutoGenTag: true, DisableAutoGenTag: true,
@ -245,13 +248,13 @@ func newStorageUserWebAuthnListCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserWebAuthnDeleteCmd() (cmd *cobra.Command) { func newStorageUserWebAuthnDeleteCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "delete [username]", Use: "delete [username]",
Short: cmdAutheliaStorageUserWebAuthnDeleteShort, Short: cmdAutheliaStorageUserWebAuthnDeleteShort,
Long: cmdAutheliaStorageUserWebAuthnDeleteLong, Long: cmdAutheliaStorageUserWebAuthnDeleteLong,
Example: cmdAutheliaStorageUserWebAuthnDeleteExample, Example: cmdAutheliaStorageUserWebAuthnDeleteExample,
RunE: storageWebAuthnDeleteRunE, RunE: ctx.StorageWebauthnDeleteRunE,
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
DisableAutoGenTag: true, DisableAutoGenTag: true,
@ -264,7 +267,7 @@ func newStorageUserWebAuthnDeleteCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserTOTPCmd() (cmd *cobra.Command) { func newStorageUserTOTPCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "totp", Use: "totp",
Short: cmdAutheliaStorageUserTOTPShort, Short: cmdAutheliaStorageUserTOTPShort,
@ -275,21 +278,21 @@ func newStorageUserTOTPCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newStorageUserTOTPGenerateCmd(), newStorageUserTOTPGenerateCmd(ctx),
newStorageUserTOTPDeleteCmd(), newStorageUserTOTPDeleteCmd(ctx),
newStorageUserTOTPExportCmd(), newStorageUserTOTPExportCmd(ctx),
) )
return cmd return cmd
} }
func newStorageUserTOTPGenerateCmd() (cmd *cobra.Command) { func newStorageUserTOTPGenerateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "generate <username>", Use: "generate <username>",
Short: cmdAutheliaStorageUserTOTPGenerateShort, Short: cmdAutheliaStorageUserTOTPGenerateShort,
Long: cmdAutheliaStorageUserTOTPGenerateLong, Long: cmdAutheliaStorageUserTOTPGenerateLong,
Example: cmdAutheliaStorageUserTOTPGenerateExample, Example: cmdAutheliaStorageUserTOTPGenerateExample,
RunE: storageTOTPGenerateRunE, RunE: ctx.StorageTOTPGenerateRunE,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
DisableAutoGenTag: true, DisableAutoGenTag: true,
@ -307,13 +310,13 @@ func newStorageUserTOTPGenerateCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserTOTPDeleteCmd() (cmd *cobra.Command) { func newStorageUserTOTPDeleteCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "delete <username>", Use: "delete <username>",
Short: cmdAutheliaStorageUserTOTPDeleteShort, Short: cmdAutheliaStorageUserTOTPDeleteShort,
Long: cmdAutheliaStorageUserTOTPDeleteLong, Long: cmdAutheliaStorageUserTOTPDeleteLong,
Example: cmdAutheliaStorageUserTOTPDeleteExample, Example: cmdAutheliaStorageUserTOTPDeleteExample,
RunE: storageTOTPDeleteRunE, RunE: ctx.StorageTOTPDeleteRunE,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
DisableAutoGenTag: true, DisableAutoGenTag: true,
@ -322,13 +325,13 @@ func newStorageUserTOTPDeleteCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageUserTOTPExportCmd() (cmd *cobra.Command) { func newStorageUserTOTPExportCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "export", Use: "export",
Short: cmdAutheliaStorageUserTOTPExportShort, Short: cmdAutheliaStorageUserTOTPExportShort,
Long: cmdAutheliaStorageUserTOTPExportLong, Long: cmdAutheliaStorageUserTOTPExportLong,
Example: cmdAutheliaStorageUserTOTPExportExample, Example: cmdAutheliaStorageUserTOTPExportExample,
RunE: storageTOTPExportRunE, RunE: ctx.StorageTOTPExportRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -339,13 +342,13 @@ func newStorageUserTOTPExportCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageSchemaInfoCmd() (cmd *cobra.Command) { func newStorageSchemaInfoCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "schema-info", Use: "schema-info",
Short: cmdAutheliaStorageSchemaInfoShort, Short: cmdAutheliaStorageSchemaInfoShort,
Long: cmdAutheliaStorageSchemaInfoLong, Long: cmdAutheliaStorageSchemaInfoLong,
Example: cmdAutheliaStorageSchemaInfoExample, Example: cmdAutheliaStorageSchemaInfoExample,
RunE: storageSchemaInfoRunE, RunE: ctx.StorageSchemaInfoRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -354,7 +357,7 @@ func newStorageSchemaInfoCmd() (cmd *cobra.Command) {
} }
// NewMigrationCmd returns a new Migration Cmd. // NewMigrationCmd returns a new Migration Cmd.
func newStorageMigrateCmd() (cmd *cobra.Command) { func newStorageMigrateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "migrate", Use: "migrate",
Short: cmdAutheliaStorageMigrateShort, Short: cmdAutheliaStorageMigrateShort,
@ -366,22 +369,22 @@ func newStorageMigrateCmd() (cmd *cobra.Command) {
} }
cmd.AddCommand( cmd.AddCommand(
newStorageMigrateUpCmd(), newStorageMigrateDownCmd(), newStorageMigrateUpCmd(ctx), newStorageMigrateDownCmd(ctx),
newStorageMigrateListUpCmd(), newStorageMigrateListDownCmd(), newStorageMigrateListUpCmd(ctx), newStorageMigrateListDownCmd(ctx),
newStorageMigrateHistoryCmd(), newStorageMigrateHistoryCmd(ctx),
) )
return cmd return cmd
} }
func newStorageMigrateHistoryCmd() (cmd *cobra.Command) { func newStorageMigrateHistoryCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "history", Use: "history",
Short: cmdAutheliaStorageMigrateHistoryShort, Short: cmdAutheliaStorageMigrateHistoryShort,
Long: cmdAutheliaStorageMigrateHistoryLong, Long: cmdAutheliaStorageMigrateHistoryLong,
Example: cmdAutheliaStorageMigrateHistoryExample, Example: cmdAutheliaStorageMigrateHistoryExample,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: storageMigrateHistoryRunE, RunE: ctx.StorageMigrateHistoryRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -389,14 +392,14 @@ func newStorageMigrateHistoryCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageMigrateListUpCmd() (cmd *cobra.Command) { func newStorageMigrateListUpCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "list-up", Use: "list-up",
Short: cmdAutheliaStorageMigrateListUpShort, Short: cmdAutheliaStorageMigrateListUpShort,
Long: cmdAutheliaStorageMigrateListUpLong, Long: cmdAutheliaStorageMigrateListUpLong,
Example: cmdAutheliaStorageMigrateListUpExample, Example: cmdAutheliaStorageMigrateListUpExample,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: newStorageMigrateListRunE(true), RunE: ctx.NewStorageMigrateListRunE(true),
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -404,14 +407,14 @@ func newStorageMigrateListUpCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageMigrateListDownCmd() (cmd *cobra.Command) { func newStorageMigrateListDownCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "list-down", Use: "list-down",
Short: cmdAutheliaStorageMigrateListDownShort, Short: cmdAutheliaStorageMigrateListDownShort,
Long: cmdAutheliaStorageMigrateListDownLong, Long: cmdAutheliaStorageMigrateListDownLong,
Example: cmdAutheliaStorageMigrateListDownExample, Example: cmdAutheliaStorageMigrateListDownExample,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: newStorageMigrateListRunE(false), RunE: ctx.NewStorageMigrateListRunE(false),
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -419,14 +422,14 @@ func newStorageMigrateListDownCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageMigrateUpCmd() (cmd *cobra.Command) { func newStorageMigrateUpCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: storageMigrateDirectionUp, Use: storageMigrateDirectionUp,
Short: cmdAutheliaStorageMigrateUpShort, Short: cmdAutheliaStorageMigrateUpShort,
Long: cmdAutheliaStorageMigrateUpLong, Long: cmdAutheliaStorageMigrateUpLong,
Example: cmdAutheliaStorageMigrateUpExample, Example: cmdAutheliaStorageMigrateUpExample,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: newStorageMigrationRunE(true), RunE: ctx.NewStorageMigrationRunE(true),
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
@ -436,14 +439,14 @@ func newStorageMigrateUpCmd() (cmd *cobra.Command) {
return cmd return cmd
} }
func newStorageMigrateDownCmd() (cmd *cobra.Command) { func newStorageMigrateDownCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: storageMigrateDirectionDown, Use: storageMigrateDirectionDown,
Short: cmdAutheliaStorageMigrateDownShort, Short: cmdAutheliaStorageMigrateDownShort,
Long: cmdAutheliaStorageMigrateDownLong, Long: cmdAutheliaStorageMigrateDownLong,
Example: cmdAutheliaStorageMigrateDownExample, Example: cmdAutheliaStorageMigrateDownExample,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: newStorageMigrationRunE(false), RunE: ctx.NewStorageMigrationRunE(false),
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }

View File

@ -1,9 +1,7 @@
package commands package commands
import ( import (
"context"
"database/sql" "database/sql"
"encoding/base32"
"errors" "errors"
"fmt" "fmt"
"image" "image"
@ -15,11 +13,8 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator" "github.com/authelia/authelia/v4/internal/configuration/validator"
"github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/storage" "github.com/authelia/authelia/v4/internal/storage"
@ -27,28 +22,35 @@ import (
"github.com/authelia/authelia/v4/internal/utils" "github.com/authelia/authelia/v4/internal/utils"
) )
func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) { // LoadProvidersStorageRunE is a special PreRunE that loads the storage provider into the CmdCtx.
var configs []string func (ctx *CmdCtx) LoadProvidersStorageRunE(cmd *cobra.Command, args []string) (err error) {
switch warns, errs := ctx.LoadTrustedCertificates(); {
case len(errs) != 0:
err = fmt.Errorf("had the following errors loading the trusted certificates")
if configs, err = cmd.Flags().GetStringSlice(cmdFlagNameConfig); err != nil { for _, e := range errs {
return err err = fmt.Errorf("%+v: %w", err, e)
}
sources := make([]configuration.Source, 0, len(configs)+3)
if cmd.Flags().Changed(cmdFlagNameConfig) {
for _, configFile := range configs {
if _, err := os.Stat(configFile); os.IsNotExist(err) {
return fmt.Errorf("could not load the provided configuration file %s: %w", configFile, err)
}
sources = append(sources, configuration.NewYAMLFileSource(configFile))
} }
} else if _, err := os.Stat(configs[0]); err == nil {
sources = append(sources, configuration.NewYAMLFileSource(configs[0]))
}
mapping := map[string]string{ return err
case len(warns) != 0:
err = fmt.Errorf("had the following warnings loading the trusted certificates")
for _, e := range errs {
err = fmt.Errorf("%+v: %w", err, e)
}
return err
default:
ctx.providers.StorageProvider = getStorageProvider(ctx)
return nil
}
}
// ConfigStorageCommandLineConfigPersistentPreRunE configures the storage command mapping.
func (ctx *CmdCtx) ConfigStorageCommandLineConfigPersistentPreRunE(cmd *cobra.Command, _ []string) (err error) {
flagsMap := map[string]string{
cmdFlagNameEncryptionKey: "storage.encryption_key", cmdFlagNameEncryptionKey: "storage.encryption_key",
cmdFlagNameSQLite3Path: "storage.local.path", cmdFlagNameSQLite3Path: "storage.local.path",
@ -77,75 +79,73 @@ func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) {
cmdFlagNameSecretSize: "totp.secret_size", cmdFlagNameSecretSize: "totp.secret_size",
} }
sources = append(sources, configuration.NewEnvironmentSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)) return ctx.ConfigSetFlagsMapRunE(cmd.Flags(), flagsMap, true, false)
sources = append(sources, configuration.NewSecretsSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)) }
sources = append(sources, configuration.NewCommandLineSourceWithMapping(cmd.Flags(), mapping, true, false))
val := schema.NewStructValidator() // ConfigValidateStoragePersistentPreRunE validates the storage config before running commands using it.
func (ctx *CmdCtx) ConfigValidateStoragePersistentPreRunE(_ *cobra.Command, _ []string) (err error) {
if errs := ctx.cconfig.validator.Errors(); len(errs) != 0 {
var (
i int
e error
)
config = &schema.Configuration{} for i, e = range errs {
if i == 0 {
err = e
continue
}
err = fmt.Errorf("%w, %v", err, e)
}
if _, err = configuration.LoadAdvanced(val, "", &config, sources...); err != nil {
return err return err
} }
if val.HasErrors() { validator.ValidateStorage(ctx.config.Storage, ctx.cconfig.validator)
var finalErr error
for i, err := range val.Errors() { validator.ValidateTOTP(ctx.config, ctx.cconfig.validator)
if errs := ctx.cconfig.validator.Errors(); len(errs) != 0 {
var (
i int
e error
)
for i, e = range errs {
if i == 0 { if i == 0 {
finalErr = err err = e
continue continue
} }
finalErr = fmt.Errorf("%w, %v", finalErr, err) err = fmt.Errorf("%w, %v", err, e)
} }
return finalErr return err
}
validator.ValidateStorage(config.Storage, val)
validator.ValidateTOTP(config, val)
if val.HasErrors() {
var finalErr error
for i, err := range val.Errors() {
if i == 0 {
finalErr = err
continue
}
finalErr = fmt.Errorf("%w, %v", finalErr, err)
}
return finalErr
} }
return nil return nil
} }
func storageSchemaEncryptionCheckRunE(cmd *cobra.Command, args []string) (err error) { func (ctx *CmdCtx) StorageSchemaEncryptionCheckRunE(cmd *cobra.Command, args []string) (err error) {
var ( var (
provider storage.Provider verbose bool
verbose bool result storage.EncryptionValidationResult
result storage.EncryptionValidationResult
ctx = context.Background()
) )
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
if verbose, err = cmd.Flags().GetBool(cmdFlagNameVerbose); err != nil { if verbose, err = cmd.Flags().GetBool(cmdFlagNameVerbose); err != nil {
return err return err
} }
if result, err = provider.SchemaEncryptionCheckKey(ctx, verbose); err != nil { if result, err = ctx.providers.StorageProvider.SchemaEncryptionCheckKey(ctx, verbose); err != nil {
switch { switch {
case errors.Is(err, storage.ErrSchemaEncryptionVersionUnsupported): case errors.Is(err, storage.ErrSchemaEncryptionVersionUnsupported):
fmt.Printf("Storage Encryption Key Validation: FAILURE\n\n\tCause: The schema version doesn't support encryption.\n") fmt.Printf("Storage Encryption Key Validation: FAILURE\n\n\tCause: The schema version doesn't support encryption.\n")
@ -183,26 +183,22 @@ func storageSchemaEncryptionCheckRunE(cmd *cobra.Command, args []string) (err er
return nil return nil
} }
func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (err error) { // StorageSchemaEncryptionChangeKeyRunE is the RunE for the authelia storage encryption change-key command.
func (ctx *CmdCtx) StorageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (err error) {
var ( var (
provider storage.Provider key string
key string version int
version int
ctx = context.Background()
) )
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if err = checkStorageSchemaUpToDate(ctx, provider); err != nil { if err = ctx.CheckSchemaVersion(); err != nil {
return err return storageWrapCheckSchemaErr(err)
} }
if version, err = provider.SchemaVersion(ctx); err != nil { if version, err = ctx.providers.StorageProvider.SchemaVersion(ctx); err != nil {
return err return err
} }
@ -218,7 +214,7 @@ func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (er
} }
if !useFlag || key == "" { if !useFlag || key == "" {
if key, err = termReadPasswordStrWithPrompt("Enter New Storage Encryption Key: ", cmdFlagNameNewEncryptionKey); err != nil { if key, err = termReadPasswordWithPrompt("Enter New Storage Encryption Key: ", cmdFlagNameNewEncryptionKey); err != nil {
return err return err
} }
} }
@ -230,7 +226,7 @@ func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (er
return errors.New("the new encryption key must be at least 20 characters") return errors.New("the new encryption key must be at least 20 characters")
} }
if err = provider.SchemaEncryptionChangeKey(ctx, key); err != nil { if err = ctx.providers.StorageProvider.SchemaEncryptionChangeKey(ctx, key); err != nil {
return err return err
} }
@ -239,27 +235,25 @@ func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (er
return nil return nil
} }
func storageWebAuthnListRunE(cmd *cobra.Command, args []string) (err error) { // StorageWebauthnListRunE is the RunE for the authelia storage user webauthn list command.
func (ctx *CmdCtx) StorageWebauthnListRunE(cmd *cobra.Command, args []string) (err error) {
if len(args) == 0 || args[0] == "" { if len(args) == 0 || args[0] == "" {
return storageWebAuthnListAllRunE(cmd, args) return ctx.StorageWebauthnListAllRunE(cmd, args)
} }
var (
provider storage.Provider
ctx = context.Background()
)
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
var devices []model.WebauthnDevice var devices []model.WebauthnDevice
user := args[0] user := args[0]
devices, err = provider.LoadWebauthnDevicesByUsername(ctx, user) devices, err = ctx.providers.StorageProvider.LoadWebauthnDevicesByUsername(ctx, user)
switch { switch {
case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebauthnDevice)): case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebauthnDevice)):
@ -278,18 +272,16 @@ func storageWebAuthnListRunE(cmd *cobra.Command, args []string) (err error) {
return nil return nil
} }
func storageWebAuthnListAllRunE(_ *cobra.Command, _ []string) (err error) { // StorageWebauthnListAllRunE is the RunE for the authelia storage user webauthn list command when no args are specified.
var ( func (ctx *CmdCtx) StorageWebauthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
provider storage.Provider
ctx = context.Background()
)
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
var devices []model.WebauthnDevice var devices []model.WebauthnDevice
limit := 10 limit := 10
@ -297,7 +289,7 @@ func storageWebAuthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
output := strings.Builder{} output := strings.Builder{}
for page := 0; true; page++ { for page := 0; true; page++ {
if devices, err = provider.LoadWebauthnDevices(ctx, limit, page); err != nil { if devices, err = ctx.providers.StorageProvider.LoadWebauthnDevices(ctx, limit, page); err != nil {
return fmt.Errorf("failed to list devices: %w", err) return fmt.Errorf("failed to list devices: %w", err)
} }
@ -320,35 +312,33 @@ func storageWebAuthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
return nil return nil
} }
func storageWebAuthnDeleteRunE(cmd *cobra.Command, args []string) (err error) { // StorageWebauthnDeleteRunE is the RunE for the authelia storage user webauthn delete command.
var ( func (ctx *CmdCtx) StorageWebauthnDeleteRunE(cmd *cobra.Command, args []string) (err error) {
provider storage.Provider
ctx = context.Background()
)
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
var ( var (
all, byKID bool all, byKID bool
description, kid, user string description, kid, user string
) )
if all, byKID, description, kid, user, err = storageWebAuthnDeleteGetAndValidateConfig(cmd, args); err != nil { if all, byKID, description, kid, user, err = storageWebauthnDeleteRunEOptsFromFlags(cmd.Flags(), args); err != nil {
return err return err
} }
if byKID { if byKID {
if err = provider.DeleteWebauthnDevice(ctx, kid); err != nil { if err = ctx.providers.StorageProvider.DeleteWebauthnDevice(ctx, kid); err != nil {
return fmt.Errorf("failed to delete WebAuthn device with kid '%s': %w", kid, err) return fmt.Errorf("failed to delete WebAuthn device with kid '%s': %w", kid, err)
} }
fmt.Printf("Deleted WebAuthn device with kid '%s'", kid) fmt.Printf("Deleted WebAuthn device with kid '%s'", kid)
} else { } else {
err = provider.DeleteWebauthnDeviceByUsername(ctx, user, description) err = ctx.providers.StorageProvider.DeleteWebauthnDeviceByUsername(ctx, user, description)
if all { if all {
if err != nil { if err != nil {
@ -368,62 +358,9 @@ func storageWebAuthnDeleteRunE(cmd *cobra.Command, args []string) (err error) {
return nil return nil
} }
func storageWebAuthnDeleteGetAndValidateConfig(cmd *cobra.Command, args []string) (all, byKID bool, description, kid, user string, err error) { // StorageTOTPGenerateRunE is the RunE for the authelia storage user totp generate command.
if len(args) != 0 { func (ctx *CmdCtx) StorageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
user = args[0]
}
flags := 0
if cmd.Flags().Changed(cmdFlagNameAll) {
if all, err = cmd.Flags().GetBool(cmdFlagNameAll); err != nil {
return
}
flags++
}
if cmd.Flags().Changed(cmdFlagNameDescription) {
if description, err = cmd.Flags().GetString(cmdFlagNameDescription); err != nil {
return
}
flags++
}
if byKID = cmd.Flags().Changed(cmdFlagNameKeyID); byKID {
if kid, err = cmd.Flags().GetString(cmdFlagNameKeyID); err != nil {
return
}
flags++
}
if flags > 1 {
err = fmt.Errorf("must only supply one of the flags --all, --description, and --kid but %d were specified", flags)
return
}
if flags == 0 {
err = fmt.Errorf("must supply one of the flags --all, --description, or --kid")
return
}
if !byKID && len(user) == 0 {
err = fmt.Errorf("must supply the username or the --kid flag")
return
}
return
}
func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
var ( var (
provider storage.Provider
ctx = context.Background()
c *model.TOTPConfiguration c *model.TOTPConfiguration
force bool force bool
filename, secret string filename, secret string
@ -431,25 +368,27 @@ func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
img image.Image img image.Image
) )
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
if force, filename, secret, err = storageTOTPGenerateRunEOptsFromFlags(cmd.Flags()); err != nil { if force, filename, secret, err = storageTOTPGenerateRunEOptsFromFlags(cmd.Flags()); err != nil {
return err return err
} }
if _, err = provider.LoadTOTPConfiguration(ctx, args[0]); err == nil && !force { if _, err = ctx.providers.StorageProvider.LoadTOTPConfiguration(ctx, args[0]); err == nil && !force {
return fmt.Errorf("%s already has a TOTP configuration, use --force to overwrite", args[0]) return fmt.Errorf("%s already has a TOTP configuration, use --force to overwrite", args[0])
} else if err != nil && !errors.Is(err, storage.ErrNoTOTPConfiguration) { } else if err != nil && !errors.Is(err, storage.ErrNoTOTPConfiguration) {
return err return err
} }
totpProvider := totp.NewTimeBasedProvider(config.TOTP) totpProvider := totp.NewTimeBasedProvider(ctx.config.TOTP)
if c, err = totpProvider.GenerateCustom(args[0], config.TOTP.Algorithm, secret, config.TOTP.Digits, config.TOTP.Period, config.TOTP.SecretSize); err != nil { if c, err = totpProvider.GenerateCustom(args[0], ctx.config.TOTP.Algorithm, secret, ctx.config.TOTP.Digits, ctx.config.TOTP.Period, ctx.config.TOTP.SecretSize); err != nil {
return err return err
} }
@ -477,7 +416,7 @@ func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
extraInfo = fmt.Sprintf(" and saved it as a PNG image at the path '%s'", filename) extraInfo = fmt.Sprintf(" and saved it as a PNG image at the path '%s'", filename)
} }
if err = provider.SaveTOTPConfiguration(ctx, *c); err != nil { if err = ctx.providers.StorageProvider.SaveTOTPConfiguration(ctx, *c); err != nil {
return err return err
} }
@ -486,47 +425,23 @@ func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
return nil return nil
} }
func storageTOTPGenerateRunEOptsFromFlags(flags *pflag.FlagSet) (force bool, filename, secret string, err error) { // StorageTOTPDeleteRunE is the RunE for the authelia storage user totp delete command.
if force, err = flags.GetBool("force"); err != nil { func (ctx *CmdCtx) StorageTOTPDeleteRunE(cmd *cobra.Command, args []string) (err error) {
return force, filename, secret, err
}
if filename, err = flags.GetString("path"); err != nil {
return force, filename, secret, err
}
if secret, err = flags.GetString("secret"); err != nil {
return force, filename, secret, err
}
secretLength := base32.StdEncoding.WithPadding(base32.NoPadding).DecodedLen(len(secret))
if secret != "" && secretLength < schema.TOTPSecretSizeMinimum {
return force, filename, secret, fmt.Errorf("decoded length of the base32 secret must have "+
"a length of more than %d but '%s' has a decoded length of %d", schema.TOTPSecretSizeMinimum, secret, secretLength)
}
return force, filename, secret, nil
}
func storageTOTPDeleteRunE(cmd *cobra.Command, args []string) (err error) {
var (
provider storage.Provider
ctx = context.Background()
)
user := args[0] user := args[0]
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if _, err = provider.LoadTOTPConfiguration(ctx, user); err != nil { if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
if _, err = ctx.providers.StorageProvider.LoadTOTPConfiguration(ctx, user); err != nil {
return fmt.Errorf("can't delete configuration for user '%s': %+v", user, err) return fmt.Errorf("can't delete configuration for user '%s': %+v", user, err)
} }
if err = provider.DeleteTOTPConfiguration(ctx, user); err != nil { if err = ctx.providers.StorageProvider.DeleteTOTPConfiguration(ctx, user); err != nil {
return fmt.Errorf("can't delete configuration for user '%s': %+v", user, err) return fmt.Errorf("can't delete configuration for user '%s': %+v", user, err)
} }
@ -535,34 +450,30 @@ func storageTOTPDeleteRunE(cmd *cobra.Command, args []string) (err error) {
return nil return nil
} }
func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) { // StorageTOTPExportRunE is the RunE for the authelia storage user totp export command.
func (ctx *CmdCtx) StorageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) {
var ( var (
provider storage.Provider
format, dir string format, dir string
configurations []model.TOTPConfiguration configurations []model.TOTPConfiguration
img image.Image img image.Image
ctx = context.Background()
) )
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if err = checkStorageSchemaUpToDate(ctx, provider); err != nil { if err = ctx.CheckSchemaVersion(); err != nil {
return err return storageWrapCheckSchemaErr(err)
} }
if format, dir, err = storageTOTPExportGetConfigFromFlags(cmd); err != nil { if format, dir, err = flagsGetTOTPExportOptions(cmd.Flags()); err != nil {
return err return err
} }
limit := 10 limit := 10
for page := 0; true; page++ { for page := 0; true; page++ {
if configurations, err = provider.LoadTOTPConfigurations(ctx, limit, page); err != nil { if configurations, err = ctx.providers.StorageProvider.LoadTOTPConfigurations(ctx, limit, page); err != nil {
return err return err
} }
@ -607,56 +518,18 @@ func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) {
return nil return nil
} }
func storageTOTPExportGetConfigFromFlags(cmd *cobra.Command) (format, dir string, err error) { // StorageMigrateHistoryRunE is the RunE for the authelia storage migrate history command.
if format, err = cmd.Flags().GetString(cmdFlagNameFormat); err != nil { func (ctx *CmdCtx) StorageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) {
return "", "", err
}
if dir, err = cmd.Flags().GetString("dir"); err != nil {
return "", "", err
}
switch format {
case storageTOTPExportFormatCSV, storageTOTPExportFormatURI:
break
case storageTOTPExportFormatPNG:
if dir == "" {
dir = utils.RandomString(8, utils.CharSetAlphaNumeric, false)
}
if _, err = os.Stat(dir); !os.IsNotExist(err) {
return "", "", errors.New("output directory must not exist")
}
if err = os.MkdirAll(dir, 0700); err != nil {
return "", "", err
}
default:
return "", "", errors.New("format must be csv, uri, or png")
}
return format, dir, nil
}
func storageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) {
var ( var (
provider storage.Provider
version int version int
migrations []model.Migration migrations []model.Migration
ctx = context.Background()
) )
provider = getStorageProvider()
if provider == nil {
return errNoStorageProvider
}
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if version, err = provider.SchemaVersion(ctx); err != nil { if version, err = ctx.providers.StorageProvider.SchemaVersion(ctx); err != nil {
return err return err
} }
@ -665,7 +538,7 @@ func storageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) {
return return
} }
if migrations, err = provider.SchemaMigrationHistory(ctx); err != nil { if migrations, err = ctx.providers.StorageProvider.SchemaMigrationHistory(ctx); err != nil {
return err return err
} }
@ -682,26 +555,23 @@ func storageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) {
return nil return nil
} }
func newStorageMigrateListRunE(up bool) func(cmd *cobra.Command, args []string) (err error) { // NewStorageMigrateListRunE creates the RunE for the authelia storage migrate list command.
func (ctx *CmdCtx) NewStorageMigrateListRunE(up bool) func(cmd *cobra.Command, args []string) (err error) {
return func(cmd *cobra.Command, args []string) (err error) { return func(cmd *cobra.Command, args []string) (err error) {
var ( var (
provider storage.Provider
ctx = context.Background()
migrations []model.SchemaMigration migrations []model.SchemaMigration
directionStr string directionStr string
) )
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if up { if up {
migrations, err = provider.SchemaMigrationsUp(ctx, 0) migrations, err = ctx.providers.StorageProvider.SchemaMigrationsUp(ctx, 0)
directionStr = "Up" directionStr = "Up"
} else { } else {
migrations, err = provider.SchemaMigrationsDown(ctx, 0) migrations, err = ctx.providers.StorageProvider.SchemaMigrationsDown(ctx, 0)
directionStr = "Down" directionStr = "Down"
} }
@ -723,19 +593,15 @@ func newStorageMigrateListRunE(up bool) func(cmd *cobra.Command, args []string)
} }
} }
func newStorageMigrationRunE(up bool) func(cmd *cobra.Command, args []string) (err error) { // NewStorageMigrationRunE creates the RunE for the authelia storage migrate command.
func (ctx *CmdCtx) NewStorageMigrationRunE(up bool) func(cmd *cobra.Command, args []string) (err error) {
return func(cmd *cobra.Command, args []string) (err error) { return func(cmd *cobra.Command, args []string) (err error) {
var ( var (
provider storage.Provider target int
target int
ctx = context.Background()
) )
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if target, err = cmd.Flags().GetInt(cmdFlagNameTarget); err != nil { if target, err = cmd.Flags().GetInt(cmdFlagNameTarget); err != nil {
@ -746,68 +612,48 @@ func newStorageMigrationRunE(up bool) func(cmd *cobra.Command, args []string) (e
case up: case up:
switch cmd.Flags().Changed(cmdFlagNameTarget) { switch cmd.Flags().Changed(cmdFlagNameTarget) {
case true: case true:
return provider.SchemaMigrate(ctx, true, target) return ctx.providers.StorageProvider.SchemaMigrate(ctx, true, target)
default: default:
return provider.SchemaMigrate(ctx, true, storage.SchemaLatest) return ctx.providers.StorageProvider.SchemaMigrate(ctx, true, storage.SchemaLatest)
} }
default: default:
if !cmd.Flags().Changed(cmdFlagNameTarget) { if !cmd.Flags().Changed(cmdFlagNameTarget) {
return errors.New("you must set a target version") return errors.New("you must set a target version")
} }
if err = storageMigrateDownConfirmDestroy(cmd); err != nil { var confirmed bool
if confirmed, err = termReadConfirmation(cmd.Flags(), cmdFlagNameDestroyData, "Schema Down Migrations may DESTROY data, type 'DESTROY' and press return to continue: ", "DESTROY"); err != nil {
return err return err
} }
return provider.SchemaMigrate(ctx, false, target) if !confirmed {
return errors.New("cancelling down migration due to user not accepting data destruction")
}
return ctx.providers.StorageProvider.SchemaMigrate(ctx, false, target)
} }
} }
} }
func storageMigrateDownConfirmDestroy(cmd *cobra.Command) (err error) { // StorageSchemaInfoRunE is the RunE for the authelia storage schema info command.
var destroy bool func (ctx *CmdCtx) StorageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
if destroy, err = cmd.Flags().GetBool(cmdFlagNameDestroyData); err != nil {
return err
}
if !destroy {
fmt.Printf("Schema Down Migrations may DESTROY data, type 'DESTROY' and press return to continue: ")
var text string
_, _ = fmt.Scanln(&text)
if text != "DESTROY" {
return errors.New("cancelling down migration due to user not accepting data destruction")
}
}
return nil
}
func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
var ( var (
upgradeStr, tablesStr string upgradeStr, tablesStr string
provider storage.Provider
tables []string tables []string
version, latest int version, latest int
ctx = context.Background()
) )
provider = getStorageProvider()
defer func() { defer func() {
_ = provider.Close() _ = ctx.providers.StorageProvider.Close()
}() }()
if version, err = provider.SchemaVersion(ctx); err != nil && err.Error() != "unknown schema state" { if version, err = ctx.providers.StorageProvider.SchemaVersion(ctx); err != nil && err.Error() != "unknown schema state" {
return err return err
} }
if tables, err = provider.SchemaTables(ctx); err != nil { if tables, err = ctx.providers.StorageProvider.SchemaTables(ctx); err != nil {
return err return err
} }
@ -817,7 +663,7 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
tablesStr = strings.Join(tables, ", ") tablesStr = strings.Join(tables, ", ")
} }
if latest, err = provider.SchemaLatestVersion(); err != nil { if latest, err = ctx.providers.StorageProvider.SchemaLatestVersion(); err != nil {
return err return err
} }
@ -832,7 +678,7 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
result storage.EncryptionValidationResult result storage.EncryptionValidationResult
) )
switch result, err = provider.SchemaEncryptionCheckKey(ctx, false); { switch result, err = ctx.providers.StorageProvider.SchemaEncryptionCheckKey(ctx, false); {
case err != nil: case err != nil:
if errors.Is(err, storage.ErrSchemaEncryptionVersionUnsupported) { if errors.Is(err, storage.ErrSchemaEncryptionVersionUnsupported) {
encryption = "unsupported (schema version)" encryption = "unsupported (schema version)"
@ -850,30 +696,9 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
return nil return nil
} }
func checkStorageSchemaUpToDate(ctx context.Context, provider storage.Provider) (err error) { // StorageUserIdentifiersExportRunE is the RunE for the authelia storage user identifiers export command.
var version, latest int func (ctx *CmdCtx) StorageUserIdentifiersExportRunE(cmd *cobra.Command, _ []string) (err error) {
if version, err = provider.SchemaVersion(ctx); err != nil {
return err
}
if latest, err = provider.SchemaLatestVersion(); err != nil {
return err
}
if version != latest {
return fmt.Errorf("schema is version %d which is outdated please migrate to version %d in order to use this command or use an older binary", version, latest)
}
return nil
}
func storageUserIdentifiersExport(cmd *cobra.Command, _ []string) (err error) {
var ( var (
provider storage.Provider
ctx = context.Background()
file string file string
) )
@ -890,7 +715,13 @@ func storageUserIdentifiersExport(cmd *cobra.Command, _ []string) (err error) {
return fmt.Errorf("error occurred opening '%s': %w", file, err) return fmt.Errorf("error occurred opening '%s': %w", file, err)
} }
provider = getStorageProvider() defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
var ( var (
export model.UserOpaqueIdentifiersExport export model.UserOpaqueIdentifiersExport
@ -898,7 +729,7 @@ func storageUserIdentifiersExport(cmd *cobra.Command, _ []string) (err error) {
data []byte data []byte
) )
if export.Identifiers, err = provider.LoadUserOpaqueIdentifiers(ctx); err != nil { if export.Identifiers, err = ctx.providers.StorageProvider.LoadUserOpaqueIdentifiers(ctx); err != nil {
return err return err
} }
@ -919,12 +750,9 @@ func storageUserIdentifiersExport(cmd *cobra.Command, _ []string) (err error) {
return nil return nil
} }
func storageUserIdentifiersImport(cmd *cobra.Command, _ []string) (err error) { // StorageUserIdentifiersImportRunE is the RunE for the authelia storage user identifiers import command.
func (ctx *CmdCtx) StorageUserIdentifiersImportRunE(cmd *cobra.Command, _ []string) (err error) {
var ( var (
provider storage.Provider
ctx = context.Background()
file string file string
stat os.FileInfo stat os.FileInfo
) )
@ -958,10 +786,16 @@ func storageUserIdentifiersImport(cmd *cobra.Command, _ []string) (err error) {
return fmt.Errorf("can't import a file with no data") return fmt.Errorf("can't import a file with no data")
} }
provider = getStorageProvider() defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
for _, opaqueID := range export.Identifiers { for _, opaqueID := range export.Identifiers {
if err = provider.SaveUserOpaqueIdentifier(ctx, opaqueID); err != nil { if err = ctx.providers.StorageProvider.SaveUserOpaqueIdentifier(ctx, opaqueID); err != nil {
return err return err
} }
} }
@ -971,41 +805,26 @@ func storageUserIdentifiersImport(cmd *cobra.Command, _ []string) (err error) {
return nil return nil
} }
func containsIdentifier(identifier model.UserOpaqueIdentifier, identifiers []model.UserOpaqueIdentifier) bool { // StorageUserIdentifiersGenerateRunE is the RunE for the authelia storage user identifiers generate command.
for i := 0; i < len(identifiers); i++ { func (ctx *CmdCtx) StorageUserIdentifiersGenerateRunE(cmd *cobra.Command, _ []string) (err error) {
if identifier.Service == identifiers[i].Service && identifier.SectorID == identifiers[i].SectorID && identifier.Username == identifiers[i].Username {
return true
}
}
return false
}
func storageUserIdentifiersGenerate(cmd *cobra.Command, _ []string) (err error) {
var ( var (
provider storage.Provider
ctx = context.Background()
users, services, sectors []string users, services, sectors []string
) )
provider = getStorageProvider() defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
identifiers, err := provider.LoadUserOpaqueIdentifiers(ctx) if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
identifiers, err := ctx.providers.StorageProvider.LoadUserOpaqueIdentifiers(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) { if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("can't load the existing identifiers: %w", err) return fmt.Errorf("can't load the existing identifiers: %w", err)
} }
if users, err = cmd.Flags().GetStringSlice(cmdFlagNameUsers); err != nil { if users, services, sectors, err = flagsGetUserIdentifiersGenerateOptions(cmd.Flags()); err != nil {
return err
}
if services, err = cmd.Flags().GetStringSlice(cmdFlagNameServices); err != nil {
return err
}
if sectors, err = cmd.Flags().GetStringSlice(cmdFlagNameSectors); err != nil {
return err return err
} }
@ -1043,7 +862,7 @@ func storageUserIdentifiersGenerate(cmd *cobra.Command, _ []string) (err error)
return fmt.Errorf("failed to generate a uuid: %w", err) return fmt.Errorf("failed to generate a uuid: %w", err)
} }
if err = provider.SaveUserOpaqueIdentifier(ctx, identifier); err != nil { if err = ctx.providers.StorageProvider.SaveUserOpaqueIdentifier(ctx, identifier); err != nil {
return fmt.Errorf("failed to save identifier: %w", err) return fmt.Errorf("failed to save identifier: %w", err)
} }
@ -1057,12 +876,9 @@ func storageUserIdentifiersGenerate(cmd *cobra.Command, _ []string) (err error)
return nil return nil
} }
func storageUserIdentifiersAdd(cmd *cobra.Command, args []string) (err error) { // StorageUserIdentifiersAddRunE is the RunE for the authelia storage user identifiers add command.
func (ctx *CmdCtx) StorageUserIdentifiersAddRunE(cmd *cobra.Command, args []string) (err error) {
var ( var (
provider storage.Provider
ctx = context.Background()
service, sector string service, sector string
) )
@ -1106,9 +922,15 @@ func storageUserIdentifiersAdd(cmd *cobra.Command, args []string) (err error) {
} }
} }
provider = getStorageProvider() defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
if err = provider.SaveUserOpaqueIdentifier(ctx, opaqueID); err != nil { if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
if err = ctx.providers.StorageProvider.SaveUserOpaqueIdentifier(ctx, opaqueID); err != nil {
return err return err
} }

View File

@ -1,7 +1,9 @@
package commands package commands
import ( import (
"errors"
"fmt" "fmt"
"io"
"os" "os"
"syscall" "syscall"
@ -24,16 +26,51 @@ func recoverErr(i any) error {
} }
} }
func configFilterExisting(configs []string) (finalConfigs []string) { func flagsGetUserIdentifiersGenerateOptions(flags *pflag.FlagSet) (users, services, sectors []string, err error) {
var err error if users, err = flags.GetStringSlice(cmdFlagNameUsers); err != nil {
return nil, nil, nil, err
for _, c := range configs {
if _, err = os.Stat(c); err == nil || !os.IsNotExist(err) {
finalConfigs = append(finalConfigs, c)
}
} }
return finalConfigs if services, err = flags.GetStringSlice(cmdFlagNameServices); err != nil {
return nil, nil, nil, err
}
if sectors, err = flags.GetStringSlice(cmdFlagNameSectors); err != nil {
return nil, nil, nil, err
}
return users, services, sectors, nil
}
func flagsGetTOTPExportOptions(flags *pflag.FlagSet) (format, dir string, err error) {
if format, err = flags.GetString(cmdFlagNameFormat); err != nil {
return "", "", err
}
if dir, err = flags.GetString("dir"); err != nil {
return "", "", err
}
switch format {
case storageTOTPExportFormatCSV, storageTOTPExportFormatURI:
break
case storageTOTPExportFormatPNG:
if dir == "" {
dir = utils.RandomString(8, utils.CharSetAlphaNumeric, false)
}
if _, err = os.Stat(dir); !os.IsNotExist(err) {
return "", "", errors.New("output directory must not exist")
}
if err = os.MkdirAll(dir, 0700); err != nil {
return "", "", err
}
default:
return "", "", errors.New("format must be csv, uri, or png")
}
return format, dir, nil
} }
//nolint:gocyclo //nolint:gocyclo
@ -102,37 +139,92 @@ func flagsGetRandomCharacters(flags *pflag.FlagSet, flagNameLength, flagNameChar
return utils.RandomString(n, charset, true), nil return utils.RandomString(n, charset, true), nil
} }
func termReadPasswordStrWithPrompt(prompt, flag string) (data string, err error) { func termReadConfirmation(flags *pflag.FlagSet, name, prompt, confirmation string) (confirmed bool, err error) {
var d []byte if confirmed, _ = flags.GetBool(name); confirmed {
return confirmed, nil
}
terminal, fd, state, err := getTerminal(prompt)
if err != nil {
return false, err
}
defer func(fd int, oldState *term.State) {
_ = term.Restore(fd, oldState)
}(fd, state)
var input string
if input, err = terminal.ReadLine(); err != nil {
return false, fmt.Errorf("failed to read from the terminal: %w", err)
}
if input != confirmation {
return false, nil
}
return true, nil
}
func getTerminal(prompt string) (terminal *term.Terminal, fd int, state *term.State, err error) {
fd = int(syscall.Stdin) //nolint:unconvert,nolintlint
if !term.IsTerminal(fd) {
return nil, -1, nil, ErrStdinIsNotTerminal
}
var width, height int
if width, height, err = term.GetSize(int(syscall.Stdout)); err != nil { //nolint:unconvert,nolintlint
return nil, -1, nil, fmt.Errorf("failed to get terminal size: %w", err)
}
state, err = term.MakeRaw(fd)
if err != nil {
return nil, -1, nil, fmt.Errorf("failed to get terminal state: %w", err)
}
c := struct {
io.Reader
io.Writer
}{
os.Stdin,
os.Stdout,
}
terminal = term.NewTerminal(c, prompt)
if err = terminal.SetSize(width, height); err != nil {
return nil, -1, nil, fmt.Errorf("failed to set terminal size: %w", err)
}
return terminal, fd, state, nil
}
func termReadPasswordWithPrompt(prompt, flag string) (password string, err error) {
terminal, fd, state, err := getTerminal("")
if err != nil {
if errors.Is(err, ErrStdinIsNotTerminal) {
switch len(flag) {
case 0:
return "", err
case 1:
return "", fmt.Errorf("you must either use an interactive terminal or use the -%s flag", flag)
default:
return "", fmt.Errorf("you must either use an interactive terminal or use the --%s flag", flag)
}
}
if d, err = termReadPasswordWithPrompt(prompt, flag); err != nil {
return "", err return "", err
} }
return string(d), nil defer func(fd int, oldState *term.State) {
} _ = term.Restore(fd, oldState)
}(fd, state)
func termReadPasswordWithPrompt(prompt, flag string) (data []byte, err error) { if password, err = terminal.ReadPassword(prompt); err != nil {
fd := int(syscall.Stdin) //nolint:unconvert,nolintlint return "", fmt.Errorf("failed to read the input from the terminal: %w", err)
if isTerm := term.IsTerminal(fd); !isTerm {
switch len(flag) {
case 0:
return nil, ErrStdinIsNotTerminal
case 1:
return nil, fmt.Errorf("you must either use an interactive terminal or use the -%s flag", flag)
default:
return nil, fmt.Errorf("you must either use an interactive terminal or use the --%s flag", flag)
}
} }
fmt.Print(prompt) return password, nil
if data, err = term.ReadPassword(fd); err != nil {
return nil, fmt.Errorf("failed to read the input from the terminal: %w", err)
}
fmt.Println("")
return data, nil
} }

View File

@ -4,64 +4,51 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
) )
func newValidateConfigCmd() (cmd *cobra.Command) { func newValidateConfigCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{ cmd = &cobra.Command{
Use: "validate-config", Use: "validate-config",
Short: cmdAutheliaValidateConfigShort, Short: cmdAutheliaValidateConfigShort,
Long: cmdAutheliaValidateConfigLong, Long: cmdAutheliaValidateConfigLong,
Example: cmdAutheliaValidateConfigExample, Example: cmdAutheliaValidateConfigExample,
Args: cobra.NoArgs, Args: cobra.NoArgs,
RunE: cmdValidateConfigRunE, PreRunE: ctx.ChainRunE(
ctx.ConfigLoadRunE,
ctx.ConfigValidateKeysRunE,
ctx.ConfigValidateRunE,
),
RunE: ctx.ValidateConfigRunE,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
cmdWithConfigFlags(cmd, false, []string{"configuration.yml"})
return cmd return cmd
} }
func cmdValidateConfigRunE(cmd *cobra.Command, _ []string) (err error) { // ValidateConfigRunE is the RunE for the authelia validate-config command.
var ( func (ctx *CmdCtx) ValidateConfigRunE(_ *cobra.Command, _ []string) (err error) {
configs []string
val *schema.StructValidator
)
if configs, err = cmd.Flags().GetStringSlice(cmdFlagNameConfig); err != nil {
return err
}
config, val, err = loadConfig(configs, true, true, configuration.NewFileFiltersDefault()...)
if err != nil {
return fmt.Errorf("error occurred loading configuration: %v", err)
}
switch { switch {
case val.HasErrors(): case ctx.cconfig.validator.HasErrors():
fmt.Println("Configuration parsed and loaded with errors:") fmt.Println("Configuration parsed and loaded with errors:")
fmt.Println("") fmt.Println("")
for _, err = range val.Errors() { for _, err = range ctx.cconfig.validator.Errors() {
fmt.Printf("\t - %v\n", err) fmt.Printf("\t - %v\n", err)
} }
fmt.Println("") fmt.Println("")
if !val.HasWarnings() { if !ctx.cconfig.validator.HasWarnings() {
break break
} }
fallthrough fallthrough
case val.HasWarnings(): case ctx.cconfig.validator.HasWarnings():
fmt.Println("Configuration parsed and loaded with warnings:") fmt.Println("Configuration parsed and loaded with warnings:")
fmt.Println("") fmt.Println("")
for _, err = range val.Warnings() { for _, err = range ctx.cconfig.validator.Warnings() {
fmt.Printf("\t - %v\n", err) fmt.Printf("\t - %v\n", err)
} }

View File

@ -86,15 +86,13 @@ func loadSources(ko *koanf.Koanf, val *schema.StructValidator, sources ...Source
} }
for _, source := range sources { for _, source := range sources {
err := source.Load(val) if err = source.Load(val); err != nil {
if err != nil {
val.Push(fmt.Errorf("failed to load configuration from %s source: %+v", source.Name(), err)) val.Push(fmt.Errorf("failed to load configuration from %s source: %+v", source.Name(), err))
continue continue
} }
err = source.Merge(ko, val) if err = source.Merge(ko, val); err != nil {
if err != nil {
val.Push(fmt.Errorf("failed to merge configuration from %s source: %+v", source.Name(), err)) val.Push(fmt.Errorf("failed to merge configuration from %s source: %+v", source.Name(), err))
continue continue

View File

@ -224,7 +224,9 @@ func NewDefaultSourcesFiltered(files []string, filters []FileFilter, prefix, del
// NewDefaultSourcesWithDefaults returns a slice of Source configured to load from specified YAML files with additional sources. // NewDefaultSourcesWithDefaults returns a slice of Source configured to load from specified YAML files with additional sources.
func NewDefaultSourcesWithDefaults(files []string, filters []FileFilter, prefix, delimiter string, defaults Source, additionalSources ...Source) (sources []Source) { func NewDefaultSourcesWithDefaults(files []string, filters []FileFilter, prefix, delimiter string, defaults Source, additionalSources ...Source) (sources []Source) {
sources = []Source{defaults} if defaults != nil {
sources = []Source{defaults}
}
if len(filters) == 0 { if len(filters) == 0 {
sources = append(sources, NewDefaultSources(files, prefix, delimiter, additionalSources...)...) sources = append(sources, NewDefaultSources(files, prefix, delimiter, additionalSources...)...)

View File

@ -21,28 +21,28 @@ type YAMLFileSource struct {
filters []FileFilter filters []FileFilter
} }
// EnvironmentSource is a configuration Source which loads values from the environment. // EnvironmentSource is a configuration configuration.Source which loads values from the environment.
type EnvironmentSource struct { type EnvironmentSource struct {
koanf *koanf.Koanf koanf *koanf.Koanf
prefix string prefix string
delimiter string delimiter string
} }
// SecretsSource loads environment variables that have a value pointing to a file. // SecretsSource is a configuration.Source which loads environment variables that have a value pointing to a file.
type SecretsSource struct { type SecretsSource struct {
koanf *koanf.Koanf koanf *koanf.Koanf
prefix string prefix string
delimiter string delimiter string
} }
// CommandLineSource loads configuration from the command line flags. // CommandLineSource is a configuration.Source which loads configuration from the command line flags.
type CommandLineSource struct { type CommandLineSource struct {
koanf *koanf.Koanf koanf *koanf.Koanf
flags *pflag.FlagSet flags *pflag.FlagSet
callback func(flag *pflag.Flag) (string, any) callback func(flag *pflag.Flag) (string, any)
} }
// MapSource loads configuration from the command line flags. // MapSource is a configuration.Source which loads configuration from the command line flags.
type MapSource struct { type MapSource struct {
m map[string]any m map[string]any
koanf *koanf.Koanf koanf *koanf.Koanf

View File

@ -84,18 +84,6 @@ func (s *CLISuite) TestShouldFailValidateConfig() {
s.Assert().Contains(output, "failed to load configuration from yaml file(/config/invalid.yml) source: open /config/invalid.yml: no such file or directory") s.Assert().Contains(output, "failed to load configuration from yaml file(/config/invalid.yml) source: open /config/invalid.yml: no such file or directory")
} }
func (s *CLISuite) TestShouldHashPasswordArgon2idLegacy() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "hash-password", "test", "-m", "32"})
s.Assert().NoError(err)
s.Assert().Contains(output, "Digest: $argon2id$v=19$m=32768,t=3,p=4$")
}
func (s *CLISuite) TestShouldHashPasswordSHA512Legacy() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "hash-password", "test", "-z"})
s.Assert().NoError(err)
s.Assert().Contains(output, "Digest: $6$rounds=50000")
}
func (s *CLISuite) TestShouldHashPasswordArgon2() { func (s *CLISuite) TestShouldHashPasswordArgon2() {
var ( var (
output string output string
@ -782,18 +770,6 @@ func (s *CLISuite) TestShouldNotGenerateRSAWithBadCAFileContent() {
s.Assert().Contains(output, "Error: could not parse certificate from file '/tmp/ca.public.bad.crt': failed to parse PEM block containing the key\n") s.Assert().Contains(output, "Error: could not parse certificate from file '/tmp/ca.public.bad.crt': failed to parse PEM block containing the key\n")
} }
func (s *CLISuite) TestStorageShouldShowErrWithoutConfig() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info"})
s.Assert().EqualError(err, "exit status 1")
s.Assert().Contains(output, "Error: storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided, storage: option 'encryption_key' is required\n")
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "history"})
s.Assert().EqualError(err, "exit status 1")
s.Assert().Contains(output, "Error: storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided, storage: option 'encryption_key' is required\n")
}
func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() { func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {
_ = os.Remove("/tmp/db.sqlite3") _ = os.Remove("/tmp/db.sqlite3")
@ -804,8 +780,7 @@ func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {
s.Assert().Regexp(pattern, output) s.Assert().Regexp(pattern, output)
patternOutdated := regexp.MustCompile(`Error: schema is version \d+ which is outdated please migrate to version \d+ in order to use this command or use an older binary`) patternOutdated := regexp.MustCompile(`Error: command requires the use of a up to date schema version: storage schema outdated: version \d+ is outdated please migrate to version \d+ in order to use this command or use an older binary`)
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "user", "totp", "export", "--config=/config/configuration.storage.yml"}) output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "user", "totp", "export", "--config=/config/configuration.storage.yml"})
s.Assert().EqualError(err, "exit status 1") s.Assert().EqualError(err, "exit status 1")
s.Assert().Regexp(patternOutdated, output) s.Assert().Regexp(patternOutdated, output)
@ -815,8 +790,8 @@ func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {
s.Assert().Regexp(patternOutdated, output) s.Assert().Regexp(patternOutdated, output)
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--config=/config/configuration.storage.yml"}) output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--config=/config/configuration.storage.yml"})
s.Assert().NoError(err) s.Assert().EqualError(err, "exit status 1")
s.Assert().Contains(output, "Storage Encryption Key Validation: FAILURE\n\n\tCause: The schema version doesn't support encryption.\n") s.Assert().Contains(output, "Error: command requires the use of a up to date schema version: storage schema outdated: version 0 is outdated please migrate to version 7 in order to use this command or use an older binary\n")
output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "down", "--target=0", "--destroy-data", "--config=/config/configuration.storage.yml"}) output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "down", "--target=0", "--destroy-data", "--config=/config/configuration.storage.yml"})
s.Assert().EqualError(err, "exit status 1") s.Assert().EqualError(err, "exit status 1")