[FEATURE] File Secrets (#896)
* [FEATURE] File Secret Loading * add a validator for secrets * run the secrets validator before the main config validator * only allow a secret to be defined in one of: config, env, file env * remove LF if found in file * update configuration before main config validation * fix unit tests * implement secret testing * refactor the secrets validator * make check os agnostic * update docs * add warning when user attempts to use ENV instead of ENV file * discourage ENV in docs * update config template * oxford comma * apply suggestions from code review * rename Validate to ValidateConfiguration * add k8s example * add deprecation notice in docs and warning * style changespull/901/head
parent
0ec3f18b44
commit
b9fb33d806
|
@ -15,7 +15,7 @@ log_level: debug
|
||||||
|
|
||||||
# The secret used to generate JWT tokens when validating user identity by
|
# The secret used to generate JWT tokens when validating user identity by
|
||||||
# email confirmation.
|
# email confirmation.
|
||||||
# This secret can also be set using the env variables AUTHELIA_JWT_SECRET
|
# JWT Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
jwt_secret: a_very_important_secret
|
jwt_secret: a_very_important_secret
|
||||||
|
|
||||||
# Default redirection URL
|
# Default redirection URL
|
||||||
|
@ -58,7 +58,7 @@ totp:
|
||||||
duo_api:
|
duo_api:
|
||||||
hostname: api-123456789.example.com
|
hostname: api-123456789.example.com
|
||||||
integration_key: ABCDEF
|
integration_key: ABCDEF
|
||||||
# This secret can also be set using the env variables AUTHELIA_DUO_API_SECRET_KEY
|
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
secret_key: 1234567890abcdefghifjkl
|
secret_key: 1234567890abcdefghifjkl
|
||||||
|
|
||||||
# The authentication backend to use for verifying user passwords
|
# The authentication backend to use for verifying user passwords
|
||||||
|
@ -138,7 +138,7 @@ authentication_backend:
|
||||||
|
|
||||||
# The username and password of the admin user.
|
# The username and password of the admin user.
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
# This secret can also be set using the env variables AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: password
|
password: password
|
||||||
|
|
||||||
# File backend configuration.
|
# File backend configuration.
|
||||||
|
@ -271,7 +271,7 @@ session:
|
||||||
name: authelia_session
|
name: authelia_session
|
||||||
|
|
||||||
# The secret to encrypt the session data. This is only used with Redis.
|
# The secret to encrypt the session data. This is only used with Redis.
|
||||||
# This secret can also be set using the env variables AUTHELIA_SESSION_SECRET
|
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
secret: insecure_session_secret
|
secret: insecure_session_secret
|
||||||
|
|
||||||
# The time in seconds before the cookie expires and session is reset.
|
# The time in seconds before the cookie expires and session is reset.
|
||||||
|
@ -296,7 +296,7 @@ session:
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
# This secret can also be set using the env variables AUTHELIA_SESSION_REDIS_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: authelia
|
password: authelia
|
||||||
# This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
|
# This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
|
||||||
database_index: 0
|
database_index: 0
|
||||||
|
@ -334,7 +334,7 @@ storage:
|
||||||
port: 3306
|
port: 3306
|
||||||
database: authelia
|
database: authelia
|
||||||
username: authelia
|
username: authelia
|
||||||
# This secret can also be set using the env variables AUTHELIA_STORAGE_MYSQL_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: mypassword
|
password: mypassword
|
||||||
|
|
||||||
# Settings to connect to PostgreSQL server
|
# Settings to connect to PostgreSQL server
|
||||||
|
@ -343,8 +343,9 @@ storage:
|
||||||
# port: 5432
|
# port: 5432
|
||||||
# database: authelia
|
# database: authelia
|
||||||
# username: authelia
|
# username: authelia
|
||||||
# # This secret can also be set using the env variables AUTHELIA_STORAGE_POSTGRES_PASSWORD
|
# # Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
# password: mypassword
|
# password: mypassword
|
||||||
|
# sslmode: disable
|
||||||
|
|
||||||
# Configuration of the notification system.
|
# Configuration of the notification system.
|
||||||
#
|
#
|
||||||
|
@ -372,7 +373,7 @@ notifier:
|
||||||
# - use the disable_verify_cert boolean value to disable the validation (prefer the trusted_cert option as it's more secure)
|
# - use the disable_verify_cert boolean value to disable the validation (prefer the trusted_cert option as it's more secure)
|
||||||
smtp:
|
smtp:
|
||||||
username: test
|
username: test
|
||||||
# This secret can also be set using the env variables AUTHELIA_NOTIFIER_SMTP_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: password
|
password: password
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 1025
|
port: 1025
|
||||||
|
@ -390,7 +391,7 @@ notifier:
|
||||||
# You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
|
# You need to create an app password by following: https://support.google.com/accounts/answer/185833?hl=en
|
||||||
## smtp:
|
## smtp:
|
||||||
## username: myaccount@gmail.com
|
## username: myaccount@gmail.com
|
||||||
## # This secret can also be set using the env variables AUTHELIA_NOTIFIER_SMTP_PASSWORD
|
## # Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
## password: yourapppassword
|
## password: yourapppassword
|
||||||
## sender: admin@example.com
|
## sender: admin@example.com
|
||||||
## host: smtp.gmail.com
|
## host: smtp.gmail.com
|
||||||
|
|
|
@ -81,13 +81,13 @@ authentication_backend:
|
||||||
# one returned by the LDAP server is used.
|
# one returned by the LDAP server is used.
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
|
|
||||||
# This secret can also be set using the env variables AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: password
|
password: password
|
||||||
```
|
```
|
||||||
|
|
||||||
The user must have an email address in order for Authelia to perform
|
The user must have an email address in order for Authelia to perform
|
||||||
identity verification when password reset request is initiated or
|
identity verification when a user attempts to reset their password or
|
||||||
when a second factor device is registered.
|
register a second factor device.
|
||||||
|
|
||||||
## Important notes
|
## Important notes
|
||||||
|
|
||||||
|
@ -99,3 +99,7 @@ In order to avoid such problems, we highly recommended you follow https://www.ie
|
||||||
`sAMAccountName` for Microsoft Active Directory and `uid` for other implementations as the attribute holding the
|
`sAMAccountName` for Microsoft Active Directory and `uid` for other implementations as the attribute holding the
|
||||||
unique identifier for your users.
|
unique identifier for your users.
|
||||||
|
|
||||||
|
## Loading a password from a secret instead of inside the configuration
|
||||||
|
|
||||||
|
Password can also be defined using a [secret](../secrets.md).
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ log_file_path: /var/log/authelia.log
|
||||||
`optional: false`
|
`optional: false`
|
||||||
|
|
||||||
Defines the secret used to craft JWT tokens leveraged by the identity
|
Defines the secret used to craft JWT tokens leveraged by the identity
|
||||||
verification process
|
verification process. This can also be defined using a [secret](./secrets.md).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
jwt_secret: v3ry_important_s3cr3t
|
jwt_secret: v3ry_important_s3cr3t
|
||||||
|
|
|
@ -38,7 +38,7 @@ notifier:
|
||||||
# - use the disable_verify_cert boolean value to disable the validation (prefer the trusted_cert option as it's more secure)
|
# - use the disable_verify_cert boolean value to disable the validation (prefer the trusted_cert option as it's more secure)
|
||||||
smtp:
|
smtp:
|
||||||
username: test
|
username: test
|
||||||
# This secret can also be set using the env variables AUTHELIA_NOTIFIER_SMTP_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: password
|
password: password
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 1025
|
port: 1025
|
||||||
|
@ -62,9 +62,13 @@ described [here](https://support.google.com/accounts/answer/185833?hl=en)
|
||||||
notifier:
|
notifier:
|
||||||
smtp:
|
smtp:
|
||||||
username: myaccount@gmail.com
|
username: myaccount@gmail.com
|
||||||
# This secret can also be set using the env variables AUTHELIA_NOTIFIER_SMTP_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: yourapppassword
|
password: yourapppassword
|
||||||
sender: admin@example.com
|
sender: admin@example.com
|
||||||
host: smtp.gmail.com
|
host: smtp.gmail.com
|
||||||
port: 587
|
port: 587
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Loading a password from a secret instead of inside the configuration
|
||||||
|
|
||||||
|
Password can also be defined using a [secret](../secrets.md).
|
|
@ -16,25 +16,47 @@ below.
|
||||||
|
|
||||||
A secret can be configured using an environment variable with the
|
A secret can be configured using an environment variable with the
|
||||||
prefix AUTHELIA_ followed by the path of the option capitalized
|
prefix AUTHELIA_ followed by the path of the option capitalized
|
||||||
and with dots replaced by underscores.
|
and with dots replaced by underscores followed by the suffix _FILE.
|
||||||
|
|
||||||
For instance the LDAP password is identified by the path
|
The contents of the environment variable must be a path to a file
|
||||||
**authentication_backend.ldap.password**, so this password could
|
containing the secret data. This file must be readable by the
|
||||||
alternatively be set using the environment variable called
|
user the Authelia daemon is running as.
|
||||||
**AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD**.
|
|
||||||
|
For instance the LDAP password can be defined in the configuration
|
||||||
|
at the path **authentication_backend.ldap.password**, so this password
|
||||||
|
could alternatively be set using the environment variable called
|
||||||
|
**AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE**.
|
||||||
|
|
||||||
Here is the list of the environment variables which are considered
|
Here is the list of the environment variables which are considered
|
||||||
secrets and can be defined. Any other option defined using an
|
secrets and can be defined. Any other option defined using an
|
||||||
environment variable will not be replaced.
|
environment variable will not be replaced.
|
||||||
|
|
||||||
* AUTHELIA_JWT_SECRET
|
|Configuration Key |Environment Variable |
|
||||||
* AUTHELIA_DUO_API_SECRET_KEY
|
|:----------------------------------:|:------------------------------------------------:|
|
||||||
* AUTHELIA_SESSION_SECRET
|
|jwt_secret |AUTHELIA_JWT_SECRET_FILE |
|
||||||
* AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD
|
|duo_api.secret_key |AUTHELIA_DUO_API_SECRET_KEY_FILE |
|
||||||
* AUTHELIA_NOTIFIER_SMTP_PASSWORD
|
|session.secret |AUTHELIA_SESSION_SECRET_FILE |
|
||||||
* AUTHELIA_SESSION_REDIS_PASSWORD
|
|session.redis.password |AUTHELIA_SESSION_REDIS_PASSWORD_FILE |
|
||||||
* AUTHELIA_STORAGE_MYSQL_PASSWORD
|
|storage.mysql.password |AUTHELIA_STORAGE_MYSQL_PASSWORD_FILE |
|
||||||
* AUTHELIA_STORAGE_POSTGRES_PASSWORD
|
|storage.postgres.password |AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE |
|
||||||
|
|notifier.smtp.password |AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE |
|
||||||
|
|authentication_backend.ldap.password|AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE|
|
||||||
|
|
||||||
|
|
||||||
|
## Secrets exposed in an environment variable
|
||||||
|
|
||||||
|
Prior to implementing file secrets you were able to define the
|
||||||
|
values of secrets in the environment variables themselves
|
||||||
|
in plain text instead of referencing a file. This is still
|
||||||
|
supported but discouraged. If you still want to do this
|
||||||
|
just remove _FILE from the environment variable name
|
||||||
|
and define the value in insecure plain text. See
|
||||||
|
[this article](https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/)
|
||||||
|
for reasons why this is considered insecure and is discouraged.
|
||||||
|
|
||||||
|
**DEPRECATION NOTICE:** This backwards compatibility feature will be
|
||||||
|
**removed** in 4.18.0+.
|
||||||
|
|
||||||
|
|
||||||
## Secrets in configuration file
|
## Secrets in configuration file
|
||||||
|
|
||||||
|
@ -42,3 +64,151 @@ If for some reason you prefer keeping the secrets in the configuration
|
||||||
file, be sure to apply the right permissions to the file in order to
|
file, be sure to apply the right permissions to the file in order to
|
||||||
prevent secret leaks if an another application gets compromised on your
|
prevent secret leaks if an another application gets compromised on your
|
||||||
server. The UNIX permissions should probably be something like 600.
|
server. The UNIX permissions should probably be something like 600.
|
||||||
|
|
||||||
|
|
||||||
|
## Kubernetes
|
||||||
|
|
||||||
|
Secrets can be mounted as files using the following sample manifests.
|
||||||
|
|
||||||
|
|
||||||
|
### Kustomization
|
||||||
|
|
||||||
|
- **Filename:** ./kustomization.yaml
|
||||||
|
- **Command:** kubectl apply -k
|
||||||
|
- **Notes:** this kustomization expects the Authelia configuration.yml in
|
||||||
|
the same directory. You will need to edit the kustomization.yaml with your
|
||||||
|
desired secrets after the equal signs. If you change the value before the
|
||||||
|
equal sign you'll have to adjust the volumes section of the daemonset
|
||||||
|
template (or deployment template if you're using it).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#filename: ./kustomization.yaml
|
||||||
|
generatorOptions:
|
||||||
|
disableNameSuffixHash: true
|
||||||
|
labels:
|
||||||
|
type: generated
|
||||||
|
app: authelia
|
||||||
|
configMapGenerator:
|
||||||
|
- name: authelia
|
||||||
|
files:
|
||||||
|
- configuration.yml
|
||||||
|
secretGenerator:
|
||||||
|
- name: authelia
|
||||||
|
literals:
|
||||||
|
- jwt_secret=myverysecuresecret
|
||||||
|
- session_secret=mysessionsecret
|
||||||
|
- redis_password=myredispassword
|
||||||
|
- sql_password=mysqlpassword
|
||||||
|
- ldap_password=myldappassword
|
||||||
|
- duo_secret=myduosecretkey
|
||||||
|
- smtp_password=mysmtppassword
|
||||||
|
```
|
||||||
|
|
||||||
|
### DaemonSet
|
||||||
|
|
||||||
|
- **Filename:** ./daemonset.yaml
|
||||||
|
- **Command:** kubectl apply -f ./daemonset.yaml
|
||||||
|
- **Notes:** assumes Kubernetes API 1.16 or greater
|
||||||
|
```yaml
|
||||||
|
#filename: daemonset.yaml
|
||||||
|
#command: kubectl apply -f daemonset.yaml
|
||||||
|
#notes: assumes kubernetes api 1.16+
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: authelia
|
||||||
|
labels:
|
||||||
|
app: authelia
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: authelia
|
||||||
|
updateStrategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: authelia
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: authelia
|
||||||
|
image: authelia/authelia:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: AUTHELIA_JWT_SECRET_FILE
|
||||||
|
value: /usr/app/secrets/jwt
|
||||||
|
- name: AUTHELIA_DUO_API_SECRET_KEY_FILE
|
||||||
|
value: /usr/app/secrets/duo
|
||||||
|
- name: AUTHELIA_SESSION_SECRET_FILE
|
||||||
|
value: /usr/app/secrets/session
|
||||||
|
- name: AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE
|
||||||
|
value: /usr/app/secrets/ldap_password
|
||||||
|
- name: AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
|
||||||
|
value: /usr/app/secrets/smtp_password
|
||||||
|
- name: AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE
|
||||||
|
value: /usr/app/secrets/sql_password
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/configuration
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 4
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/configuration
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 2
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/configuration
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 5
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /etc/authelia
|
||||||
|
name: config-volume
|
||||||
|
- mountPath: /usr/app/secrets
|
||||||
|
name: secrets
|
||||||
|
readOnly: true
|
||||||
|
- mountPath: /etc/localtime
|
||||||
|
name: localtime
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: config-volume
|
||||||
|
configMap:
|
||||||
|
name: authelia
|
||||||
|
items:
|
||||||
|
- key: configuration.yml
|
||||||
|
path: configuration.yml
|
||||||
|
- name: secrets
|
||||||
|
secret:
|
||||||
|
secretName: authelia
|
||||||
|
items:
|
||||||
|
- key: jwt_secret
|
||||||
|
path: jwt
|
||||||
|
- key: duo_secret
|
||||||
|
path: duo
|
||||||
|
- key: session_secret
|
||||||
|
path: session
|
||||||
|
- key: redis_password
|
||||||
|
path: redis_password
|
||||||
|
- key: sql_password
|
||||||
|
path: sql_password
|
||||||
|
- key: ldap_password
|
||||||
|
path: ldap_password
|
||||||
|
- key: smtp_password
|
||||||
|
path: smtp_password
|
||||||
|
- name: localtime
|
||||||
|
hostPath:
|
||||||
|
path: /etc/localtime
|
||||||
|
```
|
|
@ -23,7 +23,7 @@ session:
|
||||||
name: authelia_session
|
name: authelia_session
|
||||||
|
|
||||||
# The secret to encrypt the session cookie.
|
# The secret to encrypt the session cookie.
|
||||||
# This secret can also be set using the env variables AUTHELIA_SESSION_SECRET
|
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
secret: unsecure_session_secret
|
secret: unsecure_session_secret
|
||||||
|
|
||||||
# The time in seconds before the cookie expires and session is reset.
|
# The time in seconds before the cookie expires and session is reset.
|
||||||
|
@ -48,7 +48,7 @@ session:
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
# This secret can also be set using the env variables AUTHELIA_SESSION_REDIS_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: authelia
|
password: authelia
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,10 @@ storage:
|
||||||
port: 3306
|
port: 3306
|
||||||
database: authelia
|
database: authelia
|
||||||
username: authelia
|
username: authelia
|
||||||
# This secret can also be set using the env variables AUTHELIA_STORAGE_MYSQL_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: mypassword
|
password: mypassword
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Loading a password from a secret instead of inside the configuration
|
||||||
|
|
||||||
|
Password can also be defined using a [secret](../secrets.md).
|
||||||
|
|
|
@ -15,6 +15,10 @@ storage:
|
||||||
port: 3306
|
port: 3306
|
||||||
database: authelia
|
database: authelia
|
||||||
username: authelia
|
username: authelia
|
||||||
# This secret can also be set using the env variables AUTHELIA_STORAGE_MYSQL_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: mypassword
|
password: mypassword
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Loading a password from a secret instead of inside the configuration
|
||||||
|
|
||||||
|
Password can also be defined using a [secret](../secrets.md).
|
|
@ -15,6 +15,19 @@ storage:
|
||||||
port: 5432
|
port: 5432
|
||||||
database: authelia
|
database: authelia
|
||||||
username: authelia
|
username: authelia
|
||||||
# This secret can also be set using the env variables AUTHELIA_STORAGE_POSTGRES_PASSWORD
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: mypassword
|
password: mypassword
|
||||||
|
sslmode: disable
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SSL Mode
|
||||||
|
|
||||||
|
SSL mode configures how to handle SSL connections with Postgres.
|
||||||
|
Valid options are 'disable', 'require', 'verify-ca', or 'verify-full'.
|
||||||
|
See the [PostgreSQL Documentation](https://www.postgresql.org/docs/12/libpq-ssl.html)
|
||||||
|
or [Pure Go Postgres driver Documentation](https://godoc.org/github.com/lib/pq)
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
## Loading a password from a secret instead of inside the configuration
|
||||||
|
|
||||||
|
Password can also be defined using a [secret](../secrets.md).
|
|
@ -12,19 +12,27 @@ import (
|
||||||
|
|
||||||
// Read a YAML configuration and create a Configuration object out of it.
|
// Read a YAML configuration and create a Configuration object out of it.
|
||||||
func Read(configPath string) (*schema.Configuration, []error) {
|
func Read(configPath string) (*schema.Configuration, []error) {
|
||||||
viper.SetEnvPrefix("AUTHELIA")
|
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
|
||||||
// we need to bind all env variables as long as https://github.com/spf13/viper/issues/761
|
// we need to bind all env variables as long as https://github.com/spf13/viper/issues/761
|
||||||
// is not resolved.
|
// is not resolved.
|
||||||
viper.BindEnv("jwt_secret") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.BindEnv("authelia.jwt_secret") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
viper.BindEnv("duo_api.secret_key") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.BindEnv("authelia.duo_api.secret_key") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
viper.BindEnv("session.secret") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.BindEnv("authelia.session.secret") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
viper.BindEnv("authentication_backend.ldap.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.BindEnv("authelia.authentication_backend.ldap.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
viper.BindEnv("notifier.smtp.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.BindEnv("authelia.notifier.smtp.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
viper.BindEnv("session.redis.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.BindEnv("authelia.session.redis.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
viper.BindEnv("storage.mysql.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.BindEnv("authelia.storage.mysql.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
viper.BindEnv("storage.postgres.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.BindEnv("authelia.storage.postgres.password") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
|
||||||
|
viper.BindEnv("authelia.jwt_secret.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
viper.BindEnv("authelia.duo_api.secret_key.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
viper.BindEnv("authelia.session.secret.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
viper.BindEnv("authelia.authentication_backend.ldap.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
viper.BindEnv("authelia.notifier.smtp.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
viper.BindEnv("authelia.session.redis.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
viper.BindEnv("authelia.storage.mysql.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
viper.BindEnv("authelia.storage.postgres.password.file") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
|
||||||
viper.SetConfigFile(configPath)
|
viper.SetConfigFile(configPath)
|
||||||
|
|
||||||
|
@ -38,7 +46,8 @@ func Read(configPath string) (*schema.Configuration, []error) {
|
||||||
viper.Unmarshal(&configuration) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
viper.Unmarshal(&configuration) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
|
||||||
val := schema.NewStructValidator()
|
val := schema.NewStructValidator()
|
||||||
validator.Validate(&configuration, val)
|
validator.ValidateSecrets(&configuration, val, viper.GetViper())
|
||||||
|
validator.ValidateConfiguration(&configuration, val)
|
||||||
|
|
||||||
if val.HasErrors() {
|
if val.HasErrors() {
|
||||||
return nil, val.Errors()
|
return nil, val.Errors()
|
||||||
|
|
|
@ -2,12 +2,25 @@ package configuration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func resetEnv() {
|
||||||
|
_ = os.Unsetenv("AUTHELIA_JWT_SECRET")
|
||||||
|
_ = os.Unsetenv("AUTHELIA_DUO_API_SECRET_KEY")
|
||||||
|
_ = os.Unsetenv("AUTHELIA_SESSION_SECRET")
|
||||||
|
_ = os.Unsetenv("AUTHELIA_SESSION_SECRET")
|
||||||
|
_ = os.Unsetenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD")
|
||||||
|
_ = os.Unsetenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD")
|
||||||
|
_ = os.Unsetenv("AUTHELIA_SESSION_REDIS_PASSWORD")
|
||||||
|
_ = os.Unsetenv("AUTHELIA_STORAGE_MYSQL_PASSWORD")
|
||||||
|
_ = os.Unsetenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD")
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldParseConfigFile(t *testing.T) {
|
func TestShouldParseConfigFile(t *testing.T) {
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_JWT_SECRET", "secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_JWT_SECRET", "secret_from_env"))
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_DUO_API_SECRET_KEY", "duo_secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_DUO_API_SECRET_KEY", "duo_secret_from_env"))
|
||||||
|
@ -16,7 +29,6 @@ func TestShouldParseConfigFile(t *testing.T) {
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD", "mysql_secret_from_env"))
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD", "mysql_secret_from_env"))
|
||||||
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
|
||||||
|
|
||||||
config, errors := Read("./test_resources/config.yml")
|
config, errors := Read("./test_resources/config.yml")
|
||||||
|
|
||||||
|
@ -37,8 +49,58 @@ func TestShouldParseConfigFile(t *testing.T) {
|
||||||
assert.Equal(t, "smtp_secret_from_env", config.Notifier.SMTP.Password)
|
assert.Equal(t, "smtp_secret_from_env", config.Notifier.SMTP.Password)
|
||||||
assert.Equal(t, "redis_secret_from_env", config.Session.Redis.Password)
|
assert.Equal(t, "redis_secret_from_env", config.Session.Redis.Password)
|
||||||
assert.Equal(t, "mysql_secret_from_env", config.Storage.MySQL.Password)
|
assert.Equal(t, "mysql_secret_from_env", config.Storage.MySQL.Password)
|
||||||
|
|
||||||
|
assert.Equal(t, "deny", config.AccessControl.DefaultPolicy)
|
||||||
|
assert.Len(t, config.AccessControl.Rules, 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldParseAltConfigFile(t *testing.T) {
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
||||||
|
config, errors := Read("./test_resources/config_alt.yml")
|
||||||
|
require.Len(t, errors, 0)
|
||||||
|
|
||||||
|
assert.Equal(t, 9091, config.Port)
|
||||||
|
assert.Equal(t, "debug", config.LogLevel)
|
||||||
|
assert.Equal(t, "https://home.example.com:8080/", config.DefaultRedirectionURL)
|
||||||
|
assert.Equal(t, "authelia.com", config.TOTP.Issuer)
|
||||||
|
assert.Equal(t, "secret_from_env", config.JWTSecret)
|
||||||
|
|
||||||
|
assert.Equal(t, "api-123456789.example.com", config.DuoAPI.Hostname)
|
||||||
|
assert.Equal(t, "ABCDEF", config.DuoAPI.IntegrationKey)
|
||||||
assert.Equal(t, "postgres_secret_from_env", config.Storage.PostgreSQL.Password)
|
assert.Equal(t, "postgres_secret_from_env", config.Storage.PostgreSQL.Password)
|
||||||
|
|
||||||
assert.Equal(t, "deny", config.AccessControl.DefaultPolicy)
|
assert.Equal(t, "deny", config.AccessControl.DefaultPolicy)
|
||||||
assert.Len(t, config.AccessControl.Rules, 12)
|
assert.Len(t, config.AccessControl.Rules, 12)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldOnlyAllowOneEnvType(t *testing.T) {
|
||||||
|
resetEnv()
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD", "postgres_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE", "/tmp/postgres_secret"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_JWT_SECRET", "secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_DUO_API_SECRET_KEY", "duo_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_SESSION_SECRET", "session_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD", "ldap_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
||||||
|
_, errors := Read("./test_resources/config_alt.yml")
|
||||||
|
|
||||||
|
require.Len(t, errors, 2)
|
||||||
|
assert.EqualError(t, errors[0], "secret is defined in multiple areas: storage.postgres.password")
|
||||||
|
assert.True(t, strings.HasPrefix(errors[1].Error(), "error loading secret file (storage.postgres.password): open /tmp/postgres_secret: "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldOnlyAllowEnvOrConfig(t *testing.T) {
|
||||||
|
resetEnv()
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_STORAGE_MYSQL_PASSWORD", "mysql_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_JWT_SECRET", "secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_DUO_API_SECRET_KEY", "duo_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_SESSION_SECRET", "session_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD", "ldap_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_NOTIFIER_SMTP_PASSWORD", "smtp_secret_from_env"))
|
||||||
|
require.NoError(t, os.Setenv("AUTHELIA_SESSION_REDIS_PASSWORD", "redis_secret_from_env"))
|
||||||
|
_, errors := Read("./test_resources/config_with_secret.yml")
|
||||||
|
|
||||||
|
require.Len(t, errors, 1)
|
||||||
|
require.EqualError(t, errors[0], "error loading secret (jwt_secret): it's already defined in the config file")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
###############################################################
|
||||||
|
# Authelia configuration #
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 9091
|
||||||
|
|
||||||
|
log_level: debug
|
||||||
|
default_redirection_url: https://home.example.com:8080/
|
||||||
|
|
||||||
|
totp:
|
||||||
|
issuer: authelia.com
|
||||||
|
|
||||||
|
duo_api:
|
||||||
|
hostname: api-123456789.example.com
|
||||||
|
integration_key: ABCDEF
|
||||||
|
|
||||||
|
authentication_backend:
|
||||||
|
ldap:
|
||||||
|
url: ldap://127.0.0.1
|
||||||
|
base_dn: dc=example,dc=com
|
||||||
|
username_attribute: uid
|
||||||
|
additional_users_dn: ou=users
|
||||||
|
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
||||||
|
additional_groups_dn: ou=groups
|
||||||
|
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||||
|
group_name_attribute: cn
|
||||||
|
mail_attribute: mail
|
||||||
|
user: cn=admin,dc=example,dc=com
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
default_policy: deny
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# Rules applied to everyone
|
||||||
|
- domain: public.example.com
|
||||||
|
policy: bypass
|
||||||
|
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: one_factor
|
||||||
|
# Network based rule, if not provided any network matches.
|
||||||
|
networks:
|
||||||
|
- 192.168.1.0/24
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: [singlefactor.example.com, onefactor.example.com]
|
||||||
|
policy: one_factor
|
||||||
|
|
||||||
|
# Rules applied to 'admins' group
|
||||||
|
- domain: "mx2.mail.example.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: deny
|
||||||
|
- domain: "*.example.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/groups/dev/.*$"
|
||||||
|
subject: "group:dev"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to user 'john'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/john/.*$"
|
||||||
|
subject: "user:john"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group and user 'john'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/deny-all.*$"
|
||||||
|
subject: ["group:dev", "user:john"]
|
||||||
|
policy: denied
|
||||||
|
|
||||||
|
# Rules applied to user 'harry'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/harry/.*$"
|
||||||
|
subject: "user:harry"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to user 'bob'
|
||||||
|
- domain: "*.mail.example.com"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
- domain: "dev.example.com"
|
||||||
|
resources:
|
||||||
|
- "^/users/bob/.*$"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
session:
|
||||||
|
name: authelia_session
|
||||||
|
expiration: 3600000 # 1 hour
|
||||||
|
inactivity: 300000 # 5 minutes
|
||||||
|
domain: example.com
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
|
||||||
|
regulation:
|
||||||
|
max_retries: 3
|
||||||
|
find_time: 120
|
||||||
|
ban_time: 300
|
||||||
|
|
||||||
|
storage:
|
||||||
|
postgres:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 3306
|
||||||
|
database: authelia
|
||||||
|
username: authelia
|
||||||
|
|
||||||
|
notifier:
|
||||||
|
smtp:
|
||||||
|
username: test
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 1025
|
||||||
|
sender: admin@example.com
|
||||||
|
disable_require_tls: true
|
|
@ -0,0 +1,124 @@
|
||||||
|
###############################################################
|
||||||
|
# Authelia configuration #
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 9091
|
||||||
|
jwt_secret: secret_from_config
|
||||||
|
|
||||||
|
log_level: debug
|
||||||
|
default_redirection_url: https://home.example.com:8080/
|
||||||
|
|
||||||
|
totp:
|
||||||
|
issuer: authelia.com
|
||||||
|
|
||||||
|
duo_api:
|
||||||
|
hostname: api-123456789.example.com
|
||||||
|
integration_key: ABCDEF
|
||||||
|
|
||||||
|
authentication_backend:
|
||||||
|
ldap:
|
||||||
|
url: ldap://127.0.0.1
|
||||||
|
base_dn: dc=example,dc=com
|
||||||
|
username_attribute: uid
|
||||||
|
additional_users_dn: ou=users
|
||||||
|
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
||||||
|
additional_groups_dn: ou=groups
|
||||||
|
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||||
|
group_name_attribute: cn
|
||||||
|
mail_attribute: mail
|
||||||
|
user: cn=admin,dc=example,dc=com
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
default_policy: deny
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# Rules applied to everyone
|
||||||
|
- domain: public.example.com
|
||||||
|
policy: bypass
|
||||||
|
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: one_factor
|
||||||
|
# Network based rule, if not provided any network matches.
|
||||||
|
networks:
|
||||||
|
- 192.168.1.0/24
|
||||||
|
- domain: secure.example.com
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
- domain: [singlefactor.example.com, onefactor.example.com]
|
||||||
|
policy: one_factor
|
||||||
|
|
||||||
|
# Rules applied to 'admins' group
|
||||||
|
- domain: "mx2.mail.example.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: deny
|
||||||
|
- domain: "*.example.com"
|
||||||
|
subject: "group:admins"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/groups/dev/.*$"
|
||||||
|
subject: "group:dev"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to user 'john'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/john/.*$"
|
||||||
|
subject: "user:john"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group and user 'john'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/deny-all.*$"
|
||||||
|
subject: ["group:dev", "user:john"]
|
||||||
|
policy: denied
|
||||||
|
|
||||||
|
# Rules applied to user 'harry'
|
||||||
|
- domain: dev.example.com
|
||||||
|
resources:
|
||||||
|
- "^/users/harry/.*$"
|
||||||
|
subject: "user:harry"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
# Rules applied to user 'bob'
|
||||||
|
- domain: "*.mail.example.com"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
- domain: "dev.example.com"
|
||||||
|
resources:
|
||||||
|
- "^/users/bob/.*$"
|
||||||
|
subject: "user:bob"
|
||||||
|
policy: two_factor
|
||||||
|
|
||||||
|
session:
|
||||||
|
name: authelia_session
|
||||||
|
expiration: 3600000 # 1 hour
|
||||||
|
inactivity: 300000 # 5 minutes
|
||||||
|
domain: example.com
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
|
||||||
|
regulation:
|
||||||
|
max_retries: 3
|
||||||
|
find_time: 120
|
||||||
|
ban_time: 300
|
||||||
|
|
||||||
|
storage:
|
||||||
|
mysql:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 3306
|
||||||
|
database: authelia
|
||||||
|
username: authelia
|
||||||
|
|
||||||
|
notifier:
|
||||||
|
smtp:
|
||||||
|
username: test
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 1025
|
||||||
|
sender: admin@example.com
|
||||||
|
disable_require_tls: true
|
|
@ -10,8 +10,8 @@ import (
|
||||||
var defaultPort = 8080
|
var defaultPort = 8080
|
||||||
var defaultLogLevel = "info"
|
var defaultLogLevel = "info"
|
||||||
|
|
||||||
// Validate and adapt the configuration read from file.
|
// ValidateConfiguration and adapt the configuration read from file.
|
||||||
func Validate(configuration *schema.Configuration, validator *schema.StructValidator) {
|
func ValidateConfiguration(configuration *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if configuration.Host == "" {
|
if configuration.Host == "" {
|
||||||
configuration.Host = "0.0.0.0"
|
configuration.Host = "0.0.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ func TestShouldNotUpdateConfig(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, 9090, config.Port)
|
assert.Equal(t, 9090, config.Port)
|
||||||
|
@ -49,7 +49,7 @@ func TestShouldValidateAndUpdatePort(t *testing.T) {
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
config.Port = 0
|
config.Port = 0
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, 8080, config.Port)
|
assert.Equal(t, 8080, config.Port)
|
||||||
|
@ -60,7 +60,7 @@ func TestShouldValidateAndUpdateHost(t *testing.T) {
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
config.Host = ""
|
config.Host = ""
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, "0.0.0.0", config.Host)
|
assert.Equal(t, "0.0.0.0", config.Host)
|
||||||
|
@ -71,7 +71,7 @@ func TestShouldValidateAndUpdateLogsLevel(t *testing.T) {
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
config.LogLevel = ""
|
config.LogLevel = ""
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, "info", config.LogLevel)
|
assert.Equal(t, "info", config.LogLevel)
|
||||||
|
@ -81,12 +81,12 @@ func TestShouldEnsureNotifierConfigIsProvided(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
|
|
||||||
config.Notifier = nil
|
config.Notifier = nil
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "A notifier configuration must be provided")
|
assert.EqualError(t, validator.Errors()[0], "A notifier configuration must be provided")
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ func TestShouldAddDefaultAccessControl(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.NotNil(t, config.AccessControl)
|
assert.NotNil(t, config.AccessControl)
|
||||||
assert.Equal(t, "deny", config.AccessControl.DefaultPolicy)
|
assert.Equal(t, "deny", config.AccessControl.DefaultPolicy)
|
||||||
|
@ -106,7 +106,7 @@ func TestShouldRaiseErrorWhenTLSCertWithoutKeyIsProvided(t *testing.T) {
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
config.TLSCert = "/tmp/cert.pem"
|
config.TLSCert = "/tmp/cert.pem"
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "No TLS key provided, please check the \"tls_key\" which has been configured")
|
assert.EqualError(t, validator.Errors()[0], "No TLS key provided, please check the \"tls_key\" which has been configured")
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,7 @@ func TestShouldRaiseErrorWhenTLSKeyWithoutCertIsProvided(t *testing.T) {
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
config.TLSKey = "/tmp/key.pem"
|
config.TLSKey = "/tmp/key.pem"
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "No TLS certificate provided, please check the \"tls_cert\" which has been configured")
|
assert.EqualError(t, validator.Errors()[0], "No TLS certificate provided, please check the \"tls_cert\" which has been configured")
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func TestShouldNotRaiseErrorWhenBothTLSCertificateAndKeyAreProvided(t *testing.T
|
||||||
config.TLSCert = "/tmp/cert.pem"
|
config.TLSCert = "/tmp/cert.pem"
|
||||||
config.TLSKey = "/tmp/key.pem"
|
config.TLSKey = "/tmp/key.pem"
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ func TestShouldRaiseErrorWithUndefinedJWTSecretKey(t *testing.T) {
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
config.JWTSecret = ""
|
config.JWTSecret = ""
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "Provide a JWT secret using \"jwt_secret\" key")
|
assert.EqualError(t, validator.Errors()[0], "Provide a JWT secret using \"jwt_secret\" key")
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ func TestShouldRaiseErrorWithBadDefaultRedirectionURL(t *testing.T) {
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
config.DefaultRedirectionURL = "abc"
|
config.DefaultRedirectionURL = "abc"
|
||||||
|
|
||||||
Validate(&config, validator)
|
ValidateConfiguration(&config, validator)
|
||||||
require.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "Unable to parse default redirection url")
|
assert.EqualError(t, validator.Errors()[0], "Unable to parse default redirection url")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/internal/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateSecrets checks that secrets are either specified by config file/env or by file references.
|
||||||
|
func ValidateSecrets(configuration *schema.Configuration, validator *schema.StructValidator, viper *viper.Viper) {
|
||||||
|
configuration.JWTSecret = getSecretValue("jwt_secret", validator, viper)
|
||||||
|
configuration.Session.Secret = getSecretValue("session.secret", validator, viper)
|
||||||
|
|
||||||
|
if configuration.DuoAPI != nil {
|
||||||
|
configuration.DuoAPI.SecretKey = getSecretValue("duo_api.secret_key", validator, viper)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.Session.Redis != nil {
|
||||||
|
configuration.Session.Redis.Password = getSecretValue("session.redis.password", validator, viper)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.AuthenticationBackend.Ldap != nil {
|
||||||
|
configuration.AuthenticationBackend.Ldap.Password = getSecretValue("authentication_backend.ldap.password", validator, viper)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.Notifier != nil && configuration.Notifier.SMTP != nil {
|
||||||
|
configuration.Notifier.SMTP.Password = getSecretValue("notifier.smtp.password", validator, viper)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.Storage.MySQL != nil {
|
||||||
|
configuration.Storage.MySQL.Password = getSecretValue("storage.mysql.password", validator, viper)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.Storage.PostgreSQL != nil {
|
||||||
|
configuration.Storage.PostgreSQL.Password = getSecretValue("storage.postgres.password", validator, viper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecretValue(name string, validator *schema.StructValidator, viper *viper.Viper) string {
|
||||||
|
configValue := viper.GetString(name)
|
||||||
|
envValue := viper.GetString("authelia." + name)
|
||||||
|
fileEnvValue := viper.GetString("authelia." + name + ".file")
|
||||||
|
|
||||||
|
// Error Checking.
|
||||||
|
if envValue != "" && fileEnvValue != "" {
|
||||||
|
validator.Push(fmt.Errorf("secret is defined in multiple areas: %s", name))
|
||||||
|
}
|
||||||
|
if (envValue != "" || fileEnvValue != "") && configValue != "" {
|
||||||
|
validator.Push(fmt.Errorf("error loading secret (%s): it's already defined in the config file", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive Secret.
|
||||||
|
if fileEnvValue != "" {
|
||||||
|
content, err := ioutil.ReadFile(fileEnvValue)
|
||||||
|
if err != nil {
|
||||||
|
validator.Push(fmt.Errorf("error loading secret file (%s): %s", name, err))
|
||||||
|
} else {
|
||||||
|
return strings.Replace(string(content), "\n", "", -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if envValue != "" {
|
||||||
|
logging.Logger().Warnf("The following secret is defined as an environment variable, this is insecure and being removed in 4.18.0+, it's recommended to use the file secrets instead (https://docs.authelia.com/configuration/secrets.html): %s", name)
|
||||||
|
return envValue
|
||||||
|
}
|
||||||
|
return configValue
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ func validatePostgreSQLConfiguration(configuration *schema.PostgreSQLStorageConf
|
||||||
|
|
||||||
if !(configuration.SSLMode == "disable" || configuration.SSLMode == "require" ||
|
if !(configuration.SSLMode == "disable" || configuration.SSLMode == "require" ||
|
||||||
configuration.SSLMode == "verify-ca" || configuration.SSLMode == "verify-full") {
|
configuration.SSLMode == "verify-ca" || configuration.SSLMode == "verify-full") {
|
||||||
validator.Push(errors.New("SSL mode must be 'disable', 'require', 'verify-ca' or 'verify-full'"))
|
validator.Push(errors.New("SSL mode must be 'disable', 'require', 'verify-ca', or 'verify-full'"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (s *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() {
|
||||||
ValidateStorage(s.configuration, validator)
|
ValidateStorage(s.configuration, validator)
|
||||||
|
|
||||||
s.Require().Len(validator.Errors(), 1)
|
s.Require().Len(validator.Errors(), 1)
|
||||||
s.Assert().EqualError(validator.Errors()[0], "SSL mode must be 'disable', 'require', 'verify-ca' or 'verify-full'")
|
s.Assert().EqualError(validator.Errors()[0], "SSL mode must be 'disable', 'require', 'verify-ca', or 'verify-full'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRunStorageSuite(t *testing.T) {
|
func TestShouldRunStorageSuite(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue