package validator import ( "crypto/tls" "testing" "github.com/stretchr/testify/suite" "github.com/authelia/authelia/v4/internal/configuration/schema" ) type StorageSuite struct { suite.Suite config schema.Storage val *schema.StructValidator } func (suite *StorageSuite) SetupTest() { suite.val = schema.NewStructValidator() suite.config.EncryptionKey = testEncryptionKey suite.config.Local = nil suite.config.PostgreSQL = nil suite.config.MySQL = nil } func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() { suite.config.Local = nil suite.config.PostgreSQL = nil suite.config.MySQL = nil ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided") } func (suite *StorageSuite) TestShouldValidateMultipleStorageIsConfigured() { suite.config.Local = &schema.StorageLocal{} suite.config.PostgreSQL = &schema.StoragePostgreSQL{} suite.config.MySQL = &schema.StorageMySQL{} ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: option 'local', 'mysql' and 'postgres' are mutually exclusive but 'local', 'mysql', and 'postgres' have been configured") } func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() { suite.config.Local = &schema.StorageLocal{ Path: "", } ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: local: option 'path' is required") suite.val.Clear() suite.config.Local.Path = "/myapth" ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 0) } func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabaseAreProvided() { suite.config.MySQL = &schema.StorageMySQL{} ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Errors(), 3) suite.EqualError(suite.val.Errors()[0], "storage: mysql: option 'address' is required") suite.EqualError(suite.val.Errors()[1], "storage: mysql: option 'username' is required") suite.EqualError(suite.val.Errors()[2], "storage: mysql: option 'database' is required") suite.val.Clear() suite.config.MySQL = &schema.StorageMySQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://localhost:3306"), }, Username: "myuser", Password: "pass", Database: "database", }, } ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 0) } func (suite *StorageSuite) TestShouldSetDefaultMySQLTLSServerName() { suite.config.MySQL = &schema.StorageMySQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{Address: MustParseAddress("tcp://mysql:1234")}, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Len(suite.val.Errors(), 0) suite.Equal(suite.config.MySQL.Address.Hostname(), suite.config.MySQL.TLS.ServerName) suite.Equal("mysql", suite.config.MySQL.TLS.ServerName) } func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLAddressScheme() { suite.config.MySQL = &schema.StorageMySQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{Address: MustParseAddress("udp://mysql:1234")}, Username: "myuser", Password: "pass", Database: "database", }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: mysql: option 'address' with value 'udp://mysql:1234' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'") } func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSVersion() { suite.config.MySQL = &schema.StorageMySQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://db1:3306"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: mysql: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured") } func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidMySQLTLSMinVersionGreaterThanMaximum() { suite.config.MySQL = &schema.StorageMySQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://db1:3306"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS11}, }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: mysql: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.1") } func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDatabaseAreProvided() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{} suite.config.MySQL = nil ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Errors(), 3) suite.EqualError(suite.val.Errors()[0], "storage: postgres: option 'address' is required") suite.EqualError(suite.val.Errors()[1], "storage: postgres: option 'username' is required") suite.EqualError(suite.val.Errors()[2], "storage: postgres: option 'database' is required") suite.val.Clear() suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Len(suite.val.Errors(), 0) } func (suite *StorageSuite) TestShouldValidatePostgresSchemaDefault() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Len(suite.val.Errors(), 0) suite.Nil(suite.config.PostgreSQL.SSL) //nolint:staticcheck suite.Nil(suite.config.PostgreSQL.TLS) suite.Equal("public", suite.config.PostgreSQL.Schema) } func (suite *StorageSuite) TestShouldValidatePostgresPortDefault() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre"), }, Username: "myuser", Password: "pass", Database: "database", }, Schema: "public", } ValidateStorage(suite.config, suite.val) suite.Assert().Len(suite.val.Warnings(), 0) suite.Assert().Len(suite.val.Errors(), 0) suite.Equal(uint16(5432), suite.config.PostgreSQL.Address.Port()) } func (suite *StorageSuite) TestShouldValidatePostgresTLSDefaults() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{}, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Len(suite.val.Errors(), 0) suite.Nil(suite.config.PostgreSQL.SSL) //nolint:staticcheck suite.Require().NotNil(suite.config.PostgreSQL.TLS) suite.Equal(uint16(tls.VersionTLS12), suite.config.PostgreSQL.TLS.MinimumVersion.Value) } func (suite *StorageSuite) TestShouldValidatePostgresServers() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", }, Servers: []schema.StoragePostgreSQLServer{ { Address: &schema.AddressTCP{ Address: MustParseAddress("udp://server1:4321"), }, }, {}, { Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://server1"), }, }, { Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://server1:5432"), }, TLS: &schema.TLS{}, }, { Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://server1:5432"), }, TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS10}, }, }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Require().Len(suite.config.PostgreSQL.Servers, 5) suite.Equal(uint16(5432), suite.config.PostgreSQL.Servers[2].Address.Port()) suite.Equal("server1", suite.config.PostgreSQL.Servers[3].TLS.ServerName) suite.Equal(schema.TLSVersion{Value: tls.VersionTLS12}, suite.config.PostgreSQL.Servers[3].TLS.MinimumVersion) errors := suite.val.Errors() suite.Require().Len(errors, 3) suite.EqualError(errors[0], "storage: postgres: servers: #1: option 'address' with value 'udp://server1:4321' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'") suite.EqualError(errors[1], "storage: postgres: servers: #2: option 'address' is required") suite.EqualError(errors[2], "storage: postgres: servers: #5: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.0") } func (suite *StorageSuite) TestShouldValidatePostgresServersTLSMinMaxFromPrimary() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS10}, MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, }, }, Servers: []schema.StoragePostgreSQLServer{ { Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://server1:5432"), }, TLS: &schema.TLS{}, }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Len(suite.val.Errors(), 0) suite.Require().Len(suite.config.PostgreSQL.Servers, 1) suite.Equal("server1", suite.config.PostgreSQL.Servers[0].TLS.ServerName) suite.Equal(schema.TLSVersion{Value: tls.VersionTLS10}, suite.config.PostgreSQL.Servers[0].TLS.MinimumVersion) suite.Equal(schema.TLSVersion{Value: tls.VersionTLS13}, suite.config.PostgreSQL.Servers[0].TLS.MaximumVersion) } func (suite *StorageSuite) TestShouldValidatePostgresUDP() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("udp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS10}, MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: postgres: option 'address' with value 'udp://postgre:4321' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'") } func (suite *StorageSuite) TestShouldValidatePostgresSetPort() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre"), }, Username: "myuser", Password: "pass", Database: "database", }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Len(suite.val.Errors(), 0) suite.Equal(uint16(5432), suite.config.PostgreSQL.Address.Port()) } func (suite *StorageSuite) TestShouldSetDefaultPostgreSQLTLSServerName() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS12}, }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Len(suite.val.Errors(), 0) suite.Equal(suite.config.PostgreSQL.Address.Hostname(), suite.config.PostgreSQL.TLS.ServerName) } func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLTLSVersion() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionSSL30}, //nolint:staticcheck }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: postgres: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured") } func (suite *StorageSuite) TestShouldRaiseErrorOnInvalidPostgreSQLMinVersionGreaterThanMaximum() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{ MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, MaximumVersion: schema.TLSVersion{Value: tls.VersionTLS11}, }, }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: postgres: tls: option combination of 'minimum_version' and 'maximum_version' is invalid: minimum version TLS1.3 is greater than the maximum version TLS1.1") } func (suite *StorageSuite) TestShouldValidatePostgresSSLDefaults() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", }, SSL: &schema.StoragePostgreSQLSSL{}, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 1) suite.Len(suite.val.Errors(), 0) suite.NotNil(suite.config.PostgreSQL.SSL) //nolint:staticcheck suite.Require().Nil(suite.config.PostgreSQL.TLS) suite.Equal(schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode, suite.config.PostgreSQL.SSL.Mode) //nolint:staticcheck } func (suite *StorageSuite) TestShouldRaiseErrorOnTLSAndLegacySSL() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", TLS: &schema.TLS{}, }, SSL: &schema.StoragePostgreSQLSSL{}, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: postgres: can't define both 'tls' and 'ssl' configuration options") } func (suite *StorageSuite) TestShouldValidatePostgresDefaultsDontOverrideConfiguration() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", }, Schema: "authelia", SSL: &schema.StoragePostgreSQLSSL{ Mode: "require", }, } ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Warnings(), 1) suite.Len(suite.val.Errors(), 0) suite.Equal("require", suite.config.PostgreSQL.SSL.Mode) //nolint:staticcheck suite.Equal("authelia", suite.config.PostgreSQL.Schema) suite.EqualError(suite.val.Warnings()[0], "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead") } func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() { suite.config.PostgreSQL = &schema.StoragePostgreSQL{ StorageSQL: schema.StorageSQL{ Address: &schema.AddressTCP{ Address: MustParseAddress("tcp://postgre:4321"), }, Username: "myuser", Password: "pass", Database: "database", }, SSL: &schema.StoragePostgreSQLSSL{ Mode: "unknown", }, } ValidateStorage(suite.config, suite.val) suite.Len(suite.val.Warnings(), 1) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', or 'verify-full' but it's configured as 'unknown'") } func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() { suite.config.EncryptionKey = "" suite.config.Local = &schema.StorageLocal{ Path: "/this/is/a/path", } ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: option 'encryption_key' is required") } func (suite *StorageSuite) TestShouldRaiseErrorOnShortEncryptionKey() { suite.config.EncryptionKey = "abc" suite.config.Local = &schema.StorageLocal{ Path: "/this/is/a/path", } ValidateStorage(suite.config, suite.val) suite.Require().Len(suite.val.Warnings(), 0) suite.Require().Len(suite.val.Errors(), 1) suite.EqualError(suite.val.Errors()[0], "storage: option 'encryption_key' must be 20 characters or longer") } func TestShouldRunStorageSuite(t *testing.T) { suite.Run(t, new(StorageSuite)) }