From f90ca855e3d6ea760241b82226f63291f5f24708 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Thu, 2 Dec 2021 16:36:03 +1100 Subject: [PATCH] feat(storage): postgresql schema and ssl options (#2659) Adds the schema name and all ssl options for PostgreSQL. Also a significant refactor of the storage validation process. --- config.template.yml | 7 +- docs/configuration/storage/mariadb.md | 6 +- docs/configuration/storage/postgres.md | 66 +++++++++- internal/commands/storage.go | 5 + internal/commands/storage_run.go | 41 ++++--- internal/configuration/config.template.yml | 7 +- internal/configuration/schema/storage.go | 32 ++--- internal/configuration/validator/const.go | 20 +++- internal/configuration/validator/storage.go | 45 +++---- .../configuration/validator/storage_test.go | 113 ++++++++++++++---- .../storage/sql_provider_backend_mysql.go | 5 +- .../storage/sql_provider_backend_postgres.go | 26 +++- internal/suites/suite_cli_test.go | 4 +- 13 files changed, 275 insertions(+), 102 deletions(-) diff --git a/config.template.yml b/config.template.yml index 471cd1478..e94f8ae16 100644 --- a/config.template.yml +++ b/config.template.yml @@ -556,11 +556,16 @@ storage: # host: 127.0.0.1 # port: 5432 # database: authelia + # schema: public # username: authelia # ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html # password: mypassword # timeout: 5s - # sslmode: disable + # ssl: + # mode: disable + # root_certificate: disable + # certificate: disable + # key: disable ## ## Notification Provider diff --git a/docs/configuration/storage/mariadb.md b/docs/configuration/storage/mariadb.md index be955e849..03fd76cce 100644 --- a/docs/configuration/storage/mariadb.md +++ b/docs/configuration/storage/mariadb.md @@ -32,10 +32,8 @@ See the [encryption_key docs](./index.md#encryption_key).
type: string {: .label .label-config .label-purple } -default: localhost -{: .label .label-config .label-blue } -required: no -{: .label .label-config .label-green } +required: yes +{: .label .label-config .label-red }
The database server host. diff --git a/docs/configuration/storage/postgres.md b/docs/configuration/storage/postgres.md index 788027cf5..34154a659 100644 --- a/docs/configuration/storage/postgres.md +++ b/docs/configuration/storage/postgres.md @@ -19,9 +19,14 @@ storage: host: 127.0.0.1 port: 5432 database: authelia + schema: public username: authelia password: mypassword - sslmode: disable + ssl: + mode: disable + root_certificate: /path/to/root_cert.pem + certificate: /path/to/cert.pem + key: /path/to/key.pem ``` ## Options @@ -33,10 +38,8 @@ See the [encryption_key docs](./index.md#encryption_key).
type: string {: .label .label-config .label-purple } -default: localhost -{: .label .label-config .label-blue } -required: no -{: .label .label-config .label-green } +required: yes +{: .label .label-config .label-red }
The database server host. @@ -59,10 +62,29 @@ required: no The port the database server is listening on. ### database +
+type: string +{: .label .label-config .label-purple } +required: yes +{: .label .label-config .label-red } +
The database name on the database server that the assigned [user](#username) has access to for the purpose of **Authelia**. +### schema +
+type: string +{: .label .label-config .label-purple } +default: public +{: .label .label-config .label-blue } +required: no +{: .label .label-config .label-green } +
+ +The database schema name to use on the database server that the assigned [user](#username) has access to for the purpose +of **Authelia**. By default this is the public schema. + ### username
type: string @@ -96,7 +118,9 @@ required: no The SQL connection timeout. -### sslmode +### ssl + +#### mode
type: string {: .label .label-config .label-purple } @@ -111,3 +135,33 @@ Valid options are 'disable', 'require', 'verify-ca', or 'verify-full'. See the [PostgreSQL Documentation](https://www.postgresql.org/docs/12/libpq-ssl.html) or [pgx - PostgreSQL Driver and Toolkit Documentation](https://pkg.go.dev/github.com/jackc/pgx?tab=doc) for more information. + +#### root_certificate +
+type: string +{: .label .label-config .label-purple } +required: no +{: .label .label-config .label-green } +
+ +The optional location of the root certificate file encoded in the PEM format for validation purposes. + +#### certificate +
+type: string +{: .label .label-config .label-purple } +required: no +{: .label .label-config .label-green } +
+ +The optional location of the certificate file encoded in the PEM format for validation purposes. + +#### key +
+type: string +{: .label .label-config .label-purple } +required: no +{: .label .label-config .label-green } +
+ +The optional location of the key file encoded in the PEM format for authentication purposes. diff --git a/internal/commands/storage.go b/internal/commands/storage.go index df1250077..1ac7c1317 100644 --- a/internal/commands/storage.go +++ b/internal/commands/storage.go @@ -28,8 +28,13 @@ func NewStorageCmd() (cmd *cobra.Command) { cmd.PersistentFlags().String("postgres.host", "", "the PostgreSQL hostname") cmd.PersistentFlags().Int("postgres.port", 5432, "the PostgreSQL port") cmd.PersistentFlags().String("postgres.database", "authelia", "the PostgreSQL database name") + cmd.PersistentFlags().String("postgres.schema", "public", "the PostgreSQL schema name") cmd.PersistentFlags().String("postgres.username", "authelia", "the PostgreSQL username") cmd.PersistentFlags().String("postgres.password", "", "the PostgreSQL password") + cmd.PersistentFlags().String("postgres.ssl.mode", "disable", "the PostgreSQL ssl mode") + cmd.PersistentFlags().String("postgres.ssl.root_certificate", "", "the PostgreSQL ssl root certificate file location") + cmd.PersistentFlags().String("postgres.ssl.certificate", "", "the PostgreSQL ssl certificate file location") + cmd.PersistentFlags().String("postgres.ssl.key", "", "the PostgreSQL ssl key file location") cmd.AddCommand( newStorageMigrateCmd(), diff --git a/internal/commands/storage_run.go b/internal/commands/storage_run.go index c24a7f679..87a382051 100644 --- a/internal/commands/storage_run.go +++ b/internal/commands/storage_run.go @@ -40,23 +40,30 @@ func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) { } mapping := map[string]string{ - "encryption-key": "storage.encryption_key", - "sqlite.path": "storage.local.path", - "mysql.host": "storage.mysql.host", - "mysql.port": "storage.mysql.port", - "mysql.database": "storage.mysql.database", - "mysql.username": "storage.mysql.username", - "mysql.password": "storage.mysql.password", - "postgres.host": "storage.postgres.host", - "postgres.port": "storage.postgres.port", - "postgres.database": "storage.postgres.database", - "postgres.username": "storage.postgres.username", - "postgres.password": "storage.postgres.password", - "postgres.schema": "storage.postgres.schema", - "period": "totp.period", - "digits": "totp.digits", - "algorithm": "totp.algorithm", - "issuer": "totp.issuer", + "encryption-key": "storage.encryption_key", + "sqlite.path": "storage.local.path", + + "mysql.host": "storage.mysql.host", + "mysql.port": "storage.mysql.port", + "mysql.database": "storage.mysql.database", + "mysql.username": "storage.mysql.username", + "mysql.password": "storage.mysql.password", + + "postgres.host": "storage.postgres.host", + "postgres.port": "storage.postgres.port", + "postgres.database": "storage.postgres.database", + "postgres.schema": "storage.postgres.schema", + "postgres.username": "storage.postgres.username", + "postgres.password": "storage.postgres.password", + "postgres.ssl.mode": "storage.postgres.ssl.mode", + "postgres.ssl.root_certificate": "storage.postgres.ssl.root_certificate", + "postgres.ssl.certificate": "storage.postgres.ssl.certificate", + "postgres.ssl.key": "storage.postgres.ssl.key", + + "period": "totp.period", + "digits": "totp.digits", + "algorithm": "totp.algorithm", + "issuer": "totp.issuer", } sources = append(sources, configuration.NewEnvironmentSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)) diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 471cd1478..e94f8ae16 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -556,11 +556,16 @@ storage: # host: 127.0.0.1 # port: 5432 # database: authelia + # schema: public # username: authelia # ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html # password: mypassword # timeout: 5s - # sslmode: disable + # ssl: + # mode: disable + # root_certificate: disable + # certificate: disable + # key: disable ## ## Notification Provider diff --git a/internal/configuration/schema/storage.go b/internal/configuration/schema/storage.go index 831872eb6..f806fd3ef 100644 --- a/internal/configuration/schema/storage.go +++ b/internal/configuration/schema/storage.go @@ -22,10 +22,23 @@ type MySQLStorageConfiguration struct { SQLStorageConfiguration `koanf:",squash"` } -// PostgreSQLStorageConfiguration represents the configuration of a Postgres database. +// PostgreSQLStorageConfiguration represents the configuration of a PostgreSQL database. type PostgreSQLStorageConfiguration struct { SQLStorageConfiguration `koanf:",squash"` - SSLMode string `koanf:"sslmode"` + Schema string `koanf:"schema"` + + SSL PostgreSQLSSLStorageConfiguration `koanf:"ssl"` + + // Deprecated. TODO: Remove in v4.36.0. + SSLMode string `koanf:"sslmode"` +} + +// PostgreSQLSSLStorageConfiguration represents the SSL configuration of a PostgreSQL database. +type PostgreSQLSSLStorageConfiguration struct { + Mode string `koanf:"mode"` + RootCertificate string `koanf:"root_certificate"` + Certificate string `koanf:"certificate"` + Key string `koanf:"key"` } // StorageConfiguration represents the configuration of the storage backend. @@ -37,16 +50,7 @@ type StorageConfiguration struct { EncryptionKey string `koanf:"encryption_key"` } -// DefaultPostgreSQLStorageConfiguration represents the default PostgreSQL configuration. -var DefaultPostgreSQLStorageConfiguration = PostgreSQLStorageConfiguration{ - SQLStorageConfiguration: SQLStorageConfiguration{ - Timeout: 5 * time.Second, - }, -} - -// DefaultMySQLStorageConfiguration represents the default MySQL configuration. -var DefaultMySQLStorageConfiguration = MySQLStorageConfiguration{ - SQLStorageConfiguration: SQLStorageConfiguration{ - Timeout: 5 * time.Second, - }, +// DefaultSQLStorageConfiguration represents the default SQL configuration. +var DefaultSQLStorageConfiguration = SQLStorageConfiguration{ + Timeout: 5 * time.Second, } diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 8c9900ab0..ab0a04f98 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -61,6 +61,18 @@ const ( errFmtTOTPInvalidDigits = "totp: digits '%d' is invalid: must be 6 or 8" ) +// Storage Error constants. +const ( + errStrStorage = "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided" + errStrStorageEncryptionKeyMustBeProvided = "storage: 'encryption_key' configuration option must be provided" + errStrStorageEncryptionKeyTooShort = "storage: 'encryption_key' configuration option must be 20 characters or longer" + errFmtStorageUserPassMustBeProvided = "storage: %s: 'username' and 'password' configuration options must be provided" //nolint: gosec + errFmtStorageOptionMustBeProvided = "storage: %s: '%s' configuration option must be provided" + errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: 'mode' configuration option '%s' is invalid: must be one of '%s'" +) + +var storagePostgreSQLValidSSLModes = []string{testModeDisabled, "require", "verify-ca", "verify-full"} + // OpenID Error constants. const ( errFmtOIDCClientsDuplicateID = "openid connect provider: one or more clients have the same ID" @@ -235,7 +247,13 @@ var ValidKeys = []string{ "storage.postgres.username", "storage.postgres.password", "storage.postgres.timeout", - "storage.postgres.sslmode", + "storage.postgres.schema", + "storage.postgres.ssl.mode", + "storage.postgres.ssl.root_certificate", + "storage.postgres.ssl.certificate", + "storage.postgres.ssl.key", + + "storage.postgres.sslmode", // Deprecated. TODO: Remove in v4.36.0. // FileSystem Notifier Keys. "notifier.filesystem.filename", diff --git a/internal/configuration/validator/storage.go b/internal/configuration/validator/storage.go index b4ab54a2c..0a2c69f8b 100644 --- a/internal/configuration/validator/storage.go +++ b/internal/configuration/validator/storage.go @@ -2,19 +2,22 @@ package validator import ( "errors" + "fmt" + "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" + "github.com/authelia/authelia/v4/internal/utils" ) // ValidateStorage validates storage configuration. func ValidateStorage(configuration schema.StorageConfiguration, validator *schema.StructValidator) { if configuration.Local == nil && configuration.MySQL == nil && configuration.PostgreSQL == nil { - validator.Push(errors.New("A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres'")) + validator.Push(errors.New(errStrStorage)) } switch { case configuration.MySQL != nil: - validateMySQLConfiguration(&configuration.MySQL.SQLStorageConfiguration, validator) + validateSQLConfiguration(&configuration.MySQL.SQLStorageConfiguration, validator, "mysql") case configuration.PostgreSQL != nil: validatePostgreSQLConfiguration(configuration.PostgreSQL, validator) case configuration.Local != nil: @@ -22,45 +25,47 @@ func ValidateStorage(configuration schema.StorageConfiguration, validator *schem } if configuration.EncryptionKey == "" { - validator.Push(errors.New("the configuration option storage.encryption_key must be provided")) + validator.Push(errors.New(errStrStorageEncryptionKeyMustBeProvided)) } else if len(configuration.EncryptionKey) < 20 { - validator.Push(errors.New("the configuration option storage.encryption_key must be 20 characters or longer")) + validator.Push(errors.New(errStrStorageEncryptionKeyTooShort)) } } -func validateMySQLConfiguration(configuration *schema.SQLStorageConfiguration, validator *schema.StructValidator) { +func validateSQLConfiguration(configuration *schema.SQLStorageConfiguration, validator *schema.StructValidator, provider string) { if configuration.Timeout == 0 { - configuration.Timeout = schema.DefaultMySQLStorageConfiguration.Timeout + configuration.Timeout = schema.DefaultSQLStorageConfiguration.Timeout } - if configuration.Password == "" || configuration.Username == "" { - validator.Push(errors.New("the SQL username and password must be provided")) + if configuration.Host == "" { + validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "host")) + } + + if configuration.Username == "" || configuration.Password == "" { + validator.Push(fmt.Errorf(errFmtStorageUserPassMustBeProvided, provider)) } if configuration.Database == "" { - validator.Push(errors.New("the SQL database must be provided")) + validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "database")) } } func validatePostgreSQLConfiguration(configuration *schema.PostgreSQLStorageConfiguration, validator *schema.StructValidator) { - validateMySQLConfiguration(&configuration.SQLStorageConfiguration, validator) + validateSQLConfiguration(&configuration.SQLStorageConfiguration, validator, "postgres") - if configuration.Timeout == 0 { - configuration.Timeout = schema.DefaultPostgreSQLStorageConfiguration.Timeout + // Deprecated. TODO: Remove in v4.36.0. + if configuration.SSLMode != "" && configuration.SSL.Mode == "" { + configuration.SSL.Mode = configuration.SSLMode } - if configuration.SSLMode == "" { - configuration.SSLMode = testModeDisabled - } - - if !(configuration.SSLMode == testModeDisabled || configuration.SSLMode == "require" || - configuration.SSLMode == "verify-ca" || configuration.SSLMode == "verify-full") { - validator.Push(errors.New("SSL mode must be 'disable', 'require', 'verify-ca', or 'verify-full'")) + if configuration.SSL.Mode == "" { + configuration.SSL.Mode = testModeDisabled + } else if !utils.IsStringInSlice(configuration.SSL.Mode, storagePostgreSQLValidSSLModes) { + validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, configuration.SSL.Mode, strings.Join(storagePostgreSQLValidSSLModes, "', '"))) } } func validateLocalStorageConfiguration(configuration *schema.LocalStorageConfiguration, validator *schema.StructValidator) { if configuration.Path == "" { - validator.Push(errors.New("A file path must be provided with key 'path'")) + validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, "local", "path")) } } diff --git a/internal/configuration/validator/storage_test.go b/internal/configuration/validator/storage_test.go index 927d93699..b1aa888c3 100644 --- a/internal/configuration/validator/storage_test.go +++ b/internal/configuration/validator/storage_test.go @@ -17,51 +17,57 @@ type StorageSuite struct { func (suite *StorageSuite) SetupTest() { suite.validator = schema.NewStructValidator() suite.configuration.EncryptionKey = testEncryptionKey - suite.configuration.Local = &schema.LocalStorageConfiguration{ - Path: "/this/is/a/path", - } + suite.configuration.Local = nil + suite.configuration.PostgreSQL = nil + suite.configuration.MySQL = nil } func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() { suite.configuration.Local = nil + suite.configuration.PostgreSQL = nil + suite.configuration.MySQL = nil ValidateStorage(suite.configuration, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Require().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres'") + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided") } func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() { - suite.configuration.Local.Path = "" + suite.configuration.Local = &schema.LocalStorageConfiguration{ + Path: "", + } ValidateStorage(suite.configuration, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Require().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "A file path must be provided with key 'path'") + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: local: 'path' configuration option must be provided") suite.validator.Clear() suite.configuration.Local.Path = "/myapth" ValidateStorage(suite.configuration, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Require().Len(suite.validator.Warnings(), 0) + suite.Require().Len(suite.validator.Errors(), 0) } -func (suite *StorageSuite) TestShouldValidateSQLUsernamePasswordAndDatabaseAreProvided() { +func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabaseAreProvided() { suite.configuration.MySQL = &schema.MySQLStorageConfiguration{} ValidateStorage(suite.configuration, suite.validator) - suite.Require().Len(suite.validator.Errors(), 2) - 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.Require().Len(suite.validator.Errors(), 3) + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: mysql: 'host' configuration option must be provided") + suite.Assert().EqualError(suite.validator.Errors()[1], "storage: mysql: 'username' and 'password' configuration options must be provided") + suite.Assert().EqualError(suite.validator.Errors()[2], "storage: mysql: 'database' configuration option must be provided") suite.validator.Clear() suite.configuration.MySQL = &schema.MySQLStorageConfiguration{ SQLStorageConfiguration: schema.SQLStorageConfiguration{ + Host: "localhost", Username: "myuser", Password: "pass", Database: "database", @@ -69,13 +75,39 @@ func (suite *StorageSuite) TestShouldValidateSQLUsernamePasswordAndDatabaseArePr } ValidateStorage(suite.configuration, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Require().Len(suite.validator.Warnings(), 0) + suite.Require().Len(suite.validator.Errors(), 0) +} + +func (suite *StorageSuite) TestShouldValidatePostgreSQLHostUsernamePasswordAndDatabaseAreProvided() { + suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{} + suite.configuration.MySQL = nil + ValidateStorage(suite.configuration, suite.validator) + + suite.Require().Len(suite.validator.Errors(), 3) + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: 'host' configuration option must be provided") + suite.Assert().EqualError(suite.validator.Errors()[1], "storage: postgres: 'username' and 'password' configuration options must be provided") + suite.Assert().EqualError(suite.validator.Errors()[2], "storage: postgres: 'database' configuration option must be provided") + + suite.validator.Clear() + suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{ + SQLStorageConfiguration: schema.SQLStorageConfiguration{ + Host: "postgre", + Username: "myuser", + Password: "pass", + Database: "database", + }, + } + ValidateStorage(suite.configuration, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) } func (suite *StorageSuite) TestShouldValidatePostgresSSLModeIsDisableByDefault() { suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{ SQLStorageConfiguration: schema.SQLStorageConfiguration{ + Host: "db1", Username: "myuser", Password: "pass", Database: "database", @@ -84,47 +116,76 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeIsDisableByDefault() ValidateStorage(suite.configuration, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) - suite.Assert().False(suite.validator.HasErrors()) + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) - suite.Assert().Equal("disable", suite.configuration.PostgreSQL.SSLMode) + suite.Assert().Equal("disable", suite.configuration.PostgreSQL.SSL.Mode) } func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() { suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{ SQLStorageConfiguration: schema.SQLStorageConfiguration{ + Host: "db2", Username: "myuser", Password: "pass", Database: "database", }, - SSLMode: "unknown", + SSL: schema.PostgreSQLSSLStorageConfiguration{ + Mode: "unknown", + }, } ValidateStorage(suite.configuration, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "SSL mode must be 'disable', 'require', 'verify-ca', or 'verify-full'") + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: 'mode' configuration option 'unknown' is invalid: must be one of 'disable', 'require', 'verify-ca', 'verify-full'") +} + +// Deprecated. TODO: Remove in v4.36.0. +func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeMappedForDeprecations() { + suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{ + SQLStorageConfiguration: schema.SQLStorageConfiguration{ + Host: "pg", + Username: "myuser", + Password: "pass", + Database: "database", + }, + SSLMode: "require", + } + + ValidateStorage(suite.configuration, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) + + suite.Assert().Equal(suite.configuration.PostgreSQL.SSL.Mode, "require") } func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() { suite.configuration.EncryptionKey = "" + suite.configuration.Local = &schema.LocalStorageConfiguration{ + Path: "/this/is/a/path", + } ValidateStorage(suite.configuration, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Require().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "the configuration option storage.encryption_key must be provided") + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: 'encryption_key' configuration option must be provided") } func (suite *StorageSuite) TestShouldRaiseErrorOnShortEncryptionKey() { suite.configuration.EncryptionKey = "abc" + suite.configuration.Local = &schema.LocalStorageConfiguration{ + Path: "/this/is/a/path", + } ValidateStorage(suite.configuration, suite.validator) - suite.Assert().False(suite.validator.HasWarnings()) + suite.Require().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "the configuration option storage.encryption_key must be 20 characters or longer") + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: 'encryption_key' configuration option must be 20 characters or longer") } func TestShouldRunStorageSuite(t *testing.T) { diff --git a/internal/storage/sql_provider_backend_mysql.go b/internal/storage/sql_provider_backend_mysql.go index dfdff770d..6c313ee57 100644 --- a/internal/storage/sql_provider_backend_mysql.go +++ b/internal/storage/sql_provider_backend_mysql.go @@ -41,10 +41,7 @@ func dataSourceNameMySQL(config schema.MySQLStorageConfiguration) (dataSourceNam address += fmt.Sprintf(":%d", config.Port) } - dataSourceName += fmt.Sprintf("tcp(%s)", address) - if config.Database != "" { - dataSourceName += fmt.Sprintf("/%s", config.Database) - } + dataSourceName += fmt.Sprintf("tcp(%s)/%s", address, config.Database) dataSourceName += "?" dataSourceName += fmt.Sprintf("timeout=%ds&multiStatements=true&parseTime=true", int32(config.Timeout/time.Second)) diff --git a/internal/storage/sql_provider_backend_postgres.go b/internal/storage/sql_provider_backend_postgres.go index 5eadf8539..300f3f50d 100644 --- a/internal/storage/sql_provider_backend_postgres.go +++ b/internal/storage/sql_provider_backend_postgres.go @@ -56,20 +56,34 @@ func NewPostgreSQLProvider(config *schema.Configuration) (provider *PostgreSQLPr func dataSourceNamePostgreSQL(config schema.PostgreSQLStorageConfiguration) (dataSourceName string) { args := []string{ + fmt.Sprintf("host=%s", config.Host), fmt.Sprintf("user='%s'", config.Username), fmt.Sprintf("password='%s'", config.Password), - } - - if config.Host != "" { - args = append(args, fmt.Sprintf("host=%s", config.Host)) + fmt.Sprintf("dbname=%s", config.Database), } if config.Port > 0 { args = append(args, fmt.Sprintf("port=%d", config.Port)) } - if config.Database != "" { - args = append(args, fmt.Sprintf("dbname=%s", config.Database)) + if config.Schema != "" { + args = append(args, fmt.Sprintf("search_path=%s", config.Schema)) + } + + if config.SSL.Mode != "" { + args = append(args, fmt.Sprintf("sslmode=%s", config.SSL.Mode)) + } + + if config.SSL.RootCertificate != "" { + args = append(args, fmt.Sprintf("sslrootcert=%s", config.SSL.RootCertificate)) + } + + if config.SSL.Certificate != "" { + args = append(args, fmt.Sprintf("sslcert=%s", config.SSL.Certificate)) + } + + if config.SSL.Key != "" { + args = append(args, fmt.Sprintf("sslkey=%s", config.SSL.Key)) } args = append(args, fmt.Sprintf("connect_timeout=%d", int32(config.Timeout/time.Second))) diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go index f287646eb..c64a56e01 100644 --- a/internal/suites/suite_cli_test.go +++ b/internal/suites/suite_cli_test.go @@ -168,12 +168,12 @@ func (s *CLISuite) TestStorageShouldShowErrWithoutConfig() { output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info"}) s.Assert().EqualError(err, "exit status 1") - s.Assert().Contains(output, "Error: A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres', the configuration option storage.encryption_key must be provided\n") + s.Assert().Contains(output, "Error: storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided, storage: 'encryption_key' configuration option must be provided\n") output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "history"}) s.Assert().EqualError(err, "exit status 1") - s.Assert().Contains(output, "Error: A storage configuration must be provided. It could be 'local', 'mysql' or 'postgres', the configuration option storage.encryption_key must be provided\n") + s.Assert().Contains(output, "Error: storage: configuration for a 'local', 'mysql' or 'postgres' database must be provided, storage: 'encryption_key' configuration option must be provided\n") } func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {