fix: redis sentinel secret missing (#1839)
* fix: redis sentinel secret missing * refactor: use consts for authentication_backend.file.password errs * fix: unit test for new default port * test: cover additional misses * test: fix windows/linux specific test error * test: more windows specific tests * test: remove superfluous url.IsAbs * test: validator 100% coveragepull/1845/head
parent
7ccbaaffe3
commit
a44f0cf959
|
@ -32,15 +32,16 @@ secrets and can be defined. Any other option defined using an
|
|||
environment variable will not be replaced.
|
||||
|
||||
|Configuration Key |Environment Variable |
|
||||
|:----------------------------------:|:------------------------------------------------:|
|
||||
|:-----------------------------------------------:|:------------------------------------------------:|
|
||||
|jwt_secret |AUTHELIA_JWT_SECRET_FILE |
|
||||
|duo_api.secret_key |AUTHELIA_DUO_API_SECRET_KEY_FILE |
|
||||
|session.secret |AUTHELIA_SESSION_SECRET_FILE |
|
||||
|session.redis.password |AUTHELIA_SESSION_REDIS_PASSWORD_FILE |
|
||||
|session.redis.high_availability.sentinel_password|AUTHELIA_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD|
|
||||
|storage.mysql.password |AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE |
|
||||
|storage.postgres.password |AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE |
|
||||
|notifier.smtp.password |AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE |
|
||||
|authentication_backend.ldap.password|AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE|
|
||||
|authentication_backend.ldap.password |AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE|
|
||||
|
||||
## Secrets in configuration file
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -34,6 +35,10 @@ func WithDatabase(content []byte, f func(path string)) {
|
|||
}
|
||||
|
||||
func TestShouldErrorPermissionsOnLocalFS(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping test due to being on windows")
|
||||
}
|
||||
|
||||
_ = os.Mkdir("/tmp/noperms/", 0000)
|
||||
errors := checkDatabase("/tmp/noperms/users_database.yml")
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package configuration
|
||||
|
||||
const windows = "windows"
|
|
@ -55,14 +55,10 @@ func Read(configPath string) (*schema.Configuration, []error) {
|
|||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
viper.BindEnv("authelia.jwt_secret.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
viper.BindEnv("authelia.duo_api.secret_key.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
viper.BindEnv("authelia.session.secret.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
viper.BindEnv("authelia.authentication_backend.ldap.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
viper.BindEnv("authelia.notifier.smtp.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
viper.BindEnv("authelia.session.redis.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
viper.BindEnv("authelia.storage.mysql.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
viper.BindEnv("authelia.storage.postgres.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
// Dynamically load the secret env names from the SecretNames map.
|
||||
for _, secretName := range validator.SecretNames {
|
||||
_ = viper.BindEnv(validator.SecretNameToEnvName(secretName))
|
||||
}
|
||||
|
||||
viper.SetConfigFile(configPath)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
|
@ -27,6 +28,7 @@ func resetEnv() {
|
|||
_ = os.Unsetenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE")
|
||||
_ = os.Unsetenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE")
|
||||
_ = os.Unsetenv("AUTHELIA_SESSION_REDIS_PASSWORD_FILE")
|
||||
_ = os.Unsetenv("AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE")
|
||||
_ = os.Unsetenv("AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE")
|
||||
_ = os.Unsetenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE")
|
||||
}
|
||||
|
@ -49,6 +51,7 @@ func setupEnv(t *testing.T) string {
|
|||
createTestingTempFile(t, dir, "authentication", "ldap_secret_from_env")
|
||||
createTestingTempFile(t, dir, "notifier", "smtp_secret_from_env")
|
||||
createTestingTempFile(t, dir, "redis", "redis_secret_from_env")
|
||||
createTestingTempFile(t, dir, "redis-sentinel", "redis-sentinel_secret_from_env")
|
||||
createTestingTempFile(t, dir, "mysql", "mysql_secret_from_env")
|
||||
createTestingTempFile(t, dir, "postgres", "postgres_secret_from_env")
|
||||
|
||||
|
@ -65,7 +68,56 @@ func TestShouldErrorNoConfigPath(t *testing.T) {
|
|||
require.EqualError(t, errors[0], "No config file path provided")
|
||||
}
|
||||
|
||||
func TestShouldErrorSecretNotExist(t *testing.T) {
|
||||
dir := "/path/not/exist"
|
||||
|
||||
require.NoError(t, os.Setenv("AUTHELIA_JWT_SECRET_FILE", dir+"jwt"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_DUO_API_SECRET_KEY_FILE", dir+"duo"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_SECRET_FILE", dir+"session"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE", dir+"authentication"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE", dir+"notifier"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD_FILE", dir+"redis"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE", dir+"redis-sentinel"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE", dir+"mysql"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE", dir+"postgres"))
|
||||
|
||||
_, errors := Read("./test_resources/config.yml")
|
||||
|
||||
require.Len(t, errors, 12)
|
||||
|
||||
if runtime.GOOS == windows {
|
||||
assert.EqualError(t, errors[0], "error loading secret file (jwt_secret): open /path/not/existjwt: The system cannot find the path specified.")
|
||||
assert.EqualError(t, errors[1], "error loading secret file (session.secret): open /path/not/existsession: The system cannot find the path specified.")
|
||||
assert.EqualError(t, errors[2], "error loading secret file (duo_api.secret_key): open /path/not/existduo: The system cannot find the path specified.")
|
||||
assert.EqualError(t, errors[3], "error loading secret file (session.redis.password): open /path/not/existredis: The system cannot find the path specified.")
|
||||
assert.EqualError(t, errors[4], "error loading secret file (session.redis.high_availability.sentinel_password): open /path/not/existredis-sentinel: The system cannot find the path specified.")
|
||||
assert.EqualError(t, errors[5], "error loading secret file (authentication_backend.ldap.password): open /path/not/existauthentication: The system cannot find the path specified.")
|
||||
assert.EqualError(t, errors[6], "error loading secret file (notifier.smtp.password): open /path/not/existnotifier: The system cannot find the path specified.")
|
||||
assert.EqualError(t, errors[7], "error loading secret file (storage.mysql.password): open /path/not/existmysql: The system cannot find the path specified.")
|
||||
} else {
|
||||
assert.EqualError(t, errors[0], "error loading secret file (jwt_secret): open /path/not/existjwt: no such file or directory")
|
||||
assert.EqualError(t, errors[1], "error loading secret file (session.secret): open /path/not/existsession: no such file or directory")
|
||||
assert.EqualError(t, errors[2], "error loading secret file (duo_api.secret_key): open /path/not/existduo: no such file or directory")
|
||||
assert.EqualError(t, errors[3], "error loading secret file (session.redis.password): open /path/not/existredis: no such file or directory")
|
||||
assert.EqualError(t, errors[4], "error loading secret file (session.redis.high_availability.sentinel_password): open /path/not/existredis-sentinel: no such file or directory")
|
||||
assert.EqualError(t, errors[5], "error loading secret file (authentication_backend.ldap.password): open /path/not/existauthentication: no such file or directory")
|
||||
assert.EqualError(t, errors[6], "error loading secret file (notifier.smtp.password): open /path/not/existnotifier: no such file or directory")
|
||||
assert.EqualError(t, errors[7], "error loading secret file (storage.mysql.password): open /path/not/existmysql: no such file or directory")
|
||||
}
|
||||
|
||||
assert.EqualError(t, errors[8], "Provide a JWT secret using \"jwt_secret\" key")
|
||||
assert.EqualError(t, errors[9], "Please provide a password to connect to the LDAP server")
|
||||
assert.EqualError(t, errors[10], "The session secret must be set when using the redis sentinel session provider")
|
||||
assert.EqualError(t, errors[11], "the SQL username and password must be provided")
|
||||
}
|
||||
|
||||
func TestShouldErrorPermissionsOnLocalFS(t *testing.T) {
|
||||
if runtime.GOOS == windows {
|
||||
t.Skip("skipping test due to being on windows")
|
||||
}
|
||||
|
||||
resetEnv()
|
||||
|
||||
_ = os.Mkdir("/tmp/noperms/", 0000)
|
||||
_, errors := Read("/tmp/noperms/configuration.yml")
|
||||
|
||||
|
@ -88,12 +140,23 @@ func TestShouldErrorAndGenerateConfigFile(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldErrorPermissionsConfigFile(t *testing.T) {
|
||||
resetEnv()
|
||||
|
||||
_ = ioutil.WriteFile("/tmp/authelia/permissions.yml", []byte{}, 0000) // nolint:gosec
|
||||
_, errors := Read("/tmp/authelia/permissions.yml")
|
||||
|
||||
if runtime.GOOS == windows {
|
||||
require.Len(t, errors, 5)
|
||||
assert.EqualError(t, errors[0], "Provide a JWT secret using \"jwt_secret\" key")
|
||||
assert.EqualError(t, errors[1], "Please provide `ldap` or `file` object in `authentication_backend`")
|
||||
assert.EqualError(t, errors[2], "Set domain of the session object")
|
||||
assert.EqualError(t, errors[3], "A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres'")
|
||||
assert.EqualError(t, errors[4], "A notifier configuration must be provided")
|
||||
} else {
|
||||
require.Len(t, errors, 1)
|
||||
|
||||
require.EqualError(t, errors[0], "Failed to open /tmp/authelia/permissions.yml: permission denied")
|
||||
assert.EqualError(t, errors[0], "Failed to open /tmp/authelia/permissions.yml: permission denied")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldErrorParseBadConfigFile(t *testing.T) {
|
||||
|
@ -113,6 +176,7 @@ func TestShouldParseConfigFile(t *testing.T) {
|
|||
require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE", dir+"authentication"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE", dir+"notifier"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD_FILE", dir+"redis"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE", dir+"redis-sentinel"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE", dir+"mysql"))
|
||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE", dir+"postgres"))
|
||||
|
||||
|
@ -134,10 +198,15 @@ func TestShouldParseConfigFile(t *testing.T) {
|
|||
assert.Equal(t, "ldap_secret_from_env", config.AuthenticationBackend.Ldap.Password)
|
||||
assert.Equal(t, "smtp_secret_from_env", config.Notifier.SMTP.Password)
|
||||
assert.Equal(t, "redis_secret_from_env", config.Session.Redis.Password)
|
||||
assert.Equal(t, "redis-sentinel_secret_from_env", config.Session.Redis.HighAvailability.SentinelPassword)
|
||||
assert.Equal(t, "mysql_secret_from_env", config.Storage.MySQL.Password)
|
||||
|
||||
assert.Equal(t, "deny", config.AccessControl.DefaultPolicy)
|
||||
assert.Len(t, config.AccessControl.Rules, 12)
|
||||
|
||||
require.NotNil(t, config.Session)
|
||||
require.NotNil(t, config.Session.Redis)
|
||||
require.NotNil(t, config.Session.Redis.HighAvailability)
|
||||
}
|
||||
|
||||
func TestShouldParseAltConfigFile(t *testing.T) {
|
||||
|
|
|
@ -101,6 +101,8 @@ session:
|
|||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
high_availability:
|
||||
sentinel_name: test
|
||||
|
||||
regulation:
|
||||
max_retries: 3
|
||||
|
|
|
@ -92,10 +92,6 @@ func validateLdapURL(ldapURL string, validator *schema.StructValidator) (finalUR
|
|||
return "", ""
|
||||
}
|
||||
|
||||
if !parsedURL.IsAbs() {
|
||||
validator.Push(fmt.Errorf("URL to LDAP %s is still not absolute, it should be something like ldap://127.0.0.1:389", parsedURL.String()))
|
||||
}
|
||||
|
||||
return parsedURL.String(), parsedURL.Hostname()
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,22 @@ import (
|
|||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
||||
func TestShouldRaiseErrorsWhenNoBackendProvided(t *testing.T) {
|
||||
func TestShouldRaiseErrorWhenBothBackendsProvided(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
backendConfig := schema.AuthenticationBackendConfiguration{}
|
||||
|
||||
backendConfig.Ldap = &schema.LDAPAuthenticationBackendConfiguration{}
|
||||
backendConfig.File = &schema.FileAuthenticationBackendConfiguration{
|
||||
Path: "/tmp",
|
||||
}
|
||||
|
||||
ValidateAuthenticationBackend(&backendConfig, validator)
|
||||
|
||||
require.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "You cannot provide both `ldap` and `file` objects in `authentication_backend`")
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorWhenNoBackendProvided(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
backendConfig := schema.AuthenticationBackendConfiguration{}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
||||
var defaultPort = 8080
|
||||
var defaultPort = 9091
|
||||
var defaultLogLevel = "info"
|
||||
|
||||
// ValidateConfiguration and adapt the configuration read from file.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -54,7 +55,7 @@ func TestShouldValidateAndUpdatePort(t *testing.T) {
|
|||
ValidateConfiguration(&config, validator)
|
||||
|
||||
require.Len(t, validator.Errors(), 0)
|
||||
assert.Equal(t, 8080, config.Port)
|
||||
assert.Equal(t, 9091, config.Port)
|
||||
}
|
||||
|
||||
func TestShouldValidateAndUpdateHost(t *testing.T) {
|
||||
|
@ -170,7 +171,12 @@ func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
|
|||
ValidateConfiguration(&config, validator)
|
||||
|
||||
require.Len(t, validator.Errors(), 1)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
assert.EqualError(t, validator.Errors()[0], "Error checking certificate directory: CreateFile not-a-real-file.go: The system cannot find the file specified.")
|
||||
} else {
|
||||
assert.EqualError(t, validator.Errors()[0], "Error checking certificate directory: stat not-a-real-file.go: no such file or directory")
|
||||
}
|
||||
|
||||
validator = schema.NewStructValidator()
|
||||
config.CertificatesDirectory = "const.go"
|
||||
|
|
|
@ -1,7 +1,57 @@
|
|||
package validator
|
||||
|
||||
const (
|
||||
errFmtSessionSecretRedisProvider = "The session secret must be set when using the %s session provider"
|
||||
errFmtSessionRedisPortRange = "The port must be between 1 and 65535 for the %s session provider"
|
||||
errFmtSessionRedisHostRequired = "The host must be provided when using the %s session provider"
|
||||
errFmtSessionRedisHostOrNodesRequired = "Either the host or a node must be provided when using the %s session provider"
|
||||
|
||||
errFileHashing = "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password"
|
||||
errFilePHashing = "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password"
|
||||
errFilePOptions = "config key incorrect: authentication_backend.file.password_options should be authentication_backend.file.password"
|
||||
|
||||
denyPolicy = "deny"
|
||||
bypassPolicy = "bypass"
|
||||
|
||||
argon2id = "argon2id"
|
||||
sha512 = "sha512"
|
||||
|
||||
schemeLDAP = "ldap"
|
||||
schemeLDAPS = "ldaps"
|
||||
|
||||
testBadTimer = "-1"
|
||||
testInvalidPolicy = "invalid"
|
||||
testJWTSecret = "a_secret"
|
||||
testLDAPBaseDN = "base_dn"
|
||||
testLDAPPassword = "password"
|
||||
testLDAPURL = "ldap://ldap"
|
||||
testLDAPUser = "user"
|
||||
testModeDisabled = "disable"
|
||||
testTLSCert = "/tmp/cert.pem"
|
||||
testTLSKey = "/tmp/key.pem"
|
||||
|
||||
errAccessControlInvalidPolicyWithSubjects = "Policy [bypass] for domain %s with subjects %s is invalid. It is " +
|
||||
"not supported to configure both policy bypass and subjects. For more information see: " +
|
||||
"https://www.authelia.com/docs/configuration/access-control.html#combining-subjects-and-the-bypass-policy"
|
||||
)
|
||||
|
||||
var validRequestMethods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
|
||||
|
||||
// SecretNames contains a map of secret names.
|
||||
var SecretNames = map[string]string{
|
||||
"JWTSecret": "jwt_secret",
|
||||
"SessionSecret": "session.secret",
|
||||
"DUOSecretKey": "duo_api.secret_key",
|
||||
"RedisPassword": "session.redis.password",
|
||||
"RedisSentinelPassword": "session.redis.high_availability.sentinel_password",
|
||||
"LDAPPassword": "authentication_backend.ldap.password",
|
||||
"SMTPPassword": "notifier.smtp.password",
|
||||
"MySQLPassword": "storage.mysql.password",
|
||||
"PostgreSQLPassword": "storage.postgres.password",
|
||||
}
|
||||
|
||||
// validKeys is a list of valid keys that are not secret names. For the sake of consistency please place any secret in
|
||||
// the secret names map and reuse it in relevant sections.
|
||||
var validKeys = []string{
|
||||
// Root Keys.
|
||||
"host",
|
||||
|
@ -10,7 +60,6 @@ var validKeys = []string{
|
|||
"log_format",
|
||||
"log_file_path",
|
||||
"default_redirection_url",
|
||||
"jwt_secret",
|
||||
"theme",
|
||||
"tls_key",
|
||||
"tls_cert",
|
||||
|
@ -33,7 +82,6 @@ var validKeys = []string{
|
|||
|
||||
// Session Keys.
|
||||
"session.name",
|
||||
"session.secret",
|
||||
"session.expiration",
|
||||
"session.inactivity",
|
||||
"session.remember_me_duration",
|
||||
|
@ -43,7 +91,6 @@ var validKeys = []string{
|
|||
"session.redis.host",
|
||||
"session.redis.port",
|
||||
"session.redis.username",
|
||||
"session.redis.password",
|
||||
"session.redis.database_index",
|
||||
"session.redis.maximum_active_connections",
|
||||
"session.redis.minimum_idle_connections",
|
||||
|
@ -51,7 +98,6 @@ var validKeys = []string{
|
|||
"session.redis.tls.skip_verify",
|
||||
"session.redis.tls.server_name",
|
||||
"session.redis.high_availability.sentinel_name",
|
||||
"session.redis.high_availability.sentinel_password",
|
||||
"session.redis.high_availability.nodes",
|
||||
"session.redis.high_availability.route_by_latency",
|
||||
"session.redis.high_availability.route_randomly",
|
||||
|
@ -69,14 +115,12 @@ var validKeys = []string{
|
|||
"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.
|
||||
|
@ -85,7 +129,6 @@ var validKeys = []string{
|
|||
|
||||
// SMTP Notifier Keys.
|
||||
"notifier.smtp.username",
|
||||
"notifier.smtp.password",
|
||||
"notifier.smtp.host",
|
||||
"notifier.smtp.port",
|
||||
"notifier.smtp.identifier",
|
||||
|
@ -108,7 +151,6 @@ var validKeys = []string{
|
|||
// DUO API Keys.
|
||||
"duo_api.hostname",
|
||||
"duo_api.integration_key",
|
||||
"duo_api.secret_key",
|
||||
|
||||
// Authentication Backend Keys.
|
||||
"authentication_backend.disable_reset_password",
|
||||
|
@ -127,7 +169,6 @@ var validKeys = []string{
|
|||
"authentication_backend.ldap.mail_attribute",
|
||||
"authentication_backend.ldap.display_name_attribute",
|
||||
"authentication_backend.ldap.user",
|
||||
"authentication_backend.ldap.password",
|
||||
"authentication_backend.ldap.start_tls",
|
||||
"authentication_backend.ldap.tls.minimum_version",
|
||||
"authentication_backend.ldap.tls.skip_verify",
|
||||
|
@ -143,73 +184,28 @@ var validKeys = []string{
|
|||
"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",
|
||||
"google_analytics": "config key removed: google_analytics - this functionality has been deprecated",
|
||||
"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",
|
||||
"authentication_backend.file.password_options.algorithm": errFilePOptions,
|
||||
"authentication_backend.file.password_options.iterations": errFilePOptions,
|
||||
"authentication_backend.file.password_options.key_length": errFilePOptions,
|
||||
"authentication_backend.file.password_options.salt_length": errFilePOptions,
|
||||
"authentication_backend.file.password_options.memory": errFilePOptions,
|
||||
"authentication_backend.file.password_options.parallelism": errFilePOptions,
|
||||
"authentication_backend.file.password_hashing.algorithm": errFilePHashing,
|
||||
"authentication_backend.file.password_hashing.iterations": errFilePHashing,
|
||||
"authentication_backend.file.password_hashing.key_length": errFilePHashing,
|
||||
"authentication_backend.file.password_hashing.salt_length": errFilePHashing,
|
||||
"authentication_backend.file.password_hashing.memory": errFilePHashing,
|
||||
"authentication_backend.file.password_hashing.parallelism": errFilePHashing,
|
||||
"authentication_backend.file.hashing.algorithm": errFileHashing,
|
||||
"authentication_backend.file.hashing.iterations": errFileHashing,
|
||||
"authentication_backend.file.hashing.key_length": errFileHashing,
|
||||
"authentication_backend.file.hashing.salt_length": errFileHashing,
|
||||
"authentication_backend.file.hashing.memory": errFileHashing,
|
||||
"authentication_backend.file.hashing.parallelism": errFileHashing,
|
||||
}
|
||||
|
||||
const errFmtSessionSecretRedisProvider = "The session secret must be set when using the %s session provider"
|
||||
const errFmtSessionRedisPortRange = "The port must be between 1 and 65535 for the %s session provider"
|
||||
const errFmtSessionRedisHostRequired = "The host must be provided when using the %s session provider"
|
||||
const errFmtSessionRedisHostOrNodesRequired = "Either the host or a node must be provided when using the %s session provider"
|
||||
|
||||
const denyPolicy = "deny"
|
||||
const bypassPolicy = "bypass"
|
||||
|
||||
const argon2id = "argon2id"
|
||||
const sha512 = "sha512"
|
||||
|
||||
const schemeLDAP = "ldap"
|
||||
const schemeLDAPS = "ldaps"
|
||||
|
||||
const testBadTimer = "-1"
|
||||
const testInvalidPolicy = "invalid"
|
||||
const testJWTSecret = "a_secret"
|
||||
const testLDAPBaseDN = "base_dn"
|
||||
const testLDAPPassword = "password"
|
||||
const testLDAPURL = "ldap://ldap"
|
||||
const testLDAPUser = "user"
|
||||
const testModeDisabled = "disable"
|
||||
const testTLSCert = "/tmp/cert.pem"
|
||||
const testTLSKey = "/tmp/key.pem"
|
||||
|
||||
const errAccessControlInvalidPolicyWithSubjects = "Policy [bypass] for domain %s with subjects %s is invalid. It is not supported to configure both policy bypass and subjects. For more information see: https://www.authelia.com/docs/configuration/access-control.html#combining-subjects-and-the-bypass-policy"
|
||||
|
|
|
@ -17,6 +17,10 @@ func ValidateKeys(validator *schema.StructValidator, keys []string) {
|
|||
continue
|
||||
}
|
||||
|
||||
if isSecretKey(key) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err, ok := specificErrorKeys[key]; ok {
|
||||
if !utils.IsStringInSlice(err, errStrings) {
|
||||
errStrings = append(errStrings, err)
|
||||
|
|
|
@ -10,39 +10,59 @@ import (
|
|||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
||||
// SecretNameToEnvName converts a secret name into the env name.
|
||||
func SecretNameToEnvName(secretName string) (envName string) {
|
||||
return "authelia." + secretName + ".file"
|
||||
}
|
||||
|
||||
func isSecretKey(value string) (isSecretKey bool) {
|
||||
for _, secretKey := range SecretNames {
|
||||
if value == secretKey || value == SecretNameToEnvName(secretKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateSecrets checks that secrets are either specified by config file/env or by file references.
|
||||
func ValidateSecrets(configuration *schema.Configuration, validator *schema.StructValidator, viper *viper.Viper) {
|
||||
configuration.JWTSecret = getSecretValue("jwt_secret", validator, viper)
|
||||
configuration.Session.Secret = getSecretValue("session.secret", validator, viper)
|
||||
configuration.JWTSecret = getSecretValue(SecretNames["JWTSecret"], validator, viper)
|
||||
configuration.Session.Secret = getSecretValue(SecretNames["SessionSecret"], validator, viper)
|
||||
|
||||
if configuration.DuoAPI != nil {
|
||||
configuration.DuoAPI.SecretKey = getSecretValue("duo_api.secret_key", validator, viper)
|
||||
configuration.DuoAPI.SecretKey = getSecretValue(SecretNames["DUOSecretKey"], validator, viper)
|
||||
}
|
||||
|
||||
if configuration.Session.Redis != nil {
|
||||
configuration.Session.Redis.Password = getSecretValue("session.redis.password", validator, viper)
|
||||
configuration.Session.Redis.Password = getSecretValue(SecretNames["RedisPassword"], validator, viper)
|
||||
|
||||
if configuration.Session.Redis.HighAvailability != nil {
|
||||
configuration.Session.Redis.HighAvailability.SentinelPassword =
|
||||
getSecretValue(SecretNames["RedisSentinelPassword"], validator, viper)
|
||||
}
|
||||
}
|
||||
|
||||
if configuration.AuthenticationBackend.Ldap != nil {
|
||||
configuration.AuthenticationBackend.Ldap.Password = getSecretValue("authentication_backend.ldap.password", validator, viper)
|
||||
configuration.AuthenticationBackend.Ldap.Password = getSecretValue(SecretNames["LDAPPassword"], validator, viper)
|
||||
}
|
||||
|
||||
if configuration.Notifier != nil && configuration.Notifier.SMTP != nil {
|
||||
configuration.Notifier.SMTP.Password = getSecretValue("notifier.smtp.password", validator, viper)
|
||||
configuration.Notifier.SMTP.Password = getSecretValue(SecretNames["SMTPPassword"], validator, viper)
|
||||
}
|
||||
|
||||
if configuration.Storage.MySQL != nil {
|
||||
configuration.Storage.MySQL.Password = getSecretValue("storage.mysql.password", validator, viper)
|
||||
configuration.Storage.MySQL.Password = getSecretValue(SecretNames["MySQLPassword"], validator, viper)
|
||||
}
|
||||
|
||||
if configuration.Storage.PostgreSQL != nil {
|
||||
configuration.Storage.PostgreSQL.Password = getSecretValue("storage.postgres.password", validator, viper)
|
||||
configuration.Storage.PostgreSQL.Password = getSecretValue(SecretNames["PostgreSQLPassword"], validator, viper)
|
||||
}
|
||||
}
|
||||
|
||||
func getSecretValue(name string, validator *schema.StructValidator, viper *viper.Viper) string {
|
||||
configValue := viper.GetString(name)
|
||||
fileEnvValue := viper.GetString("authelia." + name + ".file")
|
||||
fileEnvValue := viper.GetString(SecretNameToEnvName(name))
|
||||
|
||||
// Error Checking.
|
||||
if fileEnvValue != "" && configValue != "" {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShouldValidateCorrectSecretKeys(t *testing.T) {
|
||||
assert.True(t, isSecretKey("jwt_secret"))
|
||||
assert.True(t, isSecretKey("authelia.jwt_secret.file"))
|
||||
assert.False(t, isSecretKey("totp.issuer"))
|
||||
}
|
||||
|
||||
func TestShouldCreateCorrectSecretEnvNames(t *testing.T) {
|
||||
assert.Equal(t, "authelia.jwt_secret.file", SecretNameToEnvName("jwt_secret"))
|
||||
assert.Equal(t, "authelia.not_a_real_secret.file", SecretNameToEnvName("not_a_real_secret"))
|
||||
}
|
|
@ -18,6 +18,18 @@ func TestShouldSetDefaultConfig(t *testing.T) {
|
|||
assert.Equal(t, defaultWriteBufferSize, config.WriteBufferSize)
|
||||
}
|
||||
|
||||
func TestShouldParsePathCorrectly(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := schema.ServerConfiguration{
|
||||
Path: "apple",
|
||||
}
|
||||
|
||||
ValidateServer(&config, validator)
|
||||
require.Len(t, validator.Errors(), 0)
|
||||
|
||||
assert.Equal(t, "/apple", config.Path)
|
||||
}
|
||||
|
||||
func TestShouldRaiseOnNegativeValues(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := schema.ServerConfiguration{
|
||||
|
|
|
@ -24,11 +24,11 @@ func ValidateStorage(configuration schema.StorageConfiguration, validator *schem
|
|||
|
||||
func validateSQLConfiguration(configuration *schema.SQLStorageConfiguration, validator *schema.StructValidator) {
|
||||
if configuration.Password == "" || configuration.Username == "" {
|
||||
validator.Push(errors.New("Username and password must be provided"))
|
||||
validator.Push(errors.New("the SQL username and password must be provided"))
|
||||
}
|
||||
|
||||
if configuration.Database == "" {
|
||||
validator.Push(errors.New("A database must be provided"))
|
||||
validator.Push(errors.New("the SQL database must be provided"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,8 +55,8 @@ func (suite *StorageSuite) TestShouldValidateSQLUsernamePasswordAndDatabaseArePr
|
|||
ValidateStorage(suite.configuration, suite.validator)
|
||||
|
||||
suite.Require().Len(suite.validator.Errors(), 2)
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "Username and password must be provided")
|
||||
suite.Assert().EqualError(suite.validator.Errors()[1], "A database must be provided")
|
||||
suite.Assert().EqualError(suite.validator.Errors()[0], "the SQL username and password must be provided")
|
||||
suite.Assert().EqualError(suite.validator.Errors()[1], "the SQL database must be provided")
|
||||
|
||||
suite.validator.Clear()
|
||||
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{
|
||||
|
|
|
@ -2,6 +2,7 @@ package utils
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -78,15 +79,34 @@ func TestShouldReturnZeroAndErrorOnInvalidTLSVersions(t *testing.T) {
|
|||
func TestShouldReturnErrWhenX509DirectoryNotExist(t *testing.T) {
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("/tmp/asdfzyxabc123/not/a/real/dir", nil)
|
||||
assert.NotNil(t, pool)
|
||||
|
||||
if runtime.GOOS == windows {
|
||||
require.Len(t, nonFatalErrs, 1)
|
||||
assert.EqualError(t, nonFatalErrs[0], "could not load system certificate pool which may result in untrusted certificate issues: crypto/x509: system root pool is not available on Windows")
|
||||
} else {
|
||||
assert.Len(t, nonFatalErrs, 0)
|
||||
}
|
||||
|
||||
require.Len(t, errs, 1)
|
||||
|
||||
if runtime.GOOS == windows {
|
||||
assert.EqualError(t, errs[0], "could not read certificates from directory open /tmp/asdfzyxabc123/not/a/real/dir: The system cannot find the path specified.")
|
||||
} else {
|
||||
assert.EqualError(t, errs[0], "could not read certificates from directory open /tmp/asdfzyxabc123/not/a/real/dir: no such file or directory")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotReturnErrWhenX509DirectoryExist(t *testing.T) {
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("/tmp", nil)
|
||||
assert.NotNil(t, pool)
|
||||
|
||||
if runtime.GOOS == windows {
|
||||
require.Len(t, nonFatalErrs, 1)
|
||||
assert.EqualError(t, nonFatalErrs[0], "could not load system certificate pool which may result in untrusted certificate issues: crypto/x509: system root pool is not available on Windows")
|
||||
} else {
|
||||
assert.Len(t, nonFatalErrs, 0)
|
||||
}
|
||||
|
||||
assert.Len(t, errs, 0)
|
||||
}
|
||||
|
||||
|
@ -101,10 +121,20 @@ func TestShouldRaiseNonFatalErrWhenNotifierTrustedCertConfigured(t *testing.T) {
|
|||
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("/tmp", config)
|
||||
assert.NotNil(t, pool)
|
||||
require.Len(t, nonFatalErrs, 1)
|
||||
assert.Len(t, errs, 0)
|
||||
|
||||
assert.EqualError(t, nonFatalErrs[0], "defining the trusted cert in the SMTP notifier is deprecated and will be removed in 4.28.0, please use the global certificates_directory instead")
|
||||
index := 0
|
||||
|
||||
if runtime.GOOS == windows {
|
||||
require.Len(t, nonFatalErrs, 2)
|
||||
assert.EqualError(t, nonFatalErrs[0], "could not load system certificate pool which may result in untrusted certificate issues: crypto/x509: system root pool is not available on Windows")
|
||||
|
||||
index = 1
|
||||
} else {
|
||||
require.Len(t, nonFatalErrs, 1)
|
||||
}
|
||||
|
||||
assert.Len(t, errs, 0)
|
||||
assert.EqualError(t, nonFatalErrs[index], "defining the trusted cert in the SMTP notifier is deprecated and will be removed in 4.28.0, please use the global certificates_directory instead")
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrAndNonFatalErrWhenNotifierTrustedCertConfiguredAndNotExist(t *testing.T) {
|
||||
|
@ -118,17 +148,35 @@ func TestShouldRaiseErrAndNonFatalErrWhenNotifierTrustedCertConfiguredAndNotExis
|
|||
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("/tmp", config)
|
||||
assert.NotNil(t, pool)
|
||||
|
||||
index := 0
|
||||
|
||||
if runtime.GOOS == windows {
|
||||
require.Len(t, nonFatalErrs, 2)
|
||||
assert.EqualError(t, nonFatalErrs[0], "could not load system certificate pool which may result in untrusted certificate issues: crypto/x509: system root pool is not available on Windows")
|
||||
|
||||
index = 1
|
||||
} else {
|
||||
require.Len(t, nonFatalErrs, 1)
|
||||
}
|
||||
|
||||
require.Len(t, errs, 1)
|
||||
|
||||
assert.EqualError(t, errs[0], "could not import legacy SMTP trusted_cert (see the new certificates_directory option) certificate /tmp/asdfzyxabc123/not/a/real/cert.pem (file does not exist)")
|
||||
assert.EqualError(t, nonFatalErrs[0], "defining the trusted cert in the SMTP notifier is deprecated and will be removed in 4.28.0, please use the global certificates_directory instead")
|
||||
assert.EqualError(t, nonFatalErrs[index], "defining the trusted cert in the SMTP notifier is deprecated and will be removed in 4.28.0, please use the global certificates_directory instead")
|
||||
}
|
||||
|
||||
func TestShouldReadCertsFromDirectoryButNotKeys(t *testing.T) {
|
||||
pool, errs, nonFatalErrs := NewX509CertPool("../suites/common/ssl/", nil)
|
||||
assert.NotNil(t, pool)
|
||||
require.Len(t, errs, 1)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
require.Len(t, nonFatalErrs, 1)
|
||||
assert.EqualError(t, nonFatalErrs[0], "could not load system certificate pool which may result in untrusted certificate issues: crypto/x509: system root pool is not available on Windows")
|
||||
} else {
|
||||
assert.Len(t, nonFatalErrs, 0)
|
||||
}
|
||||
|
||||
assert.EqualError(t, errs[0], "could not import certificate key.pem")
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ const Year = Day * 365
|
|||
// Month is an int based representation of the time unit.
|
||||
const Month = Year / 12
|
||||
|
||||
const windows = "windows"
|
||||
|
||||
// RFC3339Zero is the default value for time.Time.Unix().
|
||||
const RFC3339Zero = int64(-62135596800)
|
||||
|
||||
|
|
Loading…
Reference in New Issue