2021-08-03 16:55:21 +07:00
package configuration
import (
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2021-08-11 08:04:35 +07:00
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/configuration/validator"
"github.com/authelia/authelia/v4/internal/utils"
2021-08-03 16:55:21 +07:00
)
func TestShouldErrorSecretNotExist ( t * testing . T ) {
testReset ( )
2021-12-01 20:14:15 +07:00
dir , err := os . MkdirTemp ( "" , "authelia-test-secret-not-exist" )
2021-08-03 16:55:21 +07:00
assert . NoError ( t , err )
2022-03-03 18:20:43 +07:00
testSetEnv ( t , "JWT_SECRET_FILE" , filepath . Join ( dir , "jwt" ) )
testSetEnv ( t , "DUO_API_SECRET_KEY_FILE" , filepath . Join ( dir , "duo" ) )
testSetEnv ( t , "SESSION_SECRET_FILE" , filepath . Join ( dir , "session" ) )
testSetEnv ( t , "AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE" , filepath . Join ( dir , "authentication" ) )
testSetEnv ( t , "NOTIFIER_SMTP_PASSWORD_FILE" , filepath . Join ( dir , "notifier" ) )
testSetEnv ( t , "SESSION_REDIS_PASSWORD_FILE" , filepath . Join ( dir , "redis" ) )
testSetEnv ( t , "SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD_FILE" , filepath . Join ( dir , "redis-sentinel" ) )
testSetEnv ( t , "STORAGE_MYSQL_PASSWORD_FILE" , filepath . Join ( dir , "mysql" ) )
testSetEnv ( t , "STORAGE_POSTGRES_PASSWORD_FILE" , filepath . Join ( dir , "postgres" ) )
testSetEnv ( t , "SERVER_TLS_KEY_FILE" , filepath . Join ( dir , "tls" ) )
testSetEnv ( t , "IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE" , filepath . Join ( dir , "oidc-key" ) )
testSetEnv ( t , "IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE" , filepath . Join ( dir , "oidc-hmac" ) )
2021-08-03 16:55:21 +07:00
val := schema . NewStructValidator ( )
_ , _ , err = Load ( val , NewEnvironmentSource ( DefaultEnvPrefix , DefaultEnvDelimiter ) , NewSecretsSource ( DefaultEnvPrefix , DefaultEnvDelimiter ) )
assert . NoError ( t , err )
assert . Len ( t , val . Warnings ( ) , 0 )
errs := val . Errors ( )
require . Len ( t , errs , 12 )
sort . Sort ( utils . ErrSliceSortAlphabetical ( errs ) )
errFmt := utils . GetExpectedErrTxt ( "filenotfound" )
// ignore the errors before this as they are checked by the valdator.
assert . EqualError ( t , errs [ 0 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "authentication" ) , "authentication_backend.ldap.password" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "authentication" ) ) ) )
assert . EqualError ( t , errs [ 1 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "duo" ) , "duo_api.secret_key" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "duo" ) ) ) )
assert . EqualError ( t , errs [ 2 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "jwt" ) , "jwt_secret" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "jwt" ) ) ) )
assert . EqualError ( t , errs [ 3 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "mysql" ) , "storage.mysql.password" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "mysql" ) ) ) )
assert . EqualError ( t , errs [ 4 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "notifier" ) , "notifier.smtp.password" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "notifier" ) ) ) )
assert . EqualError ( t , errs [ 5 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "oidc-hmac" ) , "identity_providers.oidc.hmac_secret" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "oidc-hmac" ) ) ) )
assert . EqualError ( t , errs [ 6 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "oidc-key" ) , "identity_providers.oidc.issuer_private_key" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "oidc-key" ) ) ) )
assert . EqualError ( t , errs [ 7 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "postgres" ) , "storage.postgres.password" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "postgres" ) ) ) )
assert . EqualError ( t , errs [ 8 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "redis" ) , "session.redis.password" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "redis" ) ) ) )
assert . EqualError ( t , errs [ 9 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "redis-sentinel" ) , "session.redis.high_availability.sentinel_password" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "redis-sentinel" ) ) ) )
assert . EqualError ( t , errs [ 10 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "session" ) , "session.secret" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "session" ) ) ) )
2021-12-01 20:01:32 +07:00
assert . EqualError ( t , errs [ 11 ] , fmt . Sprintf ( errFmtSecretIOIssue , filepath . Join ( dir , "tls" ) , "server.tls.key" , fmt . Sprintf ( errFmt , filepath . Join ( dir , "tls" ) ) ) )
2021-08-03 16:55:21 +07:00
}
func TestLoadShouldReturnErrWithoutValidator ( t * testing . T ) {
_ , _ , err := Load ( nil , NewEnvironmentSource ( DefaultEnvPrefix , DefaultEnvDelimiter ) )
assert . EqualError ( t , err , "no validator provided" )
}
func TestLoadShouldReturnErrWithoutSources ( t * testing . T ) {
_ , _ , err := Load ( schema . NewStructValidator ( ) )
assert . EqualError ( t , err , "no sources provided" )
}
func TestShouldHaveNotifier ( t * testing . T ) {
testReset ( )
2022-03-03 18:20:43 +07:00
testSetEnv ( t , "SESSION_SECRET" , "abc" )
testSetEnv ( t , "STORAGE_MYSQL_PASSWORD" , "abc" )
testSetEnv ( t , "JWT_SECRET" , "abc" )
testSetEnv ( t , "AUTHENTICATION_BACKEND_LDAP_PASSWORD" , "abc" )
2021-08-03 16:55:21 +07:00
val := schema . NewStructValidator ( )
_ , config , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
assert . Len ( t , val . Errors ( ) , 0 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . NotNil ( t , config . Notifier )
}
func TestShouldValidateConfigurationWithEnv ( t * testing . T ) {
testReset ( )
2022-03-03 18:20:43 +07:00
testSetEnv ( t , "SESSION_SECRET" , "abc" )
testSetEnv ( t , "STORAGE_MYSQL_PASSWORD" , "abc" )
testSetEnv ( t , "JWT_SECRET" , "abc" )
testSetEnv ( t , "AUTHENTICATION_BACKEND_LDAP_PASSWORD" , "abc" )
2021-08-03 16:55:21 +07:00
val := schema . NewStructValidator ( )
_ , _ , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
assert . Len ( t , val . Errors ( ) , 0 )
assert . Len ( t , val . Warnings ( ) , 0 )
}
func TestShouldNotIgnoreInvalidEnvs ( t * testing . T ) {
testReset ( )
2022-03-03 18:20:43 +07:00
testSetEnv ( t , "SESSION_SECRET" , "an env session secret" )
testSetEnv ( t , "STORAGE_MYSQL_PASSWORD" , "an env storage mysql password" )
testSetEnv ( t , "STORAGE_MYSQL" , "a bad env" )
testSetEnv ( t , "JWT_SECRET" , "an env jwt secret" )
testSetEnv ( t , "AUTHENTICATION_BACKEND_LDAP_PASSWORD" , "an env authentication backend ldap password" )
testSetEnv ( t , "AUTHENTICATION_BACKEND_LDAP_URL" , "an env authentication backend ldap password" )
2021-08-03 16:55:21 +07:00
val := schema . NewStructValidator ( )
keys , _ , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
validator . ValidateKeys ( keys , DefaultEnvPrefix , val )
require . Len ( t , val . Warnings ( ) , 1 )
assert . Len ( t , val . Errors ( ) , 0 )
assert . EqualError ( t , val . Warnings ( ) [ 0 ] , fmt . Sprintf ( "configuration environment variable not expected: %sSTORAGE_MYSQL" , DefaultEnvPrefix ) )
}
func TestShouldValidateAndRaiseErrorsOnNormalConfigurationAndSecret ( t * testing . T ) {
testReset ( )
2022-03-03 18:20:43 +07:00
testSetEnv ( t , "SESSION_SECRET" , "an env session secret" )
testSetEnv ( t , "SESSION_SECRET_FILE" , "./test_resources/example_secret" )
testSetEnv ( t , "STORAGE_MYSQL_PASSWORD" , "an env storage mysql password" )
testSetEnv ( t , "JWT_SECRET_FILE" , "./test_resources/example_secret" )
testSetEnv ( t , "AUTHENTICATION_BACKEND_LDAP_PASSWORD" , "an env authentication backend ldap password" )
testSetEnv ( t , "STORAGE_ENCRYPTION_KEY" , "a_very_bad_encryption_key" )
2021-08-03 16:55:21 +07:00
val := schema . NewStructValidator ( )
_ , config , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
require . Len ( t , val . Errors ( ) , 1 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . EqualError ( t , val . Errors ( ) [ 0 ] , "secrets: error loading secret into key 'session.secret': it's already defined in other configuration sources" )
assert . Equal ( t , "example_secret value" , config . JWTSecret )
assert . Equal ( t , "example_secret value" , config . Session . Secret )
assert . Equal ( t , "an env storage mysql password" , config . Storage . MySQL . Password )
assert . Equal ( t , "an env authentication backend ldap password" , config . AuthenticationBackend . LDAP . Password )
2021-11-25 08:56:58 +07:00
assert . Equal ( t , "a_very_bad_encryption_key" , config . Storage . EncryptionKey )
2021-08-03 16:55:21 +07:00
}
func TestShouldRaiseIOErrOnUnreadableFile ( t * testing . T ) {
if runtime . GOOS == constWindows {
t . Skip ( "skipping test due to being on windows" )
}
testReset ( )
2021-12-01 20:14:15 +07:00
dir , err := os . MkdirTemp ( "" , "authelia-conf" )
2021-08-03 16:55:21 +07:00
assert . NoError ( t , err )
assert . NoError ( t , os . WriteFile ( filepath . Join ( dir , "myconf.yml" ) , [ ] byte ( "server:\n port: 9091\n" ) , 0000 ) )
cfg := filepath . Join ( dir , "myconf.yml" )
val := schema . NewStructValidator ( )
_ , _ , err = Load ( val , NewYAMLFileSource ( cfg ) )
assert . NoError ( t , err )
require . Len ( t , val . Errors ( ) , 1 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . EqualError ( t , val . Errors ( ) [ 0 ] , fmt . Sprintf ( "failed to load configuration from yaml file(%s) source: open %s: permission denied" , cfg , cfg ) )
}
func TestShouldValidateConfigurationWithEnvSecrets ( t * testing . T ) {
testReset ( )
2022-03-03 18:20:43 +07:00
testSetEnv ( t , "SESSION_SECRET_FILE" , "./test_resources/example_secret" )
testSetEnv ( t , "STORAGE_MYSQL_PASSWORD_FILE" , "./test_resources/example_secret" )
testSetEnv ( t , "JWT_SECRET_FILE" , "./test_resources/example_secret" )
testSetEnv ( t , "AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE" , "./test_resources/example_secret" )
testSetEnv ( t , "STORAGE_ENCRYPTION_KEY_FILE" , "./test_resources/example_secret" )
2021-08-03 16:55:21 +07:00
val := schema . NewStructValidator ( )
_ , config , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
assert . Len ( t , val . Errors ( ) , 0 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . Equal ( t , "example_secret value" , config . JWTSecret )
assert . Equal ( t , "example_secret value" , config . Session . Secret )
assert . Equal ( t , "example_secret value" , config . AuthenticationBackend . LDAP . Password )
assert . Equal ( t , "example_secret value" , config . Storage . MySQL . Password )
2021-11-25 08:56:58 +07:00
assert . Equal ( t , "example_secret value" , config . Storage . EncryptionKey )
2021-08-03 16:55:21 +07:00
}
func TestShouldValidateAndRaiseErrorsOnBadConfiguration ( t * testing . T ) {
testReset ( )
2022-03-03 18:20:43 +07:00
testSetEnv ( t , "SESSION_SECRET" , "abc" )
testSetEnv ( t , "STORAGE_MYSQL_PASSWORD" , "abc" )
testSetEnv ( t , "JWT_SECRET" , "abc" )
testSetEnv ( t , "AUTHENTICATION_BACKEND_LDAP_PASSWORD" , "abc" )
2021-08-03 16:55:21 +07:00
val := schema . NewStructValidator ( )
keys , _ , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config_bad_keys.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
validator . ValidateKeys ( keys , DefaultEnvPrefix , val )
require . Len ( t , val . Errors ( ) , 2 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . EqualError ( t , val . Errors ( ) [ 0 ] , "configuration key not expected: loggy_file" )
assert . EqualError ( t , val . Errors ( ) [ 1 ] , "invalid configuration key 'logs_level' was replaced by 'log.level'" )
}
2021-11-30 18:15:21 +07:00
func TestShouldRaiseErrOnInvalidNotifierSMTPSender ( t * testing . T ) {
testReset ( )
val := schema . NewStructValidator ( )
keys , _ , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config_smtp_sender_invalid.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
validator . ValidateKeys ( keys , DefaultEnvPrefix , val )
require . Len ( t , val . Errors ( ) , 1 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . EqualError ( t , val . Errors ( ) [ 0 ] , "error occurred during unmarshalling configuration: 1 error(s) decoding:\n\n* error decoding 'notifier.smtp.sender': could not parse 'admin' as a RFC5322 address: mail: missing '@' or angle-addr" )
}
func TestShouldHandleErrInvalidatorWhenSMTPSenderBlank ( t * testing . T ) {
testReset ( )
val := schema . NewStructValidator ( )
keys , config , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config_smtp_sender_blank.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
validator . ValidateKeys ( keys , DefaultEnvPrefix , val )
assert . Len ( t , val . Errors ( ) , 0 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . Equal ( t , "" , config . Notifier . SMTP . Sender . Name )
assert . Equal ( t , "" , config . Notifier . SMTP . Sender . Address )
2022-01-15 09:01:40 +07:00
validator . ValidateNotifier ( config . Notifier , val )
require . Len ( t , val . Errors ( ) , 1 )
assert . Len ( t , val . Warnings ( ) , 0 )
2022-02-28 10:15:01 +07:00
assert . EqualError ( t , val . Errors ( ) [ 0 ] , "notifier: smtp: option 'sender' is required" )
2022-01-15 09:01:40 +07:00
}
func TestShouldDecodeSMTPSenderWithoutName ( t * testing . T ) {
testReset ( )
val := schema . NewStructValidator ( )
keys , config , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
validator . ValidateKeys ( keys , DefaultEnvPrefix , val )
assert . Len ( t , val . Errors ( ) , 0 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . Equal ( t , "" , config . Notifier . SMTP . Sender . Name )
assert . Equal ( t , "admin@example.com" , config . Notifier . SMTP . Sender . Address )
}
func TestShouldDecodeSMTPSenderWithName ( t * testing . T ) {
testReset ( )
val := schema . NewStructValidator ( )
keys , config , err := Load ( val , NewDefaultSources ( [ ] string { "./test_resources/config_alt.yml" } , DefaultEnvPrefix , DefaultEnvDelimiter ) ... )
assert . NoError ( t , err )
validator . ValidateKeys ( keys , DefaultEnvPrefix , val )
assert . Len ( t , val . Errors ( ) , 0 )
assert . Len ( t , val . Warnings ( ) , 0 )
assert . Equal ( t , "Admin" , config . Notifier . SMTP . Sender . Name )
assert . Equal ( t , "admin@example.com" , config . Notifier . SMTP . Sender . Address )
2022-03-13 09:51:23 +07:00
assert . Equal ( t , schema . RememberMeDisabled , config . Session . RememberMeDuration )
2021-11-30 18:15:21 +07:00
}
2021-08-03 16:55:21 +07:00
func TestShouldNotReadConfigurationOnFSAccessDenied ( t * testing . T ) {
if runtime . GOOS == constWindows {
t . Skip ( "skipping test due to being on windows" )
}
testReset ( )
2021-12-01 20:14:15 +07:00
dir , err := os . MkdirTemp ( "" , "authelia-config" )
2021-08-03 16:55:21 +07:00
assert . NoError ( t , err )
cfg := filepath . Join ( dir , "config.yml" )
assert . NoError ( t , testCreateFile ( filepath . Join ( dir , "config.yml" ) , "port: 9091\n" , 0000 ) )
val := schema . NewStructValidator ( )
_ , _ , err = Load ( val , NewYAMLFileSource ( cfg ) )
assert . NoError ( t , err )
require . Len ( t , val . Errors ( ) , 1 )
assert . EqualError ( t , val . Errors ( ) [ 0 ] , fmt . Sprintf ( "failed to load configuration from yaml file(%s) source: open %s: permission denied" , cfg , cfg ) )
}
func TestShouldNotLoadDirectoryConfiguration ( t * testing . T ) {
testReset ( )
2021-12-01 20:14:15 +07:00
dir , err := os . MkdirTemp ( "" , "authelia-config" )
2021-08-03 16:55:21 +07:00
assert . NoError ( t , err )
val := schema . NewStructValidator ( )
_ , _ , err = Load ( val , NewYAMLFileSource ( dir ) )
assert . NoError ( t , err )
require . Len ( t , val . Errors ( ) , 1 )
assert . Len ( t , val . Warnings ( ) , 0 )
expectedErr := fmt . Sprintf ( utils . GetExpectedErrTxt ( "yamlisdir" ) , dir )
assert . EqualError ( t , val . Errors ( ) [ 0 ] , fmt . Sprintf ( "failed to load configuration from yaml file(%s) source: %s" , dir , expectedErr ) )
}
2022-03-03 18:20:43 +07:00
func testSetEnv ( t * testing . T , key , value string ) {
assert . NoError ( t , os . Setenv ( DefaultEnvPrefix + key , value ) )
}
2021-08-03 16:55:21 +07:00
func testReset ( ) {
testUnsetEnvName ( "STORAGE_MYSQL" )
testUnsetEnvName ( "JWT_SECRET" )
testUnsetEnvName ( "DUO_API_SECRET_KEY" )
testUnsetEnvName ( "SESSION_SECRET" )
testUnsetEnvName ( "AUTHENTICATION_BACKEND_LDAP_PASSWORD" )
testUnsetEnvName ( "AUTHENTICATION_BACKEND_LDAP_URL" )
testUnsetEnvName ( "NOTIFIER_SMTP_PASSWORD" )
testUnsetEnvName ( "SESSION_REDIS_PASSWORD" )
testUnsetEnvName ( "SESSION_REDIS_HIGH_AVAILABILITY_SENTINEL_PASSWORD" )
testUnsetEnvName ( "STORAGE_MYSQL_PASSWORD" )
testUnsetEnvName ( "STORAGE_POSTGRES_PASSWORD" )
2021-12-01 20:01:32 +07:00
testUnsetEnvName ( "SERVER_TLS_KEY" )
testUnsetEnvName ( "SERVER_PORT" )
2021-08-03 16:55:21 +07:00
testUnsetEnvName ( "IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY" )
testUnsetEnvName ( "IDENTITY_PROVIDERS_OIDC_HMAC_SECRET" )
2021-11-25 08:56:58 +07:00
testUnsetEnvName ( "STORAGE_ENCRYPTION_KEY" )
2021-08-03 16:55:21 +07:00
}
func testUnsetEnvName ( name string ) {
_ = os . Unsetenv ( DefaultEnvPrefix + name )
_ = os . Unsetenv ( DefaultEnvPrefix + name + constSecretSuffix )
}
func testCreateFile ( path , value string , perm os . FileMode ) ( err error ) {
return os . WriteFile ( path , [ ] byte ( value ) , perm )
}