Skip to content

Commit

Permalink
fix: redis sentinel secret missing (#1839)
Browse files Browse the repository at this point in the history
* 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% coverage
  • Loading branch information
james-d-elliott committed Mar 22, 2021
1 parent 7ccbaaf commit a44f0cf
Show file tree
Hide file tree
Showing 19 changed files with 316 additions and 123 deletions.
21 changes: 11 additions & 10 deletions docs/configuration/secrets.md
Expand Up @@ -31,16 +31,17 @@ Here is the list of the environment variables which are considered
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 |
|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|
|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|

## Secrets in configuration file

Expand Down
5 changes: 5 additions & 0 deletions internal/authentication/file_user_provider_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"io/ioutil"
"log"
"os"
"runtime"
"strings"
"testing"

Expand Down Expand Up @@ -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")

Expand Down
3 changes: 3 additions & 0 deletions internal/configuration/const.go
@@ -0,0 +1,3 @@
package configuration

const windows = "windows"
12 changes: 4 additions & 8 deletions internal/configuration/reader.go
Expand Up @@ -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)

Expand Down
75 changes: 72 additions & 3 deletions internal/configuration/reader_test.go
Expand Up @@ -4,6 +4,7 @@ import (
"io/ioutil"
"os"
"path"
"runtime"
"sort"
"testing"

Expand All @@ -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")
}
Expand All @@ -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")

Expand All @@ -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")

Expand All @@ -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")

require.Len(t, errors, 1)

require.EqualError(t, errors[0], "Failed to open /tmp/authelia/permissions.yml: permission denied")
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)

assert.EqualError(t, errors[0], "Failed to open /tmp/authelia/permissions.yml: permission denied")
}
}

func TestShouldErrorParseBadConfigFile(t *testing.T) {
Expand All @@ -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"))

Expand All @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions internal/configuration/test_resources/config.yml
Expand Up @@ -101,6 +101,8 @@ session:
redis:
host: 127.0.0.1
port: 6379
high_availability:
sentinel_name: test

regulation:
max_retries: 3
Expand Down
4 changes: 0 additions & 4 deletions internal/configuration/validator/authentication.go
Expand Up @@ -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()
}

Expand Down
17 changes: 16 additions & 1 deletion internal/configuration/validator/authentication_test.go
Expand Up @@ -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{}

Expand Down
2 changes: 1 addition & 1 deletion internal/configuration/validator/configuration.go
Expand Up @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions internal/configuration/validator/configuration_test.go
@@ -1,6 +1,7 @@
package validator

import (
"runtime"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -170,7 +171,12 @@ func TestShouldRaiseErrorOnInvalidCertificatesDirectory(t *testing.T) {
ValidateConfiguration(&config, validator)

require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "Error checking certificate directory: stat not-a-real-file.go: no such file or directory")

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"
Expand Down

0 comments on commit a44f0cf

Please sign in to comment.