From e3e31e3cbc2a2c030dc51f7cd6a15ccdfc956873 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Thu, 22 Dec 2022 11:21:29 +1100 Subject: [PATCH] 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. --- .github/ISSUE_TEMPLATE/bug-report.yml | 1 + .../content/en/configuration/methods/files.md | 8 +- docs/content/en/policies/versioning.md | 2 +- .../en/reference/cli/authelia/authelia.md | 5 +- .../cli/authelia/authelia_access-control.md | 7 + .../authelia_access-control_check-policy.md | 8 +- .../cli/authelia/authelia_build-info.md | 7 + .../reference/cli/authelia/authelia_crypto.md | 7 + .../authelia/authelia_crypto_certificate.md | 7 + .../authelia_crypto_certificate_ecdsa.md | 7 + ...helia_crypto_certificate_ecdsa_generate.md | 9 +- ...thelia_crypto_certificate_ecdsa_request.md | 9 +- .../authelia_crypto_certificate_ed25519.md | 7 + ...lia_crypto_certificate_ed25519_generate.md | 9 +- ...elia_crypto_certificate_ed25519_request.md | 9 +- .../authelia_crypto_certificate_rsa.md | 7 + ...uthelia_crypto_certificate_rsa_generate.md | 9 +- ...authelia_crypto_certificate_rsa_request.md | 9 +- .../cli/authelia/authelia_crypto_hash.md | 7 + .../authelia/authelia_crypto_hash_generate.md | 8 +- .../authelia_crypto_hash_generate_argon2.md | 15 +- .../authelia_crypto_hash_generate_bcrypt.md | 15 +- .../authelia_crypto_hash_generate_pbkdf2.md | 15 +- .../authelia_crypto_hash_generate_scrypt.md | 15 +- ...authelia_crypto_hash_generate_sha2crypt.md | 15 +- .../authelia/authelia_crypto_hash_validate.md | 7 + .../cli/authelia/authelia_crypto_pair.md | 7 + .../authelia/authelia_crypto_pair_ecdsa.md | 7 + .../authelia_crypto_pair_ecdsa_generate.md | 7 + .../authelia/authelia_crypto_pair_ed25519.md | 7 + .../authelia_crypto_pair_ed25519_generate.md | 7 + .../cli/authelia/authelia_crypto_pair_rsa.md | 7 + .../authelia_crypto_pair_rsa_generate.md | 7 + .../cli/authelia/authelia_crypto_rand.md | 9 +- .../cli/authelia/authelia_hash-password.md | 55 -- .../cli/authelia/authelia_storage.md | 8 +- .../authelia/authelia_storage_encryption.md | 1 + .../authelia_storage_encryption_change-key.md | 1 + .../authelia_storage_encryption_check.md | 1 + .../cli/authelia/authelia_storage_migrate.md | 1 + .../authelia/authelia_storage_migrate_down.md | 1 + .../authelia_storage_migrate_history.md | 1 + .../authelia_storage_migrate_list-down.md | 1 + .../authelia_storage_migrate_list-up.md | 1 + .../authelia/authelia_storage_migrate_up.md | 1 + .../authelia/authelia_storage_schema-info.md | 1 + .../cli/authelia/authelia_storage_user.md | 1 + .../authelia_storage_user_identifiers.md | 1 + .../authelia_storage_user_identifiers_add.md | 1 + ...uthelia_storage_user_identifiers_export.md | 1 + ...helia_storage_user_identifiers_generate.md | 1 + ...uthelia_storage_user_identifiers_import.md | 1 + .../authelia/authelia_storage_user_totp.md | 1 + .../authelia_storage_user_totp_delete.md | 1 + .../authelia_storage_user_totp_export.md | 1 + .../authelia_storage_user_totp_generate.md | 1 + .../authelia_storage_user_webauthn.md | 1 + .../authelia_storage_user_webauthn_delete.md | 1 + .../authelia_storage_user_webauthn_list.md | 1 + .../cli/authelia/authelia_validate-config.md | 10 +- docs/layouts/shortcodes/alert.html | 2 +- internal/commands/acl.go | 49 +- internal/commands/build-info.go | 7 +- internal/commands/configuration.go | 113 ---- internal/commands/const.go | 20 +- internal/commands/context.go | 365 +++++++++++ internal/commands/crypto.go | 93 +-- internal/commands/crypto_hash.go | 590 +++++++----------- internal/commands/crypto_helper.go | 2 +- internal/commands/errors.go | 3 + internal/commands/helpers.go | 204 +++--- internal/commands/helpers_test.go | 6 +- internal/commands/root.go | 175 +++--- internal/commands/storage.go | 143 ++--- internal/commands/storage_run.go | 582 ++++++----------- internal/commands/util.go | 160 ++++- internal/commands/validate.go | 41 +- internal/configuration/provider.go | 6 +- internal/configuration/sources.go | 4 +- internal/configuration/types.go | 8 +- internal/suites/suite_cli_test.go | 31 +- 81 files changed, 1566 insertions(+), 1416 deletions(-) delete mode 100644 docs/content/en/reference/cli/authelia/authelia_hash-password.md delete mode 100644 internal/commands/configuration.go create mode 100644 internal/commands/context.go diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 9d426cc81..fad7883f8 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -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 diff --git a/docs/content/en/configuration/methods/files.md b/docs/content/en/configuration/methods/files.md index 7a55ac583..ef304ae1c 100644 --- a/docs/content/en/configuration/methods/files.md +++ b/docs/content/en/configuration/methods/files.md @@ -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 }} ``` diff --git a/docs/content/en/policies/versioning.md b/docs/content/en/policies/versioning.md index b71a36f48..27f993b1f 100644 --- a/docs/content/en/policies/versioning.md +++ b/docs/content/en/policies/versioning.md @@ -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: diff --git a/docs/content/en/reference/cli/authelia/authelia.md b/docs/content/en/reference/cli/authelia/authelia.md index d86f3ae69..836d0e064 100644 --- a/docs/content/en/reference/cli/authelia/authelia.md +++ b/docs/content/en/reference/cli/authelia/authelia.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_access-control.md b/docs/content/en/reference/cli/authelia/authelia_access-control.md index 002c0f8bd..7656e4ca1 100644 --- a/docs/content/en/reference/cli/authelia/authelia_access-control.md +++ b/docs/content/en/reference/cli/authelia/authelia_access-control.md @@ -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) diff --git a/docs/content/en/reference/cli/authelia/authelia_access-control_check-policy.md b/docs/content/en/reference/cli/authelia/authelia_access-control_check-policy.md index 026b51072..606db06e1 100644 --- a/docs/content/en/reference/cli/authelia/authelia_access-control_check-policy.md +++ b/docs/content/en/reference/cli/authelia/authelia_access-control_check-policy.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_build-info.md b/docs/content/en/reference/cli/authelia/authelia_build-info.md index dec6af881..ea4ac97a2 100644 --- a/docs/content/en/reference/cli/authelia/authelia_build-info.md +++ b/docs/content/en/reference/cli/authelia/authelia_build-info.md @@ -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) diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto.md b/docs/content/en/reference/cli/authelia/authelia_crypto.md index 2e8e12541..f4c87607f 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto.md @@ -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) diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate.md index 3644a0bb4..5142bf023 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa.md index 611dbba9f..3914ece12 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa_generate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa_generate.md index 2e9dd2285..560f9acae 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa_request.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa_request.md index 8b31a4381..c0079824c 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa_request.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ecdsa_request.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519.md index 2f7cf88ae..9e74e002a 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519_generate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519_generate.md index 5c1678778..2fe6e3901 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519_request.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519_request.md index 36ff997c2..d96d11104 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519_request.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_ed25519_request.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa.md index 588950169..ebb3c08e8 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa_generate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa_generate.md index 58c06b469..67300bd40 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa_request.md b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa_request.md index 0b4ddf684..188a4ea5a 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa_request.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_certificate_rsa_request.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash.md index e4dc2cecb..cfae2e4be 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_hash.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate.md index 29ec3b58c..22163821b 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_argon2.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_argon2.md index 23aa9af28..99ffa76ec 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_argon2.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_argon2.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_bcrypt.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_bcrypt.md index 72ef32099..5c010c50a 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_bcrypt.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_bcrypt.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_pbkdf2.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_pbkdf2.md index 9ece92331..e0b60a3c6 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_pbkdf2.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_pbkdf2.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_scrypt.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_scrypt.md index 2be31e151..d2d5dae2a 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_scrypt.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_scrypt.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_sha2crypt.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_sha2crypt.md index 59deb1c2d..4e528e9b0 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_sha2crypt.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_generate_sha2crypt.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_validate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_validate.md index f9ef2de0e..79423d0eb 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_hash_validate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_hash_validate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_pair.md b/docs/content/en/reference/cli/authelia/authelia_crypto_pair.md index 22f83b87f..c845367b6 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_pair.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_pair.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ecdsa.md b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ecdsa.md index a0250744b..b4792480c 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ecdsa.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ecdsa.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ecdsa_generate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ecdsa_generate.md index 1cec3fb35..b3039cf3b 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ecdsa_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ecdsa_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ed25519.md b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ed25519.md index ef8fc2feb..7428f1e61 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ed25519.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ed25519.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ed25519_generate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ed25519_generate.md index 734301718..f9d364c11 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ed25519_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_ed25519_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_rsa.md b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_rsa.md index 18eb02a19..d6d0e4f67 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_rsa.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_rsa.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_rsa_generate.md b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_rsa_generate.md index 1b0d95ee7..8e4bd0386 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_pair_rsa_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_pair_rsa_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_crypto_rand.md b/docs/content/en/reference/cli/authelia/authelia_crypto_rand.md index a2881b09b..ab154d308 100644 --- a/docs/content/en/reference/cli/authelia/authelia_crypto_rand.md +++ b/docs/content/en/reference/cli/authelia/authelia_crypto_rand.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_hash-password.md b/docs/content/en/reference/cli/authelia/authelia_hash-password.md deleted file mode 100644 index 08e378da8..000000000 --- a/docs/content/en/reference/cli/authelia/authelia_hash-password.md +++ /dev/null @@ -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) - diff --git a/docs/content/en/reference/cli/authelia/authelia_storage.md b/docs/content/en/reference/cli/authelia/authelia_storage.md index 7b29c9551..b7e29b9bf 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage.md @@ -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) diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_encryption.md b/docs/content/en/reference/cli/authelia/authelia_storage_encryption.md index 8cac96245..6670715bd 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_encryption.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_encryption.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_encryption_change-key.md b/docs/content/en/reference/cli/authelia/authelia_storage_encryption_change-key.md index 9f8655112..0bf48c46b 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_encryption_change-key.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_encryption_change-key.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_encryption_check.md b/docs/content/en/reference/cli/authelia/authelia_storage_encryption_check.md index b15536234..e9f593a0d 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_encryption_check.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_encryption_check.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_migrate.md b/docs/content/en/reference/cli/authelia/authelia_storage_migrate.md index 4e4b9950e..ab4fc5aaa 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_migrate.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_migrate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_down.md b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_down.md index fa2e602fe..fe43bc60a 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_down.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_down.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_history.md b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_history.md index c09284f7d..0ec2a1b37 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_history.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_history.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_list-down.md b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_list-down.md index f5ee9adbd..932440a74 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_list-down.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_list-down.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_list-up.md b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_list-up.md index 4790f146b..381da7e44 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_list-up.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_list-up.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_up.md b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_up.md index 0fd7e566f..bd671f382 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_migrate_up.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_migrate_up.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_schema-info.md b/docs/content/en/reference/cli/authelia/authelia_storage_schema-info.md index e68f10635..9f73a5a90 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_schema-info.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_schema-info.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user.md b/docs/content/en/reference/cli/authelia/authelia_storage_user.md index 3bd19da5d..e936839e8 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers.md index 65723cbd9..ca11a3f65 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_add.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_add.md index 898eca5c5..f809b0fa6 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_add.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_add.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_export.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_export.md index 033d426a5..2178026d5 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_export.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_export.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_generate.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_generate.md index 89521e187..459d0b476 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_import.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_import.md index bfe854d89..1f1bd0b2a 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_import.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_identifiers_import.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_totp.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_totp.md index ab2e5fb32..32c049c1c 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_totp.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_totp.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_delete.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_delete.md index dc8d9e90a..a1aed0cc0 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_delete.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_delete.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_export.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_export.md index 01aa812fe..28deac159 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_export.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_export.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_generate.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_generate.md index 3d00321e2..f555498bd 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_generate.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_totp_generate.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn.md index bde1bb87d..b34feb344 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_delete.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_delete.md index 1b1605e3a..150b8683f 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_delete.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_delete.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_list.md b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_list.md index af4ee9b68..23f365502 100644 --- a/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_list.md +++ b/docs/content/en/reference/cli/authelia/authelia_storage_user_webauthn_list.md @@ -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 diff --git a/docs/content/en/reference/cli/authelia/authelia_validate-config.md b/docs/content/en/reference/cli/authelia/authelia_validate-config.md index 64a01cb20..178840917 100644 --- a/docs/content/en/reference/cli/authelia/authelia_validate-config.md +++ b/docs/content/en/reference/cli/authelia/authelia_validate-config.md @@ -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 diff --git a/docs/layouts/shortcodes/alert.html b/docs/layouts/shortcodes/alert.html index e2abe8ed3..ff0d8de4e 100644 --- a/docs/layouts/shortcodes/alert.html +++ b/docs/layouts/shortcodes/alert.html @@ -8,5 +8,5 @@ {{ else }} {{ errorf "No valid text variable or Inner content given"}} {{ end }} - {{ end}} + {{ end }} diff --git a/internal/commands/acl.go b/internal/commands/acl.go index a78d9e825..7f573a73b 100644 --- a/internal/commands/acl.go +++ b/internal/commands/acl.go @@ -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 } diff --git a/internal/commands/build-info.go b/internal/commands/build-info.go index ce307b684..fd321c8e7 100644 --- a/internal/commands/build-info.go +++ b/internal/commands/build-info.go @@ -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) diff --git a/internal/commands/configuration.go b/internal/commands/configuration.go deleted file mode 100644 index bb17090e3..000000000 --- a/internal/commands/configuration.go +++ /dev/null @@ -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 -} diff --git a/internal/commands/const.go b/internal/commands/const.go index f6e2e2029..a11efcdc6 100644 --- a/internal/commands/const.go +++ b/internal/commands/const.go @@ -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 ( diff --git a/internal/commands/context.go b/internal/commands/context.go new file mode 100644 index 000000000..f968b9f86 --- /dev/null +++ b/internal/commands/context.go @@ -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 + } +} diff --git a/internal/commands/crypto.go b/internal/commands/crypto.go index 642f442d4..6cad8f23f 100644 --- a/internal/commands/crypto.go +++ b/internal/commands/crypto.go @@ -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 diff --git a/internal/commands/crypto_hash.go b/internal/commands/crypto_hash.go index 7384b8fde..3eb4fa374 100644 --- a/internal/commands/crypto_hash.go +++ b/internal/commands/crypto_hash.go @@ -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") diff --git a/internal/commands/crypto_helper.go b/internal/commands/crypto_helper.go index d85c70798..1827970ed 100644 --- a/internal/commands/crypto_helper.go +++ b/internal/commands/crypto_helper.go @@ -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") diff --git a/internal/commands/errors.go b/internal/commands/errors.go index 481053c33..5a6353fa7 100644 --- a/internal/commands/errors.go +++ b/internal/commands/errors.go @@ -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") diff --git a/internal/commands/helpers.go b/internal/commands/helpers.go index 0bfec419d..f5d9117b1 100644 --- a/internal/commands/helpers.go +++ b/internal/commands/helpers.go @@ -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 } diff --git a/internal/commands/helpers_test.go b/internal/commands/helpers_test.go index 797ecfd6c..e0ff981e0 100644 --- a/internal/commands/helpers_test.go +++ b/internal/commands/helpers_test.go @@ -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())) } diff --git a/internal/commands/root.go b/internal/commands/root.go index 95a649081..7706c464e 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -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 } diff --git a/internal/commands/storage.go b/internal/commands/storage.go index 41ca5f227..8a7129b79 100644 --- a/internal/commands/storage.go +++ b/internal/commands/storage.go @@ -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 ", 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 ", 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 ", 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, } diff --git a/internal/commands/storage_run.go b/internal/commands/storage_run.go index 6c0c89c5f..42103dd6a 100644 --- a/internal/commands/storage_run.go +++ b/internal/commands/storage_run.go @@ -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 } diff --git a/internal/commands/util.go b/internal/commands/util.go index 1534b4bb3..a5c264d0d 100644 --- a/internal/commands/util.go +++ b/internal/commands/util.go @@ -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 } diff --git a/internal/commands/validate.go b/internal/commands/validate.go index 8382ceb7c..2644e5f13 100644 --- a/internal/commands/validate.go +++ b/internal/commands/validate.go @@ -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) } diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go index b19585bd6..39ca3ac70 100644 --- a/internal/configuration/provider.go +++ b/internal/configuration/provider.go @@ -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 diff --git a/internal/configuration/sources.go b/internal/configuration/sources.go index b17613a65..47b775fe3 100644 --- a/internal/configuration/sources.go +++ b/internal/configuration/sources.go @@ -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...)...) diff --git a/internal/configuration/types.go b/internal/configuration/types.go index 32a15fb5e..4c0236dc8 100644 --- a/internal/configuration/types.go +++ b/internal/configuration/types.go @@ -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 diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go index 95c26d1d2..3a81841ef 100644 --- a/internal/suites/suite_cli_test.go +++ b/internal/suites/suite_cli_test.go @@ -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")