mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
[FEATURE] Config Validation (#901)
* [FEATURE] Config Validation * check configuration for invalid keys on startup * allow users to manually trigger all configuration validation on a file using a cmd * setup all defaults in config template and run tests against it to prevent accidents * use tests to check bad configuration values are caught * use tests to check old configuration values are caught * add tests for specific key errors * resolve merge conflicts * nolint prealloc for test
This commit is contained in:
parent
b9fb33d806
commit
c1ac25a15b
|
@ -130,8 +130,9 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(versionCmd, commands.HashPasswordCmd)
|
rootCmd.AddCommand(versionCmd, commands.HashPasswordCmd,
|
||||||
rootCmd.AddCommand(commands.CertificatesCmd)
|
commands.ValidateConfigCmd, commands.CertificatesCmd)
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,10 +382,10 @@ notifier:
|
||||||
# {title} is replaced by the text from the notifier
|
# {title} is replaced by the text from the notifier
|
||||||
subject: "[Authelia] {title}"
|
subject: "[Authelia] {title}"
|
||||||
# This address is used during the startup check to verify the email configuration is correct. It's not important what it is except if your email server only allows local delivery.
|
# This address is used during the startup check to verify the email configuration is correct. It's not important what it is except if your email server only allows local delivery.
|
||||||
## startup_check_address: test@authelia.com
|
startup_check_address: test@authelia.com
|
||||||
## trusted_cert: ""
|
trusted_cert: ""
|
||||||
## disable_require_tls: false
|
disable_require_tls: false
|
||||||
## disable_verify_cert: false
|
disable_verify_cert: false
|
||||||
|
|
||||||
# Sending an email using a Gmail account is as simple as the next section.
|
# Sending an email using a Gmail account is as simple as the next section.
|
||||||
# You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
|
# You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
|
||||||
|
|
|
@ -14,7 +14,23 @@ When running **Authelia**, you can specify your configuration by passing
|
||||||
the file path as shown below.
|
the file path as shown below.
|
||||||
|
|
||||||
$ authelia --config config.custom.yml
|
$ authelia --config config.custom.yml
|
||||||
|
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Authelia validates the configuration when it starts. This process checks multiple factors including configuration keys
|
||||||
|
that don't exist, configuration keys that have changed, the values of the keys are valid, and that a configuration
|
||||||
|
key isn't supplied at the same time as a secret for the same configuration option.
|
||||||
|
|
||||||
|
You may also optionally validate your configuration against this validation process manually by using the validate-config
|
||||||
|
option with the Authelia binary as shown below. Keep in mind if you're using [secrets](./secrets.md) you will have to
|
||||||
|
manually provide these if you don't want to get certain validation errors (specifically requesting you provide one of
|
||||||
|
the secret values). You can choose to ignore them if you know what you're doing. This command is useful prior to
|
||||||
|
upgrading to prevent configuration changes from impacting downtime in an upgrade.
|
||||||
|
|
||||||
|
$ authelia validate-config configuration.yml
|
||||||
|
|
||||||
|
|
||||||
## Duration Notation Format
|
## Duration Notation Format
|
||||||
|
|
||||||
We have implemented a string based notation for configuration options that take a duration. This section describes its
|
We have implemented a string based notation for configuration options that take a duration. This section describes its
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -458,6 +458,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
|
40
internal/commands/validate.go
Normal file
40
internal/commands/validate.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/internal/configuration"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateConfigCmd uses the internal configuration reader to validate the configuration.
|
||||||
|
var ValidateConfigCmd = &cobra.Command{
|
||||||
|
Use: "validate-config [yaml]",
|
||||||
|
Short: "Check a configuration against the internal configuration validation mechanisms.",
|
||||||
|
Run: func(cobraCmd *cobra.Command, args []string) {
|
||||||
|
configPath := args[0]
|
||||||
|
if _, err := os.Stat(configPath); err != nil {
|
||||||
|
log.Fatalf("Error Loading Configuration: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Actually use the configuration to validate some providers like Notifier
|
||||||
|
_, errs := configuration.Read(configPath)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
str := "Errors"
|
||||||
|
if len(errs) == 1 {
|
||||||
|
str = "Error"
|
||||||
|
}
|
||||||
|
errors := ""
|
||||||
|
for _, err := range errs {
|
||||||
|
errors += fmt.Sprintf("\t%s\n", err.Error())
|
||||||
|
}
|
||||||
|
log.Fatalf("%s occurred parsing configuration:\n%s", str, errors)
|
||||||
|
} else {
|
||||||
|
log.Println("Configuration parsed successfully without errors.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ func Read(configPath string) (*schema.Configuration, []error) {
|
||||||
val := schema.NewStructValidator()
|
val := schema.NewStructValidator()
|
||||||
validator.ValidateSecrets(&configuration, val, viper.GetViper())
|
validator.ValidateSecrets(&configuration, val, viper.GetViper())
|
||||||
validator.ValidateConfiguration(&configuration, val)
|
validator.ValidateConfiguration(&configuration, val)
|
||||||
|
validator.ValidateKeys(val, viper.AllKeys())
|
||||||
|
|
||||||
if val.HasErrors() {
|
if val.HasErrors() {
|
||||||
return nil, val.Errors()
|
return nil, val.Errors()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package configuration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ func TestShouldParseConfigFile(t *testing.T) {
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD", "mysql_secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD", "mysql_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
||||||
|
|
||||||
config, errors := Read("./test_resources/config.yml")
|
config, errors := Read("./test_resources/config.yml")
|
||||||
|
|
||||||
|
@ -73,6 +75,33 @@ func TestShouldParseAltConfigFile(t *testing.T) {
|
||||||
assert.Len(t, config.AccessControl.Rules, 12)
|
assert.Len(t, config.AccessControl.Rules, 12)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldNotParseConfigFileWithOldOrUnexpectedKeys(t *testing.T) {
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_JWT_SECRET", "secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_DUO_API_SECRET_KEY", "duo_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_SESSION_SECRET", "session_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD", "ldap_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD", "mysql_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
||||||
|
|
||||||
|
_, errors := Read("./test_resources/config_bad_keys.yml")
|
||||||
|
require.Len(t, errors, 2)
|
||||||
|
|
||||||
|
// Sort error slice to prevent shenanigans that somehow occur
|
||||||
|
sort.Slice(errors, func(i, j int) bool {
|
||||||
|
return errors[i].Error() < errors[j].Error()
|
||||||
|
})
|
||||||
|
assert.EqualError(t, errors[0], "config key not expected: loggy_file")
|
||||||
|
assert.EqualError(t, errors[1], "config key replaced: logs_level is now log_level")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldValidateConfigurationTemplate(t *testing.T) {
|
||||||
|
resetEnv()
|
||||||
|
_, errors := Read("../../config.template.yml")
|
||||||
|
assert.Len(t, errors, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldOnlyAllowOneEnvType(t *testing.T) {
|
func TestShouldOnlyAllowOneEnvType(t *testing.T) {
|
||||||
resetEnv()
|
resetEnv()
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
||||||
|
|
124
internal/configuration/test_resources/config_bad_keys.yml
Normal file
124
internal/configuration/test_resources/config_bad_keys.yml
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
###############################################################
|
||||||
|
# Authelia configuration #
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 9091
|
||||||
|
loggy_file: /etc/authelia/svc.log
|
||||||
|
|
||||||
|
logs_level: debug
|
||||||
|
default_redirection_url: https://home.example.com:8080/
|
||||||
|
|
||||||
|
totp:
|
||||||
|
issuer: authelia.com
|
||||||
|
|
||||||
|
duo_api:
|
||||||
|
hostname: api-123456789.example.com
|
||||||
|
integration_key: ABCDEF
|
||||||
|
|
||||||
|
authentication_backend:
|
||||||
|
ldap:
|
||||||
|
url: ldap://127.0.0.1
|
||||||
|
base_dn: dc=example,dc=com
|
||||||
|
username_attribute: uid
|
||||||
|
additional_users_dn: ou=users
|
||||||
|
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
||||||
|
additional_groups_dn: ou=groups
|
||||||
|
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||||
|
group_name_attribute: cn
|
||||||
|
mail_attribute: mail
|
||||||
|
user: cn=admin,dc=example,dc=com
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
default_policy: deny
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# Rules applied to everyone
|
||||||
|
- domain: public.example.com
|
||||||
|
policy: bypass
|
||||||
|
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: one_factor
|
||||||
|
# Network based rule, if not provided any network matches.
|
||||||
|
networks:
|
||||||
|
- 192.168.1.0/24
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: [singlefactor.example.com, onefactor.example.com]
|
||||||
|
policy: one_factor
|
||||||
|
|
||||||
|
# Rules applied to 'admins' group
|
||||||
|
- domain: "mx2.mail.example.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: deny
|
||||||
|
- domain: "*.example.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/groups/dev/.*$"
|
||||||
|
subject: "group:dev"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to user 'john'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/john/.*$"
|
||||||
|
subject: "user:john"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group and user 'john'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/deny-all.*$"
|
||||||
|
subject: ["group:dev", "user:john"]
|
||||||
|
policy: denied
|
||||||
|
|
||||||
|
# Rules applied to user 'harry'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/harry/.*$"
|
||||||
|
subject: "user:harry"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to user 'bob'
|
||||||
|
- domain: "*.mail.example.com"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
- domain: "dev.example.com"
|
||||||
|
resources:
|
||||||
|
- "^/users/bob/.*$"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
session:
|
||||||
|
name: authelia_session
|
||||||
|
expiration: 3600000 # 1 hour
|
||||||
|
inactivity: 300000 # 5 minutes
|
||||||
|
domain: example.com
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
|
||||||
|
regulation:
|
||||||
|
max_retries: 3
|
||||||
|
find_time: 120
|
||||||
|
ban_time: 300
|
||||||
|
|
||||||
|
storage:
|
||||||
|
mysql:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 3306
|
||||||
|
database: authelia
|
||||||
|
username: authelia
|
||||||
|
|
||||||
|
notifier:
|
||||||
|
smtp:
|
||||||
|
username: test
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 1025
|
||||||
|
sender: admin@example.com
|
||||||
|
disable_require_tls: true
|
148
internal/configuration/validator/const.go
Normal file
148
internal/configuration/validator/const.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package validator
|
||||||
|
|
||||||
|
var validKeys = []string{
|
||||||
|
// Root Keys.
|
||||||
|
"host",
|
||||||
|
"port",
|
||||||
|
"log_level",
|
||||||
|
"log_file_path",
|
||||||
|
"default_redirection_url",
|
||||||
|
"jwt_secret",
|
||||||
|
"tls_key",
|
||||||
|
"tls_cert",
|
||||||
|
"google_analytics",
|
||||||
|
|
||||||
|
// TOTP Keys
|
||||||
|
"totp.issuer",
|
||||||
|
"totp.period",
|
||||||
|
"totp.skew",
|
||||||
|
|
||||||
|
// Access Control Keys
|
||||||
|
"access_control.rules",
|
||||||
|
"access_control.default_policy",
|
||||||
|
|
||||||
|
// Session Keys.
|
||||||
|
"session.name",
|
||||||
|
"session.secret",
|
||||||
|
"session.expiration",
|
||||||
|
"session.inactivity",
|
||||||
|
"session.remember_me_duration",
|
||||||
|
"session.domain",
|
||||||
|
|
||||||
|
// Redis Session Keys.
|
||||||
|
"session.redis.host",
|
||||||
|
"session.redis.port",
|
||||||
|
"session.redis.password",
|
||||||
|
"session.redis.database_index",
|
||||||
|
|
||||||
|
// Local Storage Keys.
|
||||||
|
"storage.local.path",
|
||||||
|
|
||||||
|
// MySQL Storage Keys.
|
||||||
|
"storage.mysql.host",
|
||||||
|
"storage.mysql.port",
|
||||||
|
"storage.mysql.database",
|
||||||
|
"storage.mysql.username",
|
||||||
|
"storage.mysql.password",
|
||||||
|
|
||||||
|
// PostgreSQL Storage Keys.
|
||||||
|
"storage.postgres.host",
|
||||||
|
"storage.postgres.port",
|
||||||
|
"storage.postgres.database",
|
||||||
|
"storage.postgres.username",
|
||||||
|
"storage.postgres.password",
|
||||||
|
"storage.postgres.sslmode",
|
||||||
|
|
||||||
|
// FileSystem Notifier Keys.
|
||||||
|
"notifier.filesystem.filename",
|
||||||
|
"notifier.disable_startup_check",
|
||||||
|
|
||||||
|
// SMTP Notifier Keys.
|
||||||
|
"notifier.smtp.username",
|
||||||
|
"notifier.smtp.password",
|
||||||
|
"notifier.smtp.host",
|
||||||
|
"notifier.smtp.port",
|
||||||
|
"notifier.smtp.sender",
|
||||||
|
"notifier.smtp.subject",
|
||||||
|
"notifier.smtp.startup_check_address",
|
||||||
|
"notifier.smtp.disable_require_tls",
|
||||||
|
"notifier.smtp.disable_verify_cert",
|
||||||
|
"notifier.smtp.trusted_cert",
|
||||||
|
|
||||||
|
// Regulation Keys.
|
||||||
|
"regulation.max_retries",
|
||||||
|
"regulation.find_time",
|
||||||
|
"regulation.ban_time",
|
||||||
|
|
||||||
|
// DUO API Keys.
|
||||||
|
"duo_api.hostname",
|
||||||
|
"duo_api.integration_key",
|
||||||
|
"duo_api.secret_key",
|
||||||
|
|
||||||
|
// Authentication Backend Keys.
|
||||||
|
"authentication_backend.disable_reset_password",
|
||||||
|
|
||||||
|
// LDAP Authentication Backend Keys.
|
||||||
|
"authentication_backend.ldap.url",
|
||||||
|
"authentication_backend.ldap.skip_verify",
|
||||||
|
"authentication_backend.ldap.base_dn",
|
||||||
|
"authentication_backend.ldap.username_attribute",
|
||||||
|
"authentication_backend.ldap.additional_users_dn",
|
||||||
|
"authentication_backend.ldap.users_filter",
|
||||||
|
"authentication_backend.ldap.additional_groups_dn",
|
||||||
|
"authentication_backend.ldap.groups_filter",
|
||||||
|
"authentication_backend.ldap.group_name_attribute",
|
||||||
|
"authentication_backend.ldap.mail_attribute",
|
||||||
|
"authentication_backend.ldap.user",
|
||||||
|
"authentication_backend.ldap.password",
|
||||||
|
|
||||||
|
// File Authentication Backend Keys.
|
||||||
|
"authentication_backend.file.path",
|
||||||
|
"authentication_backend.file.password.algorithm",
|
||||||
|
"authentication_backend.file.password.iterations",
|
||||||
|
"authentication_backend.file.password.key_length",
|
||||||
|
"authentication_backend.file.password.salt_length",
|
||||||
|
"authentication_backend.file.password.memory",
|
||||||
|
"authentication_backend.file.password.parallelism",
|
||||||
|
|
||||||
|
// Secret Keys.
|
||||||
|
"authelia.jwt_secret",
|
||||||
|
"authelia.duo_api.secret_key",
|
||||||
|
"authelia.session.secret",
|
||||||
|
"authelia.authentication_backend.ldap.password",
|
||||||
|
"authelia.notifier.smtp.password",
|
||||||
|
"authelia.session.redis.password",
|
||||||
|
"authelia.storage.mysql.password",
|
||||||
|
"authelia.storage.postgres.password",
|
||||||
|
"authelia.jwt_secret.file",
|
||||||
|
"authelia.duo_api.secret_key.file",
|
||||||
|
"authelia.session.secret.file",
|
||||||
|
"authelia.authentication_backend.ldap.password.file",
|
||||||
|
"authelia.notifier.smtp.password.file",
|
||||||
|
"authelia.session.redis.password.file",
|
||||||
|
"authelia.storage.mysql.password.file",
|
||||||
|
"authelia.storage.postgres.password.file",
|
||||||
|
}
|
||||||
|
|
||||||
|
var specificErrorKeys = map[string]string{
|
||||||
|
"logs_file_path": "config key replaced: logs_file is now log_file",
|
||||||
|
"logs_level": "config key replaced: logs_level is now log_level",
|
||||||
|
"authentication_backend.file.password_options.algorithm": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_options.iterations": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_options.key_length": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_options.salt_length": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_options.memory": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_options.parallelism": "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_hashing.algorithm": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_hashing.iterations": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_hashing.key_length": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_hashing.salt_length": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_hashing.memory": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.password_hashing.parallelism": "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.hashing.algorithm": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.hashing.iterations": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.hashing.key_length": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.hashing.salt_length": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.hashing.memory": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||||
|
"authentication_backend.file.hashing.parallelism": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||||
|
}
|
29
internal/configuration/validator/keys.go
Normal file
29
internal/configuration/validator/keys.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidateKeys(validator *schema.StructValidator, keys []string) {
|
||||||
|
var errStrings []string
|
||||||
|
for _, key := range keys {
|
||||||
|
if utils.IsStringInSlice(key, validKeys) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, ok := specificErrorKeys[key]; ok {
|
||||||
|
if !utils.IsStringInSlice(err, errStrings) {
|
||||||
|
errStrings = append(errStrings, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validator.Push(fmt.Errorf("config key not expected: %s", key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, err := range errStrings {
|
||||||
|
validator.Push(errors.New(err))
|
||||||
|
}
|
||||||
|
}
|
83
internal/configuration/validator/keys_test.go
Normal file
83
internal/configuration/validator/keys_test.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShouldValidateGoodKeys(t *testing.T) {
|
||||||
|
configKeys := validKeys
|
||||||
|
val := schema.NewStructValidator()
|
||||||
|
ValidateKeys(val, configKeys)
|
||||||
|
|
||||||
|
require.Len(t, val.Errors(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldNotValidateBadKeys(t *testing.T) {
|
||||||
|
configKeys := validKeys
|
||||||
|
configKeys = append(configKeys, "bad_key")
|
||||||
|
configKeys = append(configKeys, "totp.skewy")
|
||||||
|
val := schema.NewStructValidator()
|
||||||
|
ValidateKeys(val, configKeys)
|
||||||
|
|
||||||
|
errs := val.Errors()
|
||||||
|
require.Len(t, errs, 2)
|
||||||
|
|
||||||
|
assert.EqualError(t, errs[0], "config key not expected: bad_key")
|
||||||
|
assert.EqualError(t, errs[1], "config key not expected: totp.skewy")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllSpecificErrorKeys(t *testing.T) {
|
||||||
|
var configKeys []string //nolint:prealloc // This is because the test is dynamic based on the keys that exist in the map
|
||||||
|
var uniqueValues []string
|
||||||
|
|
||||||
|
// Setup configKeys and uniqueValues expected.
|
||||||
|
for key, value := range specificErrorKeys {
|
||||||
|
configKeys = append(configKeys, key)
|
||||||
|
if !utils.IsStringInSlice(value, uniqueValues) {
|
||||||
|
uniqueValues = append(uniqueValues, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val := schema.NewStructValidator()
|
||||||
|
ValidateKeys(val, configKeys)
|
||||||
|
|
||||||
|
errs := val.Errors()
|
||||||
|
|
||||||
|
// Check only unique errors are shown. Require because if we don't the next test panics.
|
||||||
|
require.Len(t, errs, len(uniqueValues))
|
||||||
|
|
||||||
|
// Dynamically check all specific errors.
|
||||||
|
for i, value := range uniqueValues {
|
||||||
|
assert.EqualError(t, errs[i], value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpecificErrorKeys(t *testing.T) {
|
||||||
|
configKeys := []string{
|
||||||
|
"logs_level",
|
||||||
|
"logs_file_path",
|
||||||
|
"authentication_backend.file.password_options.algorithm",
|
||||||
|
"authentication_backend.file.password_options.iterations", // This should not show another error since our target for the specific error is password_options.
|
||||||
|
"authentication_backend.file.password_hashing.algorithm",
|
||||||
|
"authentication_backend.file.hashing.algorithm",
|
||||||
|
}
|
||||||
|
|
||||||
|
val := schema.NewStructValidator()
|
||||||
|
ValidateKeys(val, configKeys)
|
||||||
|
|
||||||
|
errs := val.Errors()
|
||||||
|
|
||||||
|
require.Len(t, errs, 5)
|
||||||
|
|
||||||
|
assert.EqualError(t, errs[0], specificErrorKeys["logs_level"])
|
||||||
|
assert.EqualError(t, errs[1], specificErrorKeys["logs_file_path"])
|
||||||
|
assert.EqualError(t, errs[2], specificErrorKeys["authentication_backend.file.password_options.iterations"])
|
||||||
|
assert.EqualError(t, errs[3], specificErrorKeys["authentication_backend.file.password_hashing.algorithm"])
|
||||||
|
assert.EqualError(t, errs[4], specificErrorKeys["authentication_backend.file.hashing.algorithm"])
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user