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
parent
252b844b46
commit
f90ca855e3
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue