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?
multiple: true
options:
- v4.37.5
- v4.37.4
- v4.37.3
- v4.37.2

View File

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

View File

@ -1,7 +1,7 @@
---
title: "Versioning Policy"
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
images: []
aliases:

View File

@ -41,8 +41,8 @@ authelia --config /etc/authelia/config/
### Options
```
-c, --config strings configuration files to load
--config.experimental.filters strings Applies filters in order to the configuration file before the YAML parser. Options are 'template', 'expand-env'
-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'
-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 build-info](authelia_build-info.md) - Show the build information of Authelia
* [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 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
```
### 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
* [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
```
-c, --config strings configuration files to load (default [configuration.yml])
--groups strings the groups of the subject
-h, --help help for check-policy
--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
```
### 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
* [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
```
### 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
* [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown)

View File

@ -34,6 +34,13 @@ authelia crypto --help
-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
* [authelia](authelia.md) - authelia untagged-unknown-dirty (master, unknown)

View File

@ -34,6 +34,13 @@ authelia crypto certificate --help
-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
* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations

View File

@ -34,6 +34,13 @@ authelia crypto certificate ecdsa --help
-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
* [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
-c, --common-name string certificate common name
-n, --common-name string certificate common name
--country strings certificate country
-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
@ -59,6 +59,13 @@ authelia crypto certificate ecdsa generate --help
-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
* [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
```
-c, --common-name string certificate common name
-n, --common-name string certificate common name
--country strings certificate country
-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
@ -54,6 +54,13 @@ authelia crypto certificate ecdsa request --help
-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
* [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
```
### 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
* [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
-c, --common-name string certificate common name
-n, --common-name string certificate common name
--country strings certificate country
-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)
@ -58,6 +58,13 @@ authelia crypto certificate ed25519 request --help
-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
* [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
```
-c, --common-name string certificate common name
-n, --common-name string certificate common name
--country strings certificate country
-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)
@ -53,6 +53,13 @@ authelia crypto certificate ed25519 request --help
-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
* [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
```
### 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
* [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)
--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
-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)
@ -59,6 +59,13 @@ authelia crypto certificate rsa generate --help
-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
* [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)
-c, --common-name string certificate common name
-n, --common-name string certificate common name
--country strings certificate country
-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)
@ -54,6 +54,13 @@ authelia crypto certificate rsa request --help
-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
* [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
```
### 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
* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations

View File

@ -37,7 +37,6 @@ authelia crypto hash generate --help
### Options
```
-c, --config strings configuration files to load (default [configuration.yml])
-h, --help help for generate
--no-confirm skip the password confirmation 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)
```
### 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
* [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
```
-c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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)
-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'
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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

View File

@ -43,13 +43,14 @@ authelia crypto hash generate bcrypt --help
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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)
-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'
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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

View File

@ -44,13 +44,14 @@ authelia crypto hash generate pbkdf2 --help
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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)
-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'
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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

View File

@ -46,13 +46,14 @@ authelia crypto hash generate scrypt --help
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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)
-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'
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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

View File

@ -44,13 +44,14 @@ authelia crypto hash generate sha2crypt --help
### Options inherited from parent commands
```
-c, --config strings configuration files to load (default [configuration.yml])
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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)
-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'
--no-confirm skip the password confirmation prompt
--password string manually supply the password rather than using the terminal prompt
--random uses a randomly generated password
--random.characters string sets the explicit characters for the random string
--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

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
```
### 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
* [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
```
### 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
* [authelia crypto](authelia_crypto.md) - Perform cryptographic operations

View File

@ -38,6 +38,13 @@ authelia crypto pair ecdsa --help
-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
* [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
```
### 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
* [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
```
### 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
* [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
```
### 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
* [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
```
### 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
* [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
```
### 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
* [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
-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
-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
* [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
```
-c, --config strings configuration files to load (default [configuration.yml])
--encryption-key string the storage encryption key to use
-h, --help help for storage
--mysql.database string the MySQL database name (default "authelia")
@ -54,6 +53,13 @@ authelia storage --help
--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
* [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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--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])
--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
--mysql.database string the MySQL database name (default "authelia")
--mysql.host string the MySQL hostname

View File

@ -37,8 +37,14 @@ authelia validate-config --config config.yml
### 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

View File

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

View File

@ -10,12 +10,10 @@ import (
"github.com/spf13/cobra"
"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"
)
func newAccessControlCommand() (cmd *cobra.Command) {
func newAccessControlCommand(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "access-control",
Short: cmdAutheliaAccessControlShort,
@ -26,25 +24,26 @@ func newAccessControlCommand() (cmd *cobra.Command) {
}
cmd.AddCommand(
newAccessControlCheckCommand(),
newAccessControlCheckCommand(ctx),
)
return cmd
}
func newAccessControlCheckCommand() (cmd *cobra.Command) {
func newAccessControlCheckCommand(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "check-policy",
Short: cmdAutheliaAccessControlCheckPolicyShort,
Long: cmdAutheliaAccessControlCheckPolicyLong,
Example: cmdAutheliaAccessControlCheckPolicyExample,
RunE: accessControlCheckRunE,
PreRunE: ctx.ChainRunE(
ctx.ConfigLoadRunE,
),
RunE: ctx.AccessControlCheckRunE,
DisableAutoGenTag: true,
}
cmdWithConfigFlags(cmd, false, []string{"configuration.yml"})
cmd.Flags().String("url", "", "the url of the object")
cmd.Flags().String("method", "GET", "the HTTP method of the object")
cmd.Flags().String("username", "", "the username of the subject")
@ -55,36 +54,14 @@ func newAccessControlCheckCommand() (cmd *cobra.Command) {
return cmd
}
func accessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
configs, err := cmd.Flags().GetStringSlice(cmdFlagNameConfig)
if err != nil {
return err
}
func (ctx *CmdCtx) AccessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
validator.ValidateAccessControl(ctx.config, ctx.cconfig.validator)
sources := make([]configuration.Source, len(configs)+2)
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() {
if ctx.cconfig.validator.HasErrors() || ctx.cconfig.validator.HasWarnings() {
return errors.New("your configuration has errors")
}
authorizer := authorization.NewAuthorizer(accessControlConfig)
authorizer := authorization.NewAuthorizer(ctx.config)
subject, object, err := getSubjectAndObjectFromFlags(cmd)
if err != nil {
@ -94,7 +71,7 @@ func accessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
results := authorizer.GetRuleMatchResults(subject, object)
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
}
@ -104,7 +81,7 @@ func accessControlCheckRunE(cmd *cobra.Command, _ []string) (err error) {
return err
}
accessControlCheckWriteOutput(object, subject, results, accessControlConfig.AccessControl.DefaultPolicy, verbose)
accessControlCheckWriteOutput(object, subject, results, ctx.config.AccessControl.DefaultPolicy, verbose)
return nil
}

View File

@ -9,13 +9,13 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
func newBuildInfoCmd() (cmd *cobra.Command) {
func newBuildInfoCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: "build-info",
Short: cmdAutheliaBuildInfoShort,
Long: cmdAutheliaBuildInfoLong,
Example: cmdAutheliaBuildInfoExample,
RunE: cmdBuildInfoRunE,
RunE: ctx.BuildInfoRunE,
Args: cobra.NoArgs,
DisableAutoGenTag: true,
@ -24,7 +24,8 @@ func newBuildInfoCmd() (cmd *cobra.Command) {
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,
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`
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 (
@ -544,8 +532,8 @@ const (
cmdFlagNameKeySize = "key-size"
cmdFlagNameSaltSize = "salt-size"
cmdFlagNameProfile = "profile"
cmdFlagNameSHA512 = "sha512"
cmdFlagNameConfig = "config"
cmdFlagNameConfig = "config"
cmdFlagNameConfigExpFilters = "config.experimental.filters"
@ -598,7 +586,6 @@ const (
)
const (
cmdUseHashPassword = "hash-password [flags] -- [password]"
cmdUseHash = "hash"
cmdUseHashArgon2 = "argon2"
cmdUseHashSHA2Crypt = "sha2crypt"
@ -627,7 +614,8 @@ const (
)
var (
errNoStorageProvider = errors.New("no storage provider configured")
errStorageSchemaOutdated = errors.New("storage schema outdated")
errStorageSchemaIncompatible = errors.New("storage schema incompatible")
)
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"
)
func newCryptoCmd() (cmd *cobra.Command) {
func newCryptoCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseCrypto,
Short: cmdAutheliaCryptoShort,
@ -27,47 +27,35 @@ func newCryptoCmd() (cmd *cobra.Command) {
}
cmd.AddCommand(
newCryptoRandCmd(),
newCryptoCertificateCmd(),
newCryptoHashCmd(),
newCryptoPairCmd(),
newCryptoRandCmd(ctx),
newCryptoCertificateCmd(ctx),
newCryptoHashCmd(ctx),
newCryptoPairCmd(ctx),
)
return cmd
}
func newCryptoRandCmd() (cmd *cobra.Command) {
func newCryptoRandCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseRand,
Short: cmdAutheliaCryptoRandShort,
Long: cmdAutheliaCryptoRandLong,
Example: cmdAutheliaCryptoRandExample,
Args: cobra.NoArgs,
RunE: func(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
},
RunE: ctx.CryptoRandRunE,
DisableAutoGenTag: true,
}
cmd.Flags().StringP(cmdFlagNameCharSet, "c", cmdFlagValueCharSet, cmdFlagUsageCharset)
cmd.Flags().StringP(cmdFlagNameCharSet, "x", cmdFlagValueCharSet, cmdFlagUsageCharset)
cmd.Flags().String(cmdFlagNameCharacters, "", cmdFlagUsageCharacters)
cmd.Flags().IntP(cmdFlagNameLength, "n", 72, cmdFlagUsageLength)
return cmd
}
func newCryptoCertificateCmd() (cmd *cobra.Command) {
func newCryptoCertificateCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseCertificate,
Short: cmdAutheliaCryptoCertificateShort,
@ -79,15 +67,15 @@ func newCryptoCertificateCmd() (cmd *cobra.Command) {
}
cmd.AddCommand(
newCryptoCertificateSubCmd(cmdUseRSA),
newCryptoCertificateSubCmd(cmdUseECDSA),
newCryptoCertificateSubCmd(cmdUseEd25519),
newCryptoCertificateSubCmd(ctx, cmdUseRSA),
newCryptoCertificateSubCmd(ctx, cmdUseECDSA),
newCryptoCertificateSubCmd(ctx, cmdUseEd25519),
)
return cmd
}
func newCryptoCertificateSubCmd(use string) (cmd *cobra.Command) {
func newCryptoCertificateSubCmd(ctx *CmdCtx, use string) (cmd *cobra.Command) {
useFmt := fmtCryptoCertificateUse(use)
cmd = &cobra.Command{
@ -100,16 +88,16 @@ func newCryptoCertificateSubCmd(use string) (cmd *cobra.Command) {
DisableAutoGenTag: true,
}
cmd.AddCommand(newCryptoGenerateCmd(cmdUseCertificate, use), newCryptoCertificateRequestCmd(use))
cmd.AddCommand(newCryptoGenerateCmd(ctx, cmdUseCertificate, use), newCryptoCertificateRequestCmd(ctx, use))
return cmd
}
func newCryptoCertificateRequestCmd(algorithm string) (cmd *cobra.Command) {
func newCryptoCertificateRequestCmd(ctx *CmdCtx, algorithm string) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseRequest,
Args: cobra.NoArgs,
RunE: cryptoCertificateRequestRunE,
RunE: ctx.CryptoCertificateRequestRunE,
DisableAutoGenTag: true,
}
@ -141,7 +129,7 @@ func newCryptoCertificateRequestCmd(algorithm string) (cmd *cobra.Command) {
return cmd
}
func newCryptoPairCmd() (cmd *cobra.Command) {
func newCryptoPairCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUsePair,
Short: cmdAutheliaCryptoPairShort,
@ -153,15 +141,15 @@ func newCryptoPairCmd() (cmd *cobra.Command) {
}
cmd.AddCommand(
newCryptoPairSubCmd(cmdUseRSA),
newCryptoPairSubCmd(cmdUseECDSA),
newCryptoPairSubCmd(cmdUseEd25519),
newCryptoPairSubCmd(ctx, cmdUseRSA),
newCryptoPairSubCmd(ctx, cmdUseECDSA),
newCryptoPairSubCmd(ctx, cmdUseEd25519),
)
return cmd
}
func newCryptoPairSubCmd(use string) (cmd *cobra.Command) {
func newCryptoPairSubCmd(ctx *CmdCtx, use string) (cmd *cobra.Command) {
var (
example, useFmt string
)
@ -183,21 +171,21 @@ func newCryptoPairSubCmd(use string) (cmd *cobra.Command) {
Long: fmt.Sprintf(cmdAutheliaCryptoPairSubLong, useFmt, useFmt),
Example: example,
Args: cobra.NoArgs,
RunE: cryptoGenerateRunE,
RunE: ctx.CryptoGenerateRunE,
DisableAutoGenTag: true,
}
cmd.AddCommand(newCryptoGenerateCmd(cmdUsePair, use))
cmd.AddCommand(newCryptoGenerateCmd(ctx, cmdUsePair, use))
return cmd
}
func newCryptoGenerateCmd(category, algorithm string) (cmd *cobra.Command) {
func newCryptoGenerateCmd(ctx *CmdCtx, category, algorithm string) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseGenerate,
Args: cobra.NoArgs,
RunE: cryptoGenerateRunE,
RunE: ctx.CryptoGenerateRunE,
DisableAutoGenTag: true,
}
@ -253,7 +241,23 @@ func newCryptoGenerateCmd(category, algorithm string) (cmd *cobra.Command) {
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 (
privateKey any
)
@ -263,13 +267,14 @@ func cryptoGenerateRunE(cmd *cobra.Command, args []string) (err error) {
}
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 (
privateKey any
)
@ -340,7 +345,8 @@ func cryptoCertificateRequestRunE(cmd *cobra.Command, _ []string) (err error) {
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 (
template, caCertificate, parent *x509.Certificate
publicKey, caPrivateKey, signatureKey any
@ -432,7 +438,8 @@ func cryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string, privateKey an
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 (
privateKeyPath, publicKeyPath string
pkcs8 bool

View File

@ -7,69 +7,13 @@ import (
"github.com/go-crypt/crypt"
"github.com/go-crypt/crypt/algorithm"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator"
)
func newHashPasswordCmd() (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) {
func newCryptoHashCmd(ctx *CmdCtx) (cmd *cobra.Command) {
cmd = &cobra.Command{
Use: cmdUseHash,
Short: cmdAutheliaCryptoHashShort,
@ -81,270 +25,15 @@ func newCryptoHashCmd() (cmd *cobra.Command) {
}
cmd.AddCommand(
newCryptoHashValidateCmd(),
newCryptoHashGenerateCmd(),
newCryptoHashValidateCmd(ctx),
newCryptoHashGenerateCmd(ctx),
)
return cmd
}
func newCryptoHashGenerateCmd() (cmd *cobra.Command) {
cmd = &cobra.Command{
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{}{
func newCryptoHashGenerateCmd(ctx *CmdCtx) (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,
@ -367,44 +56,246 @@ func cmdCryptoHashGetConfig(algorithm string, configs []string, flags *pflag.Fla
prefixFilePassword + ".scrypt.salt_length": schema.DefaultPasswordConfig.SCrypt.SaltLength,
}
sources := configuration.NewDefaultSourcesWithDefaults(
configs,
nil,
configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter,
configuration.NewMapSource(mapDefaults),
configuration.NewCommandLineSourceWithMapping(flags, flagsMap, false, false),
cmd = &cobra.Command{
Use: cmdUseGenerate,
Short: cmdAutheliaCryptoHashGenerateShort,
Long: cmdAutheliaCryptoHashGenerateLong,
Example: cmdAutheliaCryptoHashGenerateExample,
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 != "" {
alg := map[string]interface{}{prefixFilePassword + ".algorithm": algorithm}
sources = append(sources, configuration.NewMapSource(alg))
if password, _, err = cmdCryptoHashGetPassword(cmd, args, false, false); err != nil {
return fmt.Errorf("error occurred trying to obtain the password: %w", err)
}
val := schema.NewStructValidator()
if _, err = configuration.LoadAdvanced(val, prefixFilePassword, &c, sources...); err != nil {
return schema.Password{}, fmt.Errorf("error occurred loading configuration: %w", err)
if len(password) == 0 {
return fmt.Errorf("no password provided")
}
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 {
for i, e := range errs {
if i == 0 {
err = e
continue
}
return nil
}
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) {
@ -430,18 +321,15 @@ func cmdCryptoHashGetPassword(cmd *cobra.Command, args []string, useArgs, useRan
}
var (
data []byte
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)
return
}
password = string(data)
if cmd.Use == fmt.Sprintf(cmdUseFmtValidate, cmdUseValidate) {
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 data, err = termReadPasswordWithPrompt("Confirm Password: ", ""); err != nil {
var confirm string
if confirm, err = termReadPasswordWithPrompt("Confirm Password: ", ""); err != nil {
return
}
if password != string(data) {
if password != confirm {
fmt.Println("")
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
}
func cmdFlagConfig(cmd *cobra.Command) {
cmd.PersistentFlags().StringSliceP(cmdFlagNameConfig, "c", []string{"configuration.yml"}, "configuration files to load")
}
func cmdFlagPassword(cmd *cobra.Command, noConfirm bool) {
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) {
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().StringSlice(cmdFlagNameOrganizationalUnit, nil, "certificate organizational unit")
cmd.Flags().StringSlice(cmdFlagNameCountry, nil, "certificate country")

View File

@ -6,3 +6,6 @@ import (
// ErrStdinIsNotTerminal is returned when Stdin is not an interactive 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
import (
"crypto/x509"
"encoding/base32"
"errors"
"fmt"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
"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/spf13/pflag"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/model"
"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 {
case config.Storage.Local == nil:
return getStorageProviderWithPool(nil)
default:
caCertPool, _, _ := utils.NewX509CertPool(config.CertificatesDirectory)
return getStorageProviderWithPool(caCertPool)
}
}
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)
case ctx.config.Storage.PostgreSQL != nil:
return storage.NewPostgreSQLProvider(ctx.config, ctx.trusted)
case ctx.config.Storage.MySQL != nil:
return storage.NewMySQLProvider(ctx.config, ctx.trusted)
case ctx.config.Storage.Local != nil:
return storage.NewSQLiteProvider(ctx.config)
default:
return nil
}
}
func getProviders() (providers middlewares.Providers, warnings []error, errors []error) {
// TODO: Adjust this so the CertPool can be used like a provider.
caCertPool, warnings, errors := utils.NewX509CertPool(config.CertificatesDirectory)
if len(warnings) != 0 || len(errors) != 0 {
return providers, warnings, errors
func containsIdentifier(identifier model.UserOpaqueIdentifier, identifiers []model.UserOpaqueIdentifier) bool {
for i := 0; i < len(identifiers); i++ {
if identifier.Service == identifiers[i].Service && identifier.SectorID == identifiers[i].SectorID && identifier.Username == identifiers[i].Username {
return true
}
}
storageProvider := getStorageProviderWithPool(caCertPool)
var (
userProvider authentication.UserProvider
err error
)
switch {
case config.AuthenticationBackend.File != nil:
userProvider = authentication.NewFileUserProvider(config.AuthenticationBackend.File)
case config.AuthenticationBackend.LDAP != nil:
userProvider = authentication.NewLDAPUserProvider(config.AuthenticationBackend, caCertPool)
}
templatesProvider, err := templates.New(templates.Config{EmailTemplatesPath: config.Notifier.TemplatePath})
if err != nil {
errors = append(errors, err)
}
var notifier notification.Notifier
switch {
case config.Notifier.SMTP != nil:
notifier = notification.NewSMTPNotifier(config.Notifier.SMTP, caCertPool, templatesProvider)
case config.Notifier.FileSystem != nil:
notifier = notification.NewFileNotifier(*config.Notifier.FileSystem)
}
ntpProvider := ntp.NewProvider(&config.NTP)
clock := utils.RealClock{}
authorizer := authorization.NewAuthorizer(config)
sessionProvider := session.NewProvider(config.Session, caCertPool)
regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)
oidcProvider, err := oidc.NewOpenIDConnectProvider(config.IdentityProviders.OIDC, storageProvider)
if err != nil {
errors = append(errors, err)
}
totpProvider := totp.NewTimeBasedProvider(config.TOTP)
ppolicyProvider := middlewares.NewPasswordPolicyProvider(config.PasswordPolicy)
var metricsProvider metrics.Provider
if config.Telemetry.Metrics.Enabled {
metricsProvider = metrics.NewPrometheus()
}
return middlewares.Providers{
Authorizer: authorizer,
UserProvider: userProvider,
Regulator: regulator,
OpenIDConnect: oidcProvider,
StorageProvider: storageProvider,
Metrics: metricsProvider,
NTP: ntpProvider,
Notifier: notifier,
SessionProvider: sessionProvider,
Templates: templatesProvider,
TOTP: totpProvider,
PasswordPolicy: ppolicyProvider,
}, warnings, errors
return false
}
func storageWrapCheckSchemaErr(err error) error {
switch {
case errors.Is(err, errStorageSchemaIncompatible):
return fmt.Errorf("command requires the use of a compatibe schema version: %w", err)
case errors.Is(err, errStorageSchemaOutdated):
return fmt.Errorf("command requires the use of a up to date schema version: %w", err)
default:
return err
}
}
func storageTOTPGenerateRunEOptsFromFlags(flags *pflag.FlagSet) (force bool, filename, secret string, err error) {
if force, err = flags.GetBool("force"); err != nil {
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 storageWebauthnDeleteRunEOptsFromFlags(flags *pflag.FlagSet, args []string) (all, byKID bool, description, kid, user string, err error) {
if len(args) != 0 {
user = args[0]
}
f := 0
if flags.Changed(cmdFlagNameAll) {
if all, err = flags.GetBool(cmdFlagNameAll); err != nil {
return
}
f++
}
if flags.Changed(cmdFlagNameDescription) {
if description, err = flags.GetString(cmdFlagNameDescription); err != nil {
return
}
f++
}
if byKID = flags.Changed(cmdFlagNameKeyID); byKID {
if kid, err = flags.GetString(cmdFlagNameKeyID); err != nil {
return
}
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"
"github.com/stretchr/testify/assert"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func TestGetStorageProvider(t *testing.T) {
config = &schema.Configuration{}
assert.Nil(t, getStorageProvider())
assert.Nil(t, getStorageProvider(NewCmdCtx()))
}

View File

@ -1,7 +1,6 @@
package commands
import (
"context"
"fmt"
"net"
"os"
@ -11,15 +10,11 @@ import (
"syscall"
"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/valyala/fasthttp"
"golang.org/x/sync/errgroup"
"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/middlewares"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/server"
"github.com/authelia/authelia/v4/internal/utils"
@ -27,6 +22,8 @@ import (
// NewRootCmd returns a new Root Cmd.
func NewRootCmd() (cmd *cobra.Command) {
ctx := NewCmdCtx()
version := utils.Version()
cmd = &cobra.Command{
@ -36,68 +33,70 @@ func NewRootCmd() (cmd *cobra.Command) {
Example: cmdAutheliaExample,
Version: version,
Args: cobra.NoArgs,
PreRun: newCmdWithConfigPreRun(true, true, true),
Run: cmdRootRun,
PreRunE: ctx.ChainRunE(
ctx.ConfigEnsureExistsRunE,
ctx.ConfigLoadRunE,
ctx.ConfigValidateKeysRunE,
ctx.ConfigValidateRunE,
ctx.ConfigValidateLogRunE,
),
RunE: ctx.RootRunE,
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(
newAccessControlCommand(),
newBuildInfoCmd(),
newCryptoCmd(),
newHashPasswordCmd(),
newStorageCmd(),
newValidateConfigCmd(),
newAccessControlCommand(ctx),
newBuildInfoCmd(ctx),
newCryptoCmd(ctx),
newStorageCmd(ctx),
newValidateConfigCmd(ctx),
)
return cmd
}
func cmdRootRun(_ *cobra.Command, _ []string) {
logger := logging.Logger()
logger.Infof("Authelia %s is starting", utils.Version())
func (ctx *CmdCtx) RootRunE(_ *cobra.Command, _ []string) (err error) {
ctx.log.Infof("Authelia %s is starting", utils.Version())
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 {
logger.Fatalf("Cannot initialize logger: %v", err)
if err = logging.InitializeLogger(ctx.config.Log, true); err != nil {
ctx.log.Fatalf("Cannot initialize logger: %v", err)
}
providers, warnings, errors := getProviders()
if len(warnings) != 0 {
for _, err := range warnings {
logger.Warn(err)
warns, errs := ctx.LoadProviders()
if len(warns) != 0 {
for _, err = range warns {
ctx.log.Warn(err)
}
}
if len(errors) != 0 {
for _, err := range errors {
logger.Error(err)
if len(errs) != 0 {
for _, err = range errs {
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.
func runServices(config *schema.Configuration, providers middlewares.Providers, log *logrus.Logger) {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
func runServices(ctx *CmdCtx) {
defer ctx.cancel()
quit := make(chan os.Signal, 1)
@ -105,26 +104,24 @@ func runServices(config *schema.Configuration, providers middlewares.Providers,
defer signal.Stop(quit)
g, ctx := errgroup.WithContext(ctx)
var (
mainServer, metricsServer *fasthttp.Server
mainListener, metricsListener net.Listener
)
g.Go(func() (err error) {
ctx.group.Go(func() (err error) {
defer func() {
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
}
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
}
@ -132,23 +129,23 @@ func runServices(config *schema.Configuration, providers middlewares.Providers,
return nil
})
g.Go(func() (err error) {
if providers.Metrics == nil {
ctx.group.Go(func() (err error) {
if ctx.providers.Metrics == nil {
return nil
}
defer func() {
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
}
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
}
@ -156,10 +153,10 @@ func runServices(config *schema.Configuration, providers middlewares.Providers,
return nil
})
if config.AuthenticationBackend.File != nil && config.AuthenticationBackend.File.Watch {
provider := providers.UserProvider.(*authentication.FileUserProvider)
if watcher, err := runServiceFileWatcher(g, log, config.AuthenticationBackend.File.Path, provider); err != nil {
log.WithError(err).Errorf("Error opening file watcher")
if ctx.config.AuthenticationBackend.File != nil && ctx.config.AuthenticationBackend.File.Watch {
provider := ctx.providers.UserProvider.(*authentication.FileUserProvider)
if watcher, err := runServiceFileWatcher(ctx, ctx.config.AuthenticationBackend.File.Path, provider); err != nil {
ctx.log.WithError(err).Errorf("Error opening file watcher")
} else {
defer watcher.Close()
}
@ -169,38 +166,38 @@ func runServices(config *schema.Configuration, providers middlewares.Providers,
case s := <-quit:
switch s {
case syscall.SIGINT:
log.Debugf("Shutdown started due to SIGINT")
ctx.log.Debugf("Shutdown started due to SIGINT")
case syscall.SIGQUIT:
log.Debugf("Shutdown started due to SIGQUIT")
ctx.log.Debugf("Shutdown started due to SIGQUIT")
}
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
if mainServer != 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 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 {
log.WithError(err).Errorf("Error occurred closing the database connection")
if err = ctx.providers.StorageProvider.Close(); err != nil {
ctx.log.WithError(err).Errorf("Error occurred closing the database connection")
}
if err = g.Wait(); err != nil {
log.WithError(err).Errorf("Error occurred waiting for shutdown")
if err = ctx.group.Wait(); err != nil {
ctx.log.WithError(err).Errorf("Error occurred waiting for shutdown")
}
}
@ -210,7 +207,7 @@ type ProviderReload interface {
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 {
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)
}
g.Go(func() error {
ctx.group.Go(func() error {
for {
select {
case <-failed:
@ -234,30 +231,30 @@ func runServiceFileWatcher(g *errgroup.Group, log *logrus.Logger, path string, r
}
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
}
switch {
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(); {
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:
log.WithField("file", event.Name).Info("Reloaded file successfully")
ctx.log.WithField("file", event.Name).Info("Reloaded file successfully")
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:
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:
if !ok {
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
}
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
}
func doStartupChecks(config *schema.Configuration, providers *middlewares.Providers, log *logrus.Logger) {
func doStartupChecks(ctx *CmdCtx) {
var (
failures []string
err error
)
if err = doStartupCheck(log, "storage", providers.StorageProvider, false); err != nil {
log.Errorf("Failure running the storage provider startup check: %+v", err)
if err = doStartupCheck(ctx, "storage", ctx.providers.StorageProvider, false); err != nil {
ctx.log.Errorf("Failure running the storage provider startup check: %+v", err)
failures = append(failures, "storage")
}
if err = doStartupCheck(log, "user", providers.UserProvider, false); err != nil {
log.Errorf("Failure running the user provider startup check: %+v", err)
if err = doStartupCheck(ctx, "user", ctx.providers.UserProvider, false); err != nil {
ctx.log.Errorf("Failure running the user provider startup check: %+v", err)
failures = append(failures, "user")
}
if err = doStartupCheck(log, "notification", providers.Notifier, config.Notifier.DisableStartupCheck); err != nil {
log.Errorf("Failure running the notification provider startup check: %+v", err)
if err = doStartupCheck(ctx, "notification", ctx.providers.Notifier, ctx.config.Notifier.DisableStartupCheck); err != nil {
ctx.log.Errorf("Failure running the notification provider startup check: %+v", err)
failures = append(failures, "notification")
}
if !config.NTP.DisableStartupCheck && !providers.Authorizer.IsSecondFactorEnabled() {
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 {
log.Errorf("Failure running the ntp provider startup check: %+v", err)
if !ctx.config.NTP.DisableStartupCheck && !ctx.providers.Authorizer.IsSecondFactorEnabled() {
ctx.log.Debug("The NTP startup check was skipped due to there being no configured 2FA access control rules")
} else if err = doStartupCheck(ctx, "ntp", ctx.providers.NTP, ctx.config.NTP.DisableStartupCheck); err != nil {
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")
}
}
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 {
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
}

View File

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

View File

@ -1,9 +1,7 @@
package commands
import (
"context"
"database/sql"
"encoding/base32"
"errors"
"fmt"
"image"
@ -15,11 +13,8 @@ import (
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"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/model"
"github.com/authelia/authelia/v4/internal/storage"
@ -27,28 +22,35 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)
func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) {
var configs []string
// LoadProvidersStorageRunE is a special PreRunE that loads the storage provider into the CmdCtx.
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 {
return err
}
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))
for _, e := range errs {
err = fmt.Errorf("%+v: %w", err, e)
}
} 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",
cmdFlagNameSQLite3Path: "storage.local.path",
@ -77,75 +79,73 @@ func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) {
cmdFlagNameSecretSize: "totp.secret_size",
}
sources = append(sources, configuration.NewEnvironmentSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter))
sources = append(sources, configuration.NewSecretsSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter))
sources = append(sources, configuration.NewCommandLineSourceWithMapping(cmd.Flags(), mapping, true, false))
return ctx.ConfigSetFlagsMapRunE(cmd.Flags(), flagsMap, 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
}
if val.HasErrors() {
var finalErr error
validator.ValidateStorage(ctx.config.Storage, ctx.cconfig.validator)
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 {
finalErr = err
err = e
continue
}
finalErr = fmt.Errorf("%w, %v", finalErr, err)
err = fmt.Errorf("%w, %v", err, e)
}
return finalErr
}
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 err
}
return nil
}
func storageSchemaEncryptionCheckRunE(cmd *cobra.Command, args []string) (err error) {
func (ctx *CmdCtx) StorageSchemaEncryptionCheckRunE(cmd *cobra.Command, args []string) (err error) {
var (
provider storage.Provider
verbose bool
result storage.EncryptionValidationResult
ctx = context.Background()
verbose bool
result storage.EncryptionValidationResult
)
provider = getStorageProvider()
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 {
return err
}
if result, err = provider.SchemaEncryptionCheckKey(ctx, verbose); err != nil {
if result, err = ctx.providers.StorageProvider.SchemaEncryptionCheckKey(ctx, verbose); err != nil {
switch {
case errors.Is(err, storage.ErrSchemaEncryptionVersionUnsupported):
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
}
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 (
provider storage.Provider
key string
version int
ctx = context.Background()
key string
version int
)
provider = getStorageProvider()
defer func() {
_ = provider.Close()
_ = ctx.providers.StorageProvider.Close()
}()
if err = checkStorageSchemaUpToDate(ctx, provider); err != nil {
return err
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
if version, err = provider.SchemaVersion(ctx); err != nil {
if version, err = ctx.providers.StorageProvider.SchemaVersion(ctx); err != nil {
return err
}
@ -218,7 +214,7 @@ func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (er
}
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
}
}
@ -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")
}
if err = provider.SchemaEncryptionChangeKey(ctx, key); err != nil {
if err = ctx.providers.StorageProvider.SchemaEncryptionChangeKey(ctx, key); err != nil {
return err
}
@ -239,27 +235,25 @@ func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (er
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] == "" {
return storageWebAuthnListAllRunE(cmd, args)
return ctx.StorageWebauthnListAllRunE(cmd, args)
}
var (
provider storage.Provider
ctx = context.Background()
)
provider = getStorageProvider()
defer func() {
_ = provider.Close()
_ = ctx.providers.StorageProvider.Close()
}()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
var devices []model.WebauthnDevice
user := args[0]
devices, err = provider.LoadWebauthnDevicesByUsername(ctx, user)
devices, err = ctx.providers.StorageProvider.LoadWebauthnDevicesByUsername(ctx, user)
switch {
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
}
func storageWebAuthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
var (
provider storage.Provider
ctx = context.Background()
)
provider = getStorageProvider()
// StorageWebauthnListAllRunE is the RunE for the authelia storage user webauthn list command when no args are specified.
func (ctx *CmdCtx) StorageWebauthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
defer func() {
_ = provider.Close()
_ = ctx.providers.StorageProvider.Close()
}()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
var devices []model.WebauthnDevice
limit := 10
@ -297,7 +289,7 @@ func storageWebAuthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
output := strings.Builder{}
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)
}
@ -320,35 +312,33 @@ func storageWebAuthnListAllRunE(_ *cobra.Command, _ []string) (err error) {
return nil
}
func storageWebAuthnDeleteRunE(cmd *cobra.Command, args []string) (err error) {
var (
provider storage.Provider
ctx = context.Background()
)
provider = getStorageProvider()
// StorageWebauthnDeleteRunE is the RunE for the authelia storage user webauthn delete command.
func (ctx *CmdCtx) StorageWebauthnDeleteRunE(cmd *cobra.Command, args []string) (err error) {
defer func() {
_ = provider.Close()
_ = ctx.providers.StorageProvider.Close()
}()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
var (
all, byKID bool
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
}
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)
}
fmt.Printf("Deleted WebAuthn device with kid '%s'", kid)
} else {
err = provider.DeleteWebauthnDeviceByUsername(ctx, user, description)
err = ctx.providers.StorageProvider.DeleteWebauthnDeviceByUsername(ctx, user, description)
if all {
if err != nil {
@ -368,62 +358,9 @@ func storageWebAuthnDeleteRunE(cmd *cobra.Command, args []string) (err error) {
return nil
}
func storageWebAuthnDeleteGetAndValidateConfig(cmd *cobra.Command, args []string) (all, byKID bool, description, kid, user string, err error) {
if len(args) != 0 {
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) {
// StorageTOTPGenerateRunE is the RunE for the authelia storage user totp generate command.
func (ctx *CmdCtx) StorageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
var (
provider storage.Provider
ctx = context.Background()
c *model.TOTPConfiguration
force bool
filename, secret string
@ -431,25 +368,27 @@ func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
img image.Image
)
provider = getStorageProvider()
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 {
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])
} else if err != nil && !errors.Is(err, storage.ErrNoTOTPConfiguration) {
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
}
@ -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)
}
if err = provider.SaveTOTPConfiguration(ctx, *c); err != nil {
if err = ctx.providers.StorageProvider.SaveTOTPConfiguration(ctx, *c); err != nil {
return err
}
@ -486,47 +425,23 @@ func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) {
return nil
}
func storageTOTPGenerateRunEOptsFromFlags(flags *pflag.FlagSet) (force bool, filename, secret string, err error) {
if force, err = flags.GetBool("force"); err != nil {
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()
)
// StorageTOTPDeleteRunE is the RunE for the authelia storage user totp delete command.
func (ctx *CmdCtx) StorageTOTPDeleteRunE(cmd *cobra.Command, args []string) (err error) {
user := args[0]
provider = getStorageProvider()
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)
}
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)
}
@ -535,34 +450,30 @@ func storageTOTPDeleteRunE(cmd *cobra.Command, args []string) (err error) {
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 (
provider storage.Provider
format, dir string
configurations []model.TOTPConfiguration
img image.Image
ctx = context.Background()
)
provider = getStorageProvider()
defer func() {
_ = provider.Close()
_ = ctx.providers.StorageProvider.Close()
}()
if err = checkStorageSchemaUpToDate(ctx, provider); err != nil {
return err
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
if format, dir, err = storageTOTPExportGetConfigFromFlags(cmd); err != nil {
if format, dir, err = flagsGetTOTPExportOptions(cmd.Flags()); err != nil {
return err
}
limit := 10
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
}
@ -607,56 +518,18 @@ func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) {
return nil
}
func storageTOTPExportGetConfigFromFlags(cmd *cobra.Command) (format, dir string, err error) {
if format, err = cmd.Flags().GetString(cmdFlagNameFormat); err != nil {
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) {
// StorageMigrateHistoryRunE is the RunE for the authelia storage migrate history command.
func (ctx *CmdCtx) StorageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) {
var (
provider storage.Provider
version int
migrations []model.Migration
ctx = context.Background()
)
provider = getStorageProvider()
if provider == nil {
return errNoStorageProvider
}
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
}
@ -665,7 +538,7 @@ func storageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) {
return
}
if migrations, err = provider.SchemaMigrationHistory(ctx); err != nil {
if migrations, err = ctx.providers.StorageProvider.SchemaMigrationHistory(ctx); err != nil {
return err
}
@ -682,26 +555,23 @@ func storageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) {
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) {
var (
provider storage.Provider
ctx = context.Background()
migrations []model.SchemaMigration
directionStr string
)
provider = getStorageProvider()
defer func() {
_ = provider.Close()
_ = ctx.providers.StorageProvider.Close()
}()
if up {
migrations, err = provider.SchemaMigrationsUp(ctx, 0)
migrations, err = ctx.providers.StorageProvider.SchemaMigrationsUp(ctx, 0)
directionStr = "Up"
} else {
migrations, err = provider.SchemaMigrationsDown(ctx, 0)
migrations, err = ctx.providers.StorageProvider.SchemaMigrationsDown(ctx, 0)
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) {
var (
provider storage.Provider
target int
ctx = context.Background()
target int
)
provider = getStorageProvider()
defer func() {
_ = provider.Close()
_ = ctx.providers.StorageProvider.Close()
}()
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:
switch cmd.Flags().Changed(cmdFlagNameTarget) {
case true:
return provider.SchemaMigrate(ctx, true, target)
return ctx.providers.StorageProvider.SchemaMigrate(ctx, true, target)
default:
return provider.SchemaMigrate(ctx, true, storage.SchemaLatest)
return ctx.providers.StorageProvider.SchemaMigrate(ctx, true, storage.SchemaLatest)
}
default:
if !cmd.Flags().Changed(cmdFlagNameTarget) {
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 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) {
var destroy bool
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) {
// StorageSchemaInfoRunE is the RunE for the authelia storage schema info command.
func (ctx *CmdCtx) StorageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
var (
upgradeStr, tablesStr string
provider storage.Provider
tables []string
version, latest int
ctx = context.Background()
)
provider = getStorageProvider()
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
}
if tables, err = provider.SchemaTables(ctx); err != nil {
if tables, err = ctx.providers.StorageProvider.SchemaTables(ctx); err != nil {
return err
}
@ -817,7 +663,7 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
tablesStr = strings.Join(tables, ", ")
}
if latest, err = provider.SchemaLatestVersion(); err != nil {
if latest, err = ctx.providers.StorageProvider.SchemaLatestVersion(); err != nil {
return err
}
@ -832,7 +678,7 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
result storage.EncryptionValidationResult
)
switch result, err = provider.SchemaEncryptionCheckKey(ctx, false); {
switch result, err = ctx.providers.StorageProvider.SchemaEncryptionCheckKey(ctx, false); {
case err != nil:
if errors.Is(err, storage.ErrSchemaEncryptionVersionUnsupported) {
encryption = "unsupported (schema version)"
@ -850,30 +696,9 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) {
return nil
}
func checkStorageSchemaUpToDate(ctx context.Context, provider storage.Provider) (err error) {
var version, latest int
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) {
// StorageUserIdentifiersExportRunE is the RunE for the authelia storage user identifiers export command.
func (ctx *CmdCtx) StorageUserIdentifiersExportRunE(cmd *cobra.Command, _ []string) (err error) {
var (
provider storage.Provider
ctx = context.Background()
file string
)
@ -890,7 +715,13 @@ func storageUserIdentifiersExport(cmd *cobra.Command, _ []string) (err error) {
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 (
export model.UserOpaqueIdentifiersExport
@ -898,7 +729,7 @@ func storageUserIdentifiersExport(cmd *cobra.Command, _ []string) (err error) {
data []byte
)
if export.Identifiers, err = provider.LoadUserOpaqueIdentifiers(ctx); err != nil {
if export.Identifiers, err = ctx.providers.StorageProvider.LoadUserOpaqueIdentifiers(ctx); err != nil {
return err
}
@ -919,12 +750,9 @@ func storageUserIdentifiersExport(cmd *cobra.Command, _ []string) (err error) {
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 (
provider storage.Provider
ctx = context.Background()
file string
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")
}
provider = getStorageProvider()
defer func() {
_ = ctx.providers.StorageProvider.Close()
}()
if err = ctx.CheckSchemaVersion(); err != nil {
return storageWrapCheckSchemaErr(err)
}
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
}
}
@ -971,41 +805,26 @@ func storageUserIdentifiersImport(cmd *cobra.Command, _ []string) (err error) {
return nil
}
func containsIdentifier(identifier model.UserOpaqueIdentifier, identifiers []model.UserOpaqueIdentifier) bool {
for i := 0; i < len(identifiers); i++ {
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) {
// StorageUserIdentifiersGenerateRunE is the RunE for the authelia storage user identifiers generate command.
func (ctx *CmdCtx) StorageUserIdentifiersGenerateRunE(cmd *cobra.Command, _ []string) (err error) {
var (
provider storage.Provider
ctx = context.Background()
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) {
return fmt.Errorf("can't load the existing identifiers: %w", err)
}
if users, err = cmd.Flags().GetStringSlice(cmdFlagNameUsers); err != nil {
return err
}
if services, err = cmd.Flags().GetStringSlice(cmdFlagNameServices); err != nil {
return err
}
if sectors, err = cmd.Flags().GetStringSlice(cmdFlagNameSectors); err != nil {
if users, services, sectors, err = flagsGetUserIdentifiersGenerateOptions(cmd.Flags()); err != nil {
return err
}
@ -1043,7 +862,7 @@ func storageUserIdentifiersGenerate(cmd *cobra.Command, _ []string) (err error)
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)
}
@ -1057,12 +876,9 @@ func storageUserIdentifiersGenerate(cmd *cobra.Command, _ []string) (err error)
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 (
provider storage.Provider
ctx = context.Background()
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
}

View File

@ -1,7 +1,9 @@
package commands
import (
"errors"
"fmt"
"io"
"os"
"syscall"
@ -24,16 +26,51 @@ func recoverErr(i any) error {
}
}
func configFilterExisting(configs []string) (finalConfigs []string) {
var err error
for _, c := range configs {
if _, err = os.Stat(c); err == nil || !os.IsNotExist(err) {
finalConfigs = append(finalConfigs, c)
}
func flagsGetUserIdentifiersGenerateOptions(flags *pflag.FlagSet) (users, services, sectors []string, err error) {
if users, err = flags.GetStringSlice(cmdFlagNameUsers); err != nil {
return nil, nil, nil, err
}
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
@ -102,37 +139,92 @@ func flagsGetRandomCharacters(flags *pflag.FlagSet, flagNameLength, flagNameChar
return utils.RandomString(n, charset, true), nil
}
func termReadPasswordStrWithPrompt(prompt, flag string) (data string, err error) {
var d []byte
func termReadConfirmation(flags *pflag.FlagSet, name, prompt, confirmation string) (confirmed bool, err error) {
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 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) {
fd := int(syscall.Stdin) //nolint:unconvert,nolintlint
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)
}
if password, err = terminal.ReadPassword(prompt); err != nil {
return "", fmt.Errorf("failed to read the input from the terminal: %w", err)
}
fmt.Print(prompt)
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
return password, nil
}

View File

@ -4,64 +4,51 @@ import (
"fmt"
"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{
Use: "validate-config",
Short: cmdAutheliaValidateConfigShort,
Long: cmdAutheliaValidateConfigLong,
Example: cmdAutheliaValidateConfigExample,
Args: cobra.NoArgs,
RunE: cmdValidateConfigRunE,
PreRunE: ctx.ChainRunE(
ctx.ConfigLoadRunE,
ctx.ConfigValidateKeysRunE,
ctx.ConfigValidateRunE,
),
RunE: ctx.ValidateConfigRunE,
DisableAutoGenTag: true,
}
cmdWithConfigFlags(cmd, false, []string{"configuration.yml"})
return cmd
}
func cmdValidateConfigRunE(cmd *cobra.Command, _ []string) (err error) {
var (
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)
}
// ValidateConfigRunE is the RunE for the authelia validate-config command.
func (ctx *CmdCtx) ValidateConfigRunE(_ *cobra.Command, _ []string) (err error) {
switch {
case val.HasErrors():
case ctx.cconfig.validator.HasErrors():
fmt.Println("Configuration parsed and loaded with errors:")
fmt.Println("")
for _, err = range val.Errors() {
for _, err = range ctx.cconfig.validator.Errors() {
fmt.Printf("\t - %v\n", err)
}
fmt.Println("")
if !val.HasWarnings() {
if !ctx.cconfig.validator.HasWarnings() {
break
}
fallthrough
case val.HasWarnings():
case ctx.cconfig.validator.HasWarnings():
fmt.Println("Configuration parsed and loaded with warnings:")
fmt.Println("")
for _, err = range val.Warnings() {
for _, err = range ctx.cconfig.validator.Warnings() {
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 {
err := source.Load(val)
if err != nil {
if err = source.Load(val); err != nil {
val.Push(fmt.Errorf("failed to load configuration from %s source: %+v", source.Name(), err))
continue
}
err = source.Merge(ko, val)
if err != nil {
if err = source.Merge(ko, val); err != nil {
val.Push(fmt.Errorf("failed to merge configuration from %s source: %+v", source.Name(), err))
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.
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 {
sources = append(sources, NewDefaultSources(files, prefix, delimiter, additionalSources...)...)

View File

@ -21,28 +21,28 @@ type YAMLFileSource struct {
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 {
koanf *koanf.Koanf
prefix 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 {
koanf *koanf.Koanf
prefix 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 {
koanf *koanf.Koanf
flags *pflag.FlagSet
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 {
m map[string]any
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")
}
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() {
var (
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")
}
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() {
_ = os.Remove("/tmp/db.sqlite3")
@ -804,8 +780,7 @@ func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {
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"})
s.Assert().EqualError(err, "exit status 1")
s.Assert().Regexp(patternOutdated, output)
@ -815,8 +790,8 @@ func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {
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"})
s.Assert().NoError(err)
s.Assert().Contains(output, "Storage Encryption Key Validation: FAILURE\n\n\tCause: The schema version doesn't support encryption.\n")
s.Assert().EqualError(err, "exit status 1")
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"})
s.Assert().EqualError(err, "exit status 1")