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.
pull/2662/head
James Elliott 2021-12-02 16:36:03 +11:00 committed by GitHub
parent 252b844b46
commit f90ca855e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 275 additions and 102 deletions

View File

@ -556,11 +556,16 @@ storage:
# host: 127.0.0.1 # host: 127.0.0.1
# port: 5432 # port: 5432
# database: authelia # database: authelia
# schema: public
# username: authelia # username: authelia
# ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html # ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
# password: mypassword # password: mypassword
# timeout: 5s # timeout: 5s
# sslmode: disable # ssl:
# mode: disable
# root_certificate: disable
# certificate: disable
# key: disable
## ##
## Notification Provider ## Notification Provider

View File

@ -32,10 +32,8 @@ See the [encryption_key docs](./index.md#encryption_key).
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
default: localhost required: yes
{: .label .label-config .label-blue } {: .label .label-config .label-red }
required: no
{: .label .label-config .label-green }
</div> </div>
The database server host. The database server host.

View File

@ -19,9 +19,14 @@ storage:
host: 127.0.0.1 host: 127.0.0.1
port: 5432 port: 5432
database: authelia database: authelia
schema: public
username: authelia username: authelia
password: mypassword 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 ## Options
@ -33,10 +38,8 @@ See the [encryption_key docs](./index.md#encryption_key).
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .label .label-config .label-purple }
default: localhost required: yes
{: .label .label-config .label-blue } {: .label .label-config .label-red }
required: no
{: .label .label-config .label-green }
</div> </div>
The database server host. The database server host.
@ -59,10 +62,29 @@ required: no
The port the database server is listening on. The port the database server is listening on.
### database ### database
<div markdown="1">
type: string
{: .label .label-config .label-purple }
required: yes
{: .label .label-config .label-red }
</div>
The database name on the database server that the assigned [user](#username) has access to for the purpose of The database name on the database server that the assigned [user](#username) has access to for the purpose of
**Authelia**. **Authelia**.
### schema
<div markdown="1">
type: string
{: .label .label-config .label-purple }
default: public
{: .label .label-config .label-blue }
required: no
{: .label .label-config .label-green }
</div>
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 ### username
<div markdown="1"> <div markdown="1">
type: string type: string
@ -96,7 +118,9 @@ required: no
The SQL connection timeout. The SQL connection timeout.
### sslmode ### ssl
#### mode
<div markdown="1"> <div markdown="1">
type: string type: string
{: .label .label-config .label-purple } {: .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) 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) or [pgx - PostgreSQL Driver and Toolkit Documentation](https://pkg.go.dev/github.com/jackc/pgx?tab=doc)
for more information. for more information.
#### root_certificate
<div markdown="1">
type: string
{: .label .label-config .label-purple }
required: no
{: .label .label-config .label-green }
</div>
The optional location of the root certificate file encoded in the PEM format for validation purposes.
#### certificate
<div markdown="1">
type: string
{: .label .label-config .label-purple }
required: no
{: .label .label-config .label-green }
</div>
The optional location of the certificate file encoded in the PEM format for validation purposes.
#### key
<div markdown="1">
type: string
{: .label .label-config .label-purple }
required: no
{: .label .label-config .label-green }
</div>
The optional location of the key file encoded in the PEM format for authentication purposes.

View File

@ -28,8 +28,13 @@ func NewStorageCmd() (cmd *cobra.Command) {
cmd.PersistentFlags().String("postgres.host", "", "the PostgreSQL hostname") cmd.PersistentFlags().String("postgres.host", "", "the PostgreSQL hostname")
cmd.PersistentFlags().Int("postgres.port", 5432, "the PostgreSQL port") cmd.PersistentFlags().Int("postgres.port", 5432, "the PostgreSQL port")
cmd.PersistentFlags().String("postgres.database", "authelia", "the PostgreSQL database name") 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.username", "authelia", "the PostgreSQL username")
cmd.PersistentFlags().String("postgres.password", "", "the PostgreSQL password") 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( cmd.AddCommand(
newStorageMigrateCmd(), newStorageMigrateCmd(),

View File

@ -40,23 +40,30 @@ func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) {
} }
mapping := map[string]string{ mapping := map[string]string{
"encryption-key": "storage.encryption_key", "encryption-key": "storage.encryption_key",
"sqlite.path": "storage.local.path", "sqlite.path": "storage.local.path",
"mysql.host": "storage.mysql.host",
"mysql.port": "storage.mysql.port", "mysql.host": "storage.mysql.host",
"mysql.database": "storage.mysql.database", "mysql.port": "storage.mysql.port",
"mysql.username": "storage.mysql.username", "mysql.database": "storage.mysql.database",
"mysql.password": "storage.mysql.password", "mysql.username": "storage.mysql.username",
"postgres.host": "storage.postgres.host", "mysql.password": "storage.mysql.password",
"postgres.port": "storage.postgres.port",
"postgres.database": "storage.postgres.database", "postgres.host": "storage.postgres.host",
"postgres.username": "storage.postgres.username", "postgres.port": "storage.postgres.port",
"postgres.password": "storage.postgres.password", "postgres.database": "storage.postgres.database",
"postgres.schema": "storage.postgres.schema", "postgres.schema": "storage.postgres.schema",
"period": "totp.period", "postgres.username": "storage.postgres.username",
"digits": "totp.digits", "postgres.password": "storage.postgres.password",
"algorithm": "totp.algorithm", "postgres.ssl.mode": "storage.postgres.ssl.mode",
"issuer": "totp.issuer", "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)) sources = append(sources, configuration.NewEnvironmentSource(configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter))

View File

@ -556,11 +556,16 @@ storage:
# host: 127.0.0.1 # host: 127.0.0.1
# port: 5432 # port: 5432
# database: authelia # database: authelia
# schema: public
# username: authelia # username: authelia
# ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html # ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
# password: mypassword # password: mypassword
# timeout: 5s # timeout: 5s
# sslmode: disable # ssl:
# mode: disable
# root_certificate: disable
# certificate: disable
# key: disable
## ##
## Notification Provider ## Notification Provider

View File

@ -22,10 +22,23 @@ type MySQLStorageConfiguration struct {
SQLStorageConfiguration `koanf:",squash"` SQLStorageConfiguration `koanf:",squash"`
} }
// PostgreSQLStorageConfiguration represents the configuration of a Postgres database. // PostgreSQLStorageConfiguration represents the configuration of a PostgreSQL database.
type PostgreSQLStorageConfiguration struct { type PostgreSQLStorageConfiguration struct {
SQLStorageConfiguration `koanf:",squash"` 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. // StorageConfiguration represents the configuration of the storage backend.
@ -37,16 +50,7 @@ type StorageConfiguration struct {
EncryptionKey string `koanf:"encryption_key"` EncryptionKey string `koanf:"encryption_key"`
} }
// DefaultPostgreSQLStorageConfiguration represents the default PostgreSQL configuration. // DefaultSQLStorageConfiguration represents the default SQL configuration.
var DefaultPostgreSQLStorageConfiguration = PostgreSQLStorageConfiguration{ var DefaultSQLStorageConfiguration = SQLStorageConfiguration{
SQLStorageConfiguration: SQLStorageConfiguration{ Timeout: 5 * time.Second,
Timeout: 5 * time.Second,
},
}
// DefaultMySQLStorageConfiguration represents the default MySQL configuration.
var DefaultMySQLStorageConfiguration = MySQLStorageConfiguration{
SQLStorageConfiguration: SQLStorageConfiguration{
Timeout: 5 * time.Second,
},
} }

View File

@ -61,6 +61,18 @@ const (
errFmtTOTPInvalidDigits = "totp: digits '%d' is invalid: must be 6 or 8" 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. // OpenID Error constants.
const ( const (
errFmtOIDCClientsDuplicateID = "openid connect provider: one or more clients have the same ID" errFmtOIDCClientsDuplicateID = "openid connect provider: one or more clients have the same ID"
@ -235,7 +247,13 @@ var ValidKeys = []string{
"storage.postgres.username", "storage.postgres.username",
"storage.postgres.password", "storage.postgres.password",
"storage.postgres.timeout", "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. // FileSystem Notifier Keys.
"notifier.filesystem.filename", "notifier.filesystem.filename",

View File

@ -2,19 +2,22 @@ package validator
import ( import (
"errors" "errors"
"fmt"
"strings"
"github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
) )
// ValidateStorage validates storage configuration. // ValidateStorage validates storage configuration.
func ValidateStorage(configuration schema.StorageConfiguration, validator *schema.StructValidator) { func ValidateStorage(configuration schema.StorageConfiguration, validator *schema.StructValidator) {
if configuration.Local == nil && configuration.MySQL == nil && configuration.PostgreSQL == nil { 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 { switch {
case configuration.MySQL != nil: case configuration.MySQL != nil:
validateMySQLConfiguration(&configuration.MySQL.SQLStorageConfiguration, validator) validateSQLConfiguration(&configuration.MySQL.SQLStorageConfiguration, validator, "mysql")
case configuration.PostgreSQL != nil: case configuration.PostgreSQL != nil:
validatePostgreSQLConfiguration(configuration.PostgreSQL, validator) validatePostgreSQLConfiguration(configuration.PostgreSQL, validator)
case configuration.Local != nil: case configuration.Local != nil:
@ -22,45 +25,47 @@ func ValidateStorage(configuration schema.StorageConfiguration, validator *schem
} }
if configuration.EncryptionKey == "" { 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 { } 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 { if configuration.Timeout == 0 {
configuration.Timeout = schema.DefaultMySQLStorageConfiguration.Timeout configuration.Timeout = schema.DefaultSQLStorageConfiguration.Timeout
} }
if configuration.Password == "" || configuration.Username == "" { if configuration.Host == "" {
validator.Push(errors.New("the SQL username and password must be provided")) validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "host"))
}
if configuration.Username == "" || configuration.Password == "" {
validator.Push(fmt.Errorf(errFmtStorageUserPassMustBeProvided, provider))
} }
if configuration.Database == "" { 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) { func validatePostgreSQLConfiguration(configuration *schema.PostgreSQLStorageConfiguration, validator *schema.StructValidator) {
validateMySQLConfiguration(&configuration.SQLStorageConfiguration, validator) validateSQLConfiguration(&configuration.SQLStorageConfiguration, validator, "postgres")
if configuration.Timeout == 0 { // Deprecated. TODO: Remove in v4.36.0.
configuration.Timeout = schema.DefaultPostgreSQLStorageConfiguration.Timeout if configuration.SSLMode != "" && configuration.SSL.Mode == "" {
configuration.SSL.Mode = configuration.SSLMode
} }
if configuration.SSLMode == "" { if configuration.SSL.Mode == "" {
configuration.SSLMode = testModeDisabled configuration.SSL.Mode = testModeDisabled
} } else if !utils.IsStringInSlice(configuration.SSL.Mode, storagePostgreSQLValidSSLModes) {
validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, configuration.SSL.Mode, strings.Join(storagePostgreSQLValidSSLModes, "', '")))
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'"))
} }
} }
func validateLocalStorageConfiguration(configuration *schema.LocalStorageConfiguration, validator *schema.StructValidator) { func validateLocalStorageConfiguration(configuration *schema.LocalStorageConfiguration, validator *schema.StructValidator) {
if configuration.Path == "" { if configuration.Path == "" {
validator.Push(errors.New("A file path must be provided with key 'path'")) validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, "local", "path"))
} }
} }

View File

@ -17,51 +17,57 @@ type StorageSuite struct {
func (suite *StorageSuite) SetupTest() { func (suite *StorageSuite) SetupTest() {
suite.validator = schema.NewStructValidator() suite.validator = schema.NewStructValidator()
suite.configuration.EncryptionKey = testEncryptionKey suite.configuration.EncryptionKey = testEncryptionKey
suite.configuration.Local = &schema.LocalStorageConfiguration{ suite.configuration.Local = nil
Path: "/this/is/a/path", suite.configuration.PostgreSQL = nil
} suite.configuration.MySQL = nil
} }
func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() { func (suite *StorageSuite) TestShouldValidateOneStorageIsConfigured() {
suite.configuration.Local = nil suite.configuration.Local = nil
suite.configuration.PostgreSQL = nil
suite.configuration.MySQL = nil
ValidateStorage(suite.configuration, suite.validator) 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.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() { func (suite *StorageSuite) TestShouldValidateLocalPathIsProvided() {
suite.configuration.Local.Path = "" suite.configuration.Local = &schema.LocalStorageConfiguration{
Path: "",
}
ValidateStorage(suite.configuration, suite.validator) 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.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.validator.Clear()
suite.configuration.Local.Path = "/myapth" suite.configuration.Local.Path = "/myapth"
ValidateStorage(suite.configuration, suite.validator) ValidateStorage(suite.configuration, suite.validator)
suite.Assert().False(suite.validator.HasWarnings()) suite.Require().Len(suite.validator.Warnings(), 0)
suite.Assert().False(suite.validator.HasErrors()) suite.Require().Len(suite.validator.Errors(), 0)
} }
func (suite *StorageSuite) TestShouldValidateSQLUsernamePasswordAndDatabaseAreProvided() { func (suite *StorageSuite) TestShouldValidateMySQLHostUsernamePasswordAndDatabaseAreProvided() {
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{} suite.configuration.MySQL = &schema.MySQLStorageConfiguration{}
ValidateStorage(suite.configuration, suite.validator) ValidateStorage(suite.configuration, suite.validator)
suite.Require().Len(suite.validator.Errors(), 2) suite.Require().Len(suite.validator.Errors(), 3)
suite.Assert().EqualError(suite.validator.Errors()[0], "the SQL username and password must be provided") suite.Assert().EqualError(suite.validator.Errors()[0], "storage: mysql: 'host' configuration option must be provided")
suite.Assert().EqualError(suite.validator.Errors()[1], "the SQL database 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.validator.Clear()
suite.configuration.MySQL = &schema.MySQLStorageConfiguration{ suite.configuration.MySQL = &schema.MySQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{ SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "localhost",
Username: "myuser", Username: "myuser",
Password: "pass", Password: "pass",
Database: "database", Database: "database",
@ -69,13 +75,39 @@ func (suite *StorageSuite) TestShouldValidateSQLUsernamePasswordAndDatabaseArePr
} }
ValidateStorage(suite.configuration, suite.validator) ValidateStorage(suite.configuration, suite.validator)
suite.Assert().False(suite.validator.HasWarnings()) suite.Require().Len(suite.validator.Warnings(), 0)
suite.Assert().False(suite.validator.HasErrors()) 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() { func (suite *StorageSuite) TestShouldValidatePostgresSSLModeIsDisableByDefault() {
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{ suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{ SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "db1",
Username: "myuser", Username: "myuser",
Password: "pass", Password: "pass",
Database: "database", Database: "database",
@ -84,47 +116,76 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeIsDisableByDefault()
ValidateStorage(suite.configuration, suite.validator) ValidateStorage(suite.configuration, suite.validator)
suite.Assert().False(suite.validator.HasWarnings()) suite.Assert().Len(suite.validator.Warnings(), 0)
suite.Assert().False(suite.validator.HasErrors()) 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() { func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{ suite.configuration.PostgreSQL = &schema.PostgreSQLStorageConfiguration{
SQLStorageConfiguration: schema.SQLStorageConfiguration{ SQLStorageConfiguration: schema.SQLStorageConfiguration{
Host: "db2",
Username: "myuser", Username: "myuser",
Password: "pass", Password: "pass",
Database: "database", Database: "database",
}, },
SSLMode: "unknown", SSL: schema.PostgreSQLSSLStorageConfiguration{
Mode: "unknown",
},
} }
ValidateStorage(suite.configuration, suite.validator) 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.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() { func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() {
suite.configuration.EncryptionKey = "" suite.configuration.EncryptionKey = ""
suite.configuration.Local = &schema.LocalStorageConfiguration{
Path: "/this/is/a/path",
}
ValidateStorage(suite.configuration, suite.validator) 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.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() { func (suite *StorageSuite) TestShouldRaiseErrorOnShortEncryptionKey() {
suite.configuration.EncryptionKey = "abc" suite.configuration.EncryptionKey = "abc"
suite.configuration.Local = &schema.LocalStorageConfiguration{
Path: "/this/is/a/path",
}
ValidateStorage(suite.configuration, suite.validator) 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.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) { func TestShouldRunStorageSuite(t *testing.T) {

View File

@ -41,10 +41,7 @@ func dataSourceNameMySQL(config schema.MySQLStorageConfiguration) (dataSourceNam
address += fmt.Sprintf(":%d", config.Port) address += fmt.Sprintf(":%d", config.Port)
} }
dataSourceName += fmt.Sprintf("tcp(%s)", address) dataSourceName += fmt.Sprintf("tcp(%s)/%s", address, config.Database)
if config.Database != "" {
dataSourceName += fmt.Sprintf("/%s", config.Database)
}
dataSourceName += "?" dataSourceName += "?"
dataSourceName += fmt.Sprintf("timeout=%ds&multiStatements=true&parseTime=true", int32(config.Timeout/time.Second)) dataSourceName += fmt.Sprintf("timeout=%ds&multiStatements=true&parseTime=true", int32(config.Timeout/time.Second))

View File

@ -56,20 +56,34 @@ func NewPostgreSQLProvider(config *schema.Configuration) (provider *PostgreSQLPr
func dataSourceNamePostgreSQL(config schema.PostgreSQLStorageConfiguration) (dataSourceName string) { func dataSourceNamePostgreSQL(config schema.PostgreSQLStorageConfiguration) (dataSourceName string) {
args := []string{ args := []string{
fmt.Sprintf("host=%s", config.Host),
fmt.Sprintf("user='%s'", config.Username), fmt.Sprintf("user='%s'", config.Username),
fmt.Sprintf("password='%s'", config.Password), fmt.Sprintf("password='%s'", config.Password),
} fmt.Sprintf("dbname=%s", config.Database),
if config.Host != "" {
args = append(args, fmt.Sprintf("host=%s", config.Host))
} }
if config.Port > 0 { if config.Port > 0 {
args = append(args, fmt.Sprintf("port=%d", config.Port)) args = append(args, fmt.Sprintf("port=%d", config.Port))
} }
if config.Database != "" { if config.Schema != "" {
args = append(args, fmt.Sprintf("dbname=%s", config.Database)) 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))) args = append(args, fmt.Sprintf("connect_timeout=%d", int32(config.Timeout/time.Second)))

View File

@ -168,12 +168,12 @@ func (s *CLISuite) TestStorageShouldShowErrWithoutConfig() {
output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info"}) output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info"})
s.Assert().EqualError(err, "exit status 1") 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"}) output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "history"})
s.Assert().EqualError(err, "exit status 1") 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() { func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() {