mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
feat(session): add redis sentinel provider (#1768)
* feat(session): add redis sentinel provider * refactor(session): use int for ports as per go standards * refactor(configuration): adjust tests and validation * refactor(configuration): add err format consts * refactor(configuration): explicitly map redis structs * refactor(session): merge redis/redis sentinel providers * refactor(session): add additional checks to redis providers * feat(session): add redis cluster provider * fix: update config for new values * fix: provide nil certpool to affected tests/mocks * test: add additional tests to cover uncovered code * docs: expand explanation of host and nodes relation for redis * ci: add redis-sentinel to suite highavailability, add redis-sentinel quorum * fix(session): sentinel password * test: use redis alpine library image for redis sentinel, use expose instead of ports, use redis ip, adjust redis ip range, adjust redis config * test: make entrypoint.sh executable, fix entrypoint.sh if/elif * test: add redis failover tests * test: defer docker start, adjust sleep, attempt logout before login, attempt visit before login and tune timeouts, add additional logging * test: add sentinel integration test * test: add secondary node failure to tests, fix password usage, bump test timeout, add sleep * feat: use sentinel failover cluster * fix: renamed addrs to sentineladdrs upstream * test(session): sentinel failover * test: add redis standard back into testing * test: move redis standalone test to traefik2 * fix/docs: apply suggestions from code review
This commit is contained in:
parent
073c558296
commit
e041143f87
|
@ -22,6 +22,7 @@ type HostEntry struct {
|
|||
var hostEntries = []HostEntry{
|
||||
// For authelia backend.
|
||||
{Domain: "authelia.example.com", IP: "192.168.240.50"},
|
||||
|
||||
// For common tests.
|
||||
{Domain: "login.example.com", IP: "192.168.240.100"},
|
||||
{Domain: "admin.example.com", IP: "192.168.240.100"},
|
||||
|
@ -34,14 +35,28 @@ var hostEntries = []HostEntry{
|
|||
{Domain: "secure.example.com", IP: "192.168.240.100"},
|
||||
{Domain: "mail.example.com", IP: "192.168.240.100"},
|
||||
{Domain: "duo.example.com", IP: "192.168.240.100"},
|
||||
|
||||
// For Traefik suite.
|
||||
{Domain: "traefik.example.com", IP: "192.168.240.100"},
|
||||
|
||||
// For HAProxy suite.
|
||||
{Domain: "haproxy.example.com", IP: "192.168.240.100"},
|
||||
|
||||
// For testing network ACLs.
|
||||
{Domain: "proxy-client1.example.com", IP: "192.168.240.201"},
|
||||
{Domain: "proxy-client2.example.com", IP: "192.168.240.202"},
|
||||
{Domain: "proxy-client3.example.com", IP: "192.168.240.203"},
|
||||
|
||||
// Redis Replicas
|
||||
{Domain: "redis-node-0.example.com", IP: "192.168.240.110"},
|
||||
{Domain: "redis-node-1.example.com", IP: "192.168.240.111"},
|
||||
{Domain: "redis-node-2.example.com", IP: "192.168.240.112"},
|
||||
|
||||
// Redis Sentinel Replicas
|
||||
{Domain: "redis-sentinel-0.example.com", IP: "192.168.240.120"},
|
||||
{Domain: "redis-sentinel-1.example.com", IP: "192.168.240.121"},
|
||||
{Domain: "redis-sentinel-2.example.com", IP: "192.168.240.122"},
|
||||
|
||||
// Kubernetes dashboard.
|
||||
{Domain: "kubernetes.example.com", IP: "192.168.240.110"},
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ func startServer() {
|
|||
|
||||
clock := utils.RealClock{}
|
||||
authorizer := authorization.NewAuthorizer(config.AccessControl)
|
||||
sessionProvider := session.NewProvider(config.Session)
|
||||
sessionProvider := session.NewProvider(config.Session, autheliaCertPool)
|
||||
regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)
|
||||
|
||||
providers := middlewares.Providers{
|
||||
|
|
|
@ -8,6 +8,11 @@ port: 9091
|
|||
# tls_key: /config/ssl/key.pem
|
||||
# tls_cert: /config/ssl/cert.pem
|
||||
|
||||
## Certificates directory specifies where Authelia will load trusted certificates (public portion) from in addition to
|
||||
## the system certificates store.
|
||||
## They should be in base64 format, and have one of the following extensions: *.cer, *.crt, *.pem.
|
||||
# certificates_directory: /config/certificates
|
||||
|
||||
# The theme to display: light, dark, grey
|
||||
theme: light
|
||||
|
||||
|
@ -109,7 +114,7 @@ authentication_backend:
|
|||
|
||||
# The url to the ldap server. Scheme can be ldap or ldaps in the format (port optional) <scheme>://<address>[:<port>].
|
||||
url: ldap://127.0.0.1
|
||||
|
||||
|
||||
# Use StartTLS with the LDAP connection.
|
||||
start_tls: false
|
||||
|
||||
|
@ -118,6 +123,8 @@ authentication_backend:
|
|||
# server_name: ldap.example.com
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## In preference to setting this we strongly recommend you add the public portion of the certificate to the
|
||||
## certificates directory which is defined by the `certificates_directory` option at the top of the config.
|
||||
skip_verify: false
|
||||
|
||||
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
||||
|
@ -125,7 +132,7 @@ authentication_backend:
|
|||
|
||||
# The base dn for every entries.
|
||||
base_dn: dc=example,dc=com
|
||||
|
||||
|
||||
# The attribute holding the username of the user. This attribute is used to populate
|
||||
# the username in the session information. It was introduced due to #561 to handle case
|
||||
# insensitive search queries.
|
||||
|
@ -138,13 +145,13 @@ authentication_backend:
|
|||
# them, we instead advise to use the attributes mentioned above (sAMAccountName and uid) to follow
|
||||
# https://www.ietf.org/rfc/rfc2307.txt.
|
||||
# username_attribute: uid
|
||||
|
||||
|
||||
# An additional dn to define the scope to all users.
|
||||
additional_users_dn: ou=users
|
||||
|
||||
# The users filter used in search queries to find the user profile based on input filled in login form.
|
||||
# Various placeholders are available to represent the user input and back reference other options of the configuration:
|
||||
# - {input} is a placeholder replaced by what the user inputs in the login form.
|
||||
# - {input} is a placeholder replaced by what the user inputs in the login form.
|
||||
# - {username_attribute} is a mandatory placeholder replaced by what is configured in `username_attribute`.
|
||||
# - {mail_attribute} is a placeholder replaced by what is configured in `mail_attribute`.
|
||||
# - DON'T USE - {0} is an alias for {input} supported for backward compatibility but it will be deprecated in later versions, so please don't use it.
|
||||
|
@ -159,7 +166,7 @@ authentication_backend:
|
|||
|
||||
# An additional dn to define the scope of groups.
|
||||
additional_groups_dn: ou=groups
|
||||
|
||||
|
||||
# The groups filter used in search queries to find the groups of the user.
|
||||
# - {input} is a placeholder replaced by what the user inputs in the login form.
|
||||
# - {username} is a placeholder replace by the username stored in LDAP (based on `username_attribute`).
|
||||
|
@ -270,8 +277,8 @@ access_control:
|
|||
- 10.0.0.1
|
||||
|
||||
- domain:
|
||||
- secure.example.com
|
||||
- private.example.com
|
||||
- secure.example.com
|
||||
- private.example.com
|
||||
policy: two_factor
|
||||
|
||||
- domain: singlefactor.example.com
|
||||
|
@ -326,7 +333,7 @@ session:
|
|||
# The name of the session cookie. (default: 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 / Redis Sentinel.
|
||||
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
secret: insecure_session_secret
|
||||
|
||||
|
@ -348,19 +355,65 @@ session:
|
|||
# is restricted to the subdomain of the issuer.
|
||||
domain: example.com
|
||||
|
||||
# The redis connection details
|
||||
## The redis connection details
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
# Use a unix socket instead
|
||||
## Use a unix socket instead
|
||||
# host: /var/run/redis/redis.sock
|
||||
|
||||
## Optional username to be used with authentication.
|
||||
username: authelia
|
||||
|
||||
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
## Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
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
|
||||
|
||||
## The maximum number of concurrent active connections to Redis.
|
||||
maximum_active_connections: 8
|
||||
|
||||
## The target number of idle connections to have open ready for work. Useful when opening connections is slow.
|
||||
minimum_idle_connections: 0
|
||||
|
||||
## The Redis TLS configuration. If defined will require a TLS connection to the Redis instance(s).
|
||||
# tls:
|
||||
## Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||
# server_name: myredis.example.com
|
||||
|
||||
## Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## In preference to setting this we strongly recommend you add the public portion of the certificate to the
|
||||
## certificates directory which is defined by the `certificates_directory` option at the top of the config.
|
||||
# skip_verify: false
|
||||
|
||||
## Minimum TLS version for the connection.
|
||||
# minimum_version: TLS1.2
|
||||
|
||||
## The Redis HA configuration options.
|
||||
## This provides specific options to Redis Sentinel, sentinel_name must be defined (Master Name).
|
||||
# high_availability:
|
||||
## Sentinel Name / Master Name
|
||||
# sentinel_name: mysentinel
|
||||
|
||||
## Specific password for Redis Sentinel. The node username and password is configured above.
|
||||
# sentinel_password: sentinel_specific_pass
|
||||
|
||||
## The additional nodes to pre-seed the redis provider with (for sentinel).
|
||||
## If the host in the above section is defined, it will be combined with this list to connect to sentinel.
|
||||
## For high availability to be used you must have either defined; the host above or at least one node below.
|
||||
# nodes:
|
||||
# - host: sentinel-node1
|
||||
# port: 6379
|
||||
# - host: sentinel-node2
|
||||
# port: 6379
|
||||
|
||||
## Choose the host with the lowest latency.
|
||||
# route_by_latency: false
|
||||
|
||||
## Choose the host randomly.
|
||||
# route_randomly: false
|
||||
|
||||
# Configuration of the authentication regulation mechanism.
|
||||
#
|
||||
# This mechanism prevents attackers from brute forcing the first factor.
|
||||
|
@ -446,7 +499,9 @@ notifier:
|
|||
# Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||
# server_name: smtp.example.com
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## In preference to setting this we strongly recommend you add the public portion of the certificate to the
|
||||
## certificates directory which is defined by the `certificates_directory` option at the top of the config.
|
||||
skip_verify: false
|
||||
|
||||
# Minimum TLS version for either StartTLS or SMTPS.
|
||||
|
|
|
@ -48,11 +48,60 @@ session:
|
|||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
# Use a unix socket instead
|
||||
## Use a unix socket instead
|
||||
# host: /var/run/redis/redis.sock
|
||||
|
||||
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
## Optional username to be used with authentication.
|
||||
username: authelia
|
||||
|
||||
## Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
password: authelia
|
||||
|
||||
## This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
|
||||
database_index: 0
|
||||
|
||||
## The maximum number of concurrent active connections to Redis.
|
||||
maximum_active_connections: 8
|
||||
|
||||
## The target number of idle connections to have open ready for work. Useful when opening connections is slow.
|
||||
minimum_idle_connections: 0
|
||||
|
||||
## The Redis TLS configuration. If defined will require a TLS connection to the Redis instance(s).
|
||||
tls:
|
||||
## Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||
server_name: myredis.example.com
|
||||
|
||||
## Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## In preference to setting this we strongly recommend you add the public portion of the certificate to the
|
||||
## certificates directory which is defined by the `certificates_directory` option at the top of the config.
|
||||
skip_verify: false
|
||||
|
||||
## Minimum TLS version for the connection.
|
||||
minimum_version: TLS1.2
|
||||
|
||||
## The Redis HA configuration options.
|
||||
## This provides specific options to Redis Sentinel, sentinel_name must be defined (Master Name).
|
||||
high_availability:
|
||||
## Sentinel Name / Master Name
|
||||
sentinel_name: mysentinel
|
||||
|
||||
## Specific password for Redis Sentinel. The node username and password is configured above.
|
||||
sentinel_password: sentinel_specific_pass
|
||||
|
||||
## The additional nodes to pre-seed the redis provider with (for sentinel).
|
||||
## If the host in the above section is defined, it will be combined with this list to connect to sentinel.
|
||||
## For high availability to be used you must have either defined; the host above or at least one node below.
|
||||
nodes:
|
||||
- host: sentinel-node1
|
||||
port: 6379
|
||||
- host: sentinel-node2
|
||||
port: 6379
|
||||
|
||||
## Choose the host with the lowest latency.
|
||||
route_by_latency: false
|
||||
|
||||
## Choose the host randomly.
|
||||
route_randomly: false
|
||||
```
|
||||
|
||||
### Security
|
||||
|
@ -74,4 +123,9 @@ host: "[fd00:1111:2222:3333::1]"
|
|||
|
||||
## Loading a password from a secret instead of inside the configuration
|
||||
|
||||
Password can also be defined using a [secret](../secrets.md).
|
||||
Password can also be defined using a [secret](../secrets.md).
|
||||
|
||||
## Redis Sentinel
|
||||
|
||||
When using Redis Sentinel, the host specified in the main redis section is added (it will be the first node) to the
|
||||
nodes in the high availability section. This however is optional.
|
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
|||
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
|
||||
github.com/Workiva/go-datastructures v1.0.52
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/authelia/session/v2 v2.4.1
|
||||
github.com/authelia/session/v2 v2.5.7
|
||||
github.com/deckarep/golang-set v1.7.1
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/duosecurity/duo_api_golang v0.0.0-20201112143038-0e07e9f869e3
|
||||
|
|
12
go.sum
12
go.sum
|
@ -39,8 +39,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
|||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/authelia/session/v2 v2.4.1 h1:/c/imfsdr380VfZ3Do1nFma+wvfZlrYQuR299UEyZBs=
|
||||
github.com/authelia/session/v2 v2.4.1/go.mod h1:YzuxG4Aj5aFVxO49g9Upvye4BNaTNFjg6rIEgZJ3BQI=
|
||||
github.com/authelia/session/v2 v2.5.7 h1:cdF7cod8Lgw7KavtyQstP511Sov10FafIuGnx+w3a/M=
|
||||
github.com/authelia/session/v2 v2.5.7/go.mod h1:0bZpmr+V7hL2DVPyutiC+1lcNjdYVVmx3vbZUdigD6c=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
|
@ -323,9 +323,9 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC
|
|||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/savsgio/dictpool v0.0.0-20210105101557-9da1bc2fbfce h1:PRDREQ3VGiocUySEdKYQpwdyxDx+e4uKdjVaQvwIR5I=
|
||||
github.com/savsgio/dictpool v0.0.0-20210105101557-9da1bc2fbfce/go.mod h1:TNr2IIMnYd9/KYEpTVHVrnfmjizlKPTSgkWUbjyof+A=
|
||||
github.com/savsgio/gotils v0.0.0-20210105085219-0567298fdcac/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8=
|
||||
github.com/savsgio/dictpool v0.0.0-20210217113430-85d3b37fb239 h1:aTxmMsYGLUZfj0EsWaJ1s0HnctxCgjRw3A+TFoO1Tsc=
|
||||
github.com/savsgio/dictpool v0.0.0-20210217113430-85d3b37fb239/go.mod h1:CfPSewBwpXF/05Izyk9s379O1ysmtUajFVr1nOD83Fs=
|
||||
github.com/savsgio/gotils v0.0.0-20210217112953-d4a072536008/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8=
|
||||
github.com/savsgio/gotils v0.0.0-20210225112730-595c7e5a8a7a h1:9AQ3IfP72fCdbYAJNNwovzXrarhaWtxosEuN1fpent0=
|
||||
github.com/savsgio/gotils v0.0.0-20210225112730-595c7e5a8a7a/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
|
@ -383,7 +383,7 @@ github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGB
|
|||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.19.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A=
|
||||
github.com/valyala/fasthttp v1.21.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A=
|
||||
github.com/valyala/fasthttp v1.22.0 h1:OpwH5KDOJ9cS2bq8fD+KfT4IrksK0llvkHf4MZx42jQ=
|
||||
github.com/valyala/fasthttp v1.22.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
|
|
|
@ -8,6 +8,11 @@ port: 9091
|
|||
# tls_key: /config/ssl/key.pem
|
||||
# tls_cert: /config/ssl/cert.pem
|
||||
|
||||
## Certificates directory specifies where Authelia will load trusted certificates (public portion) from in addition to
|
||||
## the system certificates store.
|
||||
## They should be in base64 format, and have one of the following extensions: *.cer, *.crt, *.pem.
|
||||
# certificates_directory: /config/certificates
|
||||
|
||||
# The theme to display: light, dark, grey
|
||||
theme: light
|
||||
|
||||
|
@ -109,7 +114,7 @@ authentication_backend:
|
|||
|
||||
# The url to the ldap server. Scheme can be ldap or ldaps in the format (port optional) <scheme>://<address>[:<port>].
|
||||
url: ldap://127.0.0.1
|
||||
|
||||
|
||||
# Use StartTLS with the LDAP connection.
|
||||
start_tls: false
|
||||
|
||||
|
@ -118,6 +123,8 @@ authentication_backend:
|
|||
# server_name: ldap.example.com
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## In preference to setting this we strongly recommend you add the public portion of the certificate to the
|
||||
## certificates directory which is defined by the `certificates_directory` option at the top of the config.
|
||||
skip_verify: false
|
||||
|
||||
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
||||
|
@ -125,7 +132,7 @@ authentication_backend:
|
|||
|
||||
# The base dn for every entries.
|
||||
base_dn: dc=example,dc=com
|
||||
|
||||
|
||||
# The attribute holding the username of the user. This attribute is used to populate
|
||||
# the username in the session information. It was introduced due to #561 to handle case
|
||||
# insensitive search queries.
|
||||
|
@ -138,13 +145,13 @@ authentication_backend:
|
|||
# them, we instead advise to use the attributes mentioned above (sAMAccountName and uid) to follow
|
||||
# https://www.ietf.org/rfc/rfc2307.txt.
|
||||
# username_attribute: uid
|
||||
|
||||
|
||||
# An additional dn to define the scope to all users.
|
||||
additional_users_dn: ou=users
|
||||
|
||||
# The users filter used in search queries to find the user profile based on input filled in login form.
|
||||
# Various placeholders are available to represent the user input and back reference other options of the configuration:
|
||||
# - {input} is a placeholder replaced by what the user inputs in the login form.
|
||||
# - {input} is a placeholder replaced by what the user inputs in the login form.
|
||||
# - {username_attribute} is a mandatory placeholder replaced by what is configured in `username_attribute`.
|
||||
# - {mail_attribute} is a placeholder replaced by what is configured in `mail_attribute`.
|
||||
# - DON'T USE - {0} is an alias for {input} supported for backward compatibility but it will be deprecated in later versions, so please don't use it.
|
||||
|
@ -159,7 +166,7 @@ authentication_backend:
|
|||
|
||||
# An additional dn to define the scope of groups.
|
||||
additional_groups_dn: ou=groups
|
||||
|
||||
|
||||
# The groups filter used in search queries to find the groups of the user.
|
||||
# - {input} is a placeholder replaced by what the user inputs in the login form.
|
||||
# - {username} is a placeholder replace by the username stored in LDAP (based on `username_attribute`).
|
||||
|
@ -270,8 +277,8 @@ access_control:
|
|||
- 10.0.0.1
|
||||
|
||||
- domain:
|
||||
- secure.example.com
|
||||
- private.example.com
|
||||
- secure.example.com
|
||||
- private.example.com
|
||||
policy: two_factor
|
||||
|
||||
- domain: singlefactor.example.com
|
||||
|
@ -326,7 +333,7 @@ session:
|
|||
# The name of the session cookie. (default: 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 / Redis Sentinel.
|
||||
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
secret: insecure_session_secret
|
||||
|
||||
|
@ -348,19 +355,65 @@ session:
|
|||
# is restricted to the subdomain of the issuer.
|
||||
domain: example.com
|
||||
|
||||
# The redis connection details
|
||||
## The redis connection details
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
# Use a unix socket instead
|
||||
## Use a unix socket instead
|
||||
# host: /var/run/redis/redis.sock
|
||||
|
||||
## Optional username to be used with authentication.
|
||||
username: authelia
|
||||
|
||||
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
## Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||
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
|
||||
|
||||
## The maximum number of concurrent active connections to Redis.
|
||||
maximum_active_connections: 8
|
||||
|
||||
## The target number of idle connections to have open ready for work. Useful when opening connections is slow.
|
||||
minimum_idle_connections: 0
|
||||
|
||||
## The Redis TLS configuration. If defined will require a TLS connection to the Redis instance(s).
|
||||
# tls:
|
||||
## Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||
# server_name: myredis.example.com
|
||||
|
||||
## Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## In preference to setting this we strongly recommend you add the public portion of the certificate to the
|
||||
## certificates directory which is defined by the `certificates_directory` option at the top of the config.
|
||||
# skip_verify: false
|
||||
|
||||
## Minimum TLS version for the connection.
|
||||
# minimum_version: TLS1.2
|
||||
|
||||
## The Redis HA configuration options.
|
||||
## This provides specific options to Redis Sentinel, sentinel_name must be defined (Master Name).
|
||||
# high_availability:
|
||||
## Sentinel Name / Master Name
|
||||
# sentinel_name: mysentinel
|
||||
|
||||
## Specific password for Redis Sentinel. The node username and password is configured above.
|
||||
# sentinel_password: sentinel_specific_pass
|
||||
|
||||
## The additional nodes to pre-seed the redis provider with (for sentinel).
|
||||
## If the host in the above section is defined, it will be combined with this list to connect to sentinel.
|
||||
## For high availability to be used you must have either defined; the host above or at least one node below.
|
||||
# nodes:
|
||||
# - host: sentinel-node1
|
||||
# port: 6379
|
||||
# - host: sentinel-node2
|
||||
# port: 6379
|
||||
|
||||
## Choose the host with the lowest latency.
|
||||
# route_by_latency: false
|
||||
|
||||
## Choose the host randomly.
|
||||
# route_randomly: false
|
||||
|
||||
# Configuration of the authentication regulation mechanism.
|
||||
#
|
||||
# This mechanism prevents attackers from brute forcing the first factor.
|
||||
|
@ -446,7 +499,9 @@ notifier:
|
|||
# Server Name for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||
# server_name: smtp.example.com
|
||||
|
||||
# Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## Skip verifying the server certificate (to allow a self-signed certificate).
|
||||
## In preference to setting this we strongly recommend you add the public portion of the certificate to the
|
||||
## certificates directory which is defined by the `certificates_directory` option at the top of the config.
|
||||
skip_verify: false
|
||||
|
||||
# Minimum TLS version for either StartTLS or SMTPS.
|
||||
|
|
|
@ -1,11 +1,31 @@
|
|||
package schema
|
||||
|
||||
// RedisNode Represents a Node.
|
||||
type RedisNode struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
// RedisHighAvailabilityConfiguration holds configuration variables for Redis Cluster/Sentinel.
|
||||
type RedisHighAvailabilityConfiguration struct {
|
||||
SentinelName string `mapstructure:"sentinel_name"`
|
||||
SentinelPassword string `mapstructure:"sentinel_password"`
|
||||
Nodes []RedisNode `mapstructure:"nodes"`
|
||||
RouteByLatency bool `mapstructure:"route_by_latency"`
|
||||
RouteRandomly bool `mapstructure:"route_randomly"`
|
||||
}
|
||||
|
||||
// RedisSessionConfiguration represents the configuration related to redis session store.
|
||||
type RedisSessionConfiguration struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int64 `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DatabaseIndex int `mapstructure:"database_index"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
DatabaseIndex int `mapstructure:"database_index"`
|
||||
MaximumActiveConnections int `mapstructure:"maximum_active_connections"`
|
||||
MinimumIdleConnections int `mapstructure:"minimum_idle_connections"`
|
||||
TLS *TLSConfig `mapstructure:"tls"`
|
||||
HighAvailability *RedisHighAvailabilityConfiguration `mapstructure:"high_availability"`
|
||||
}
|
||||
|
||||
// SessionConfiguration represents the configuration related to user sessions.
|
||||
|
|
|
@ -42,8 +42,24 @@ var validKeys = []string{
|
|||
// Redis Session Keys.
|
||||
"session.redis.host",
|
||||
"session.redis.port",
|
||||
"session.redis.username",
|
||||
"session.redis.password",
|
||||
"session.redis.database_index",
|
||||
"session.redis.maximum_active_connections",
|
||||
"session.redis.minimum_idle_connections",
|
||||
"session.redis.tls.minimum_version",
|
||||
"session.redis.tls.skip_verify",
|
||||
"session.redis.tls.server_name",
|
||||
"session.redis.high_availability.sentinel_name",
|
||||
"session.redis.high_availability.sentinel_password",
|
||||
"session.redis.high_availability.nodes",
|
||||
"session.redis.high_availability.route_by_latency",
|
||||
"session.redis.high_availability.route_randomly",
|
||||
"session.redis.timeouts.dial",
|
||||
"session.redis.timeouts.idle",
|
||||
"session.redis.timeouts.pool",
|
||||
"session.redis.timeouts.read",
|
||||
"session.redis.timeouts.write",
|
||||
|
||||
// Local Storage Keys.
|
||||
"storage.local.path",
|
||||
|
@ -171,6 +187,11 @@ var specificErrorKeys = map[string]string{
|
|||
"authentication_backend.file.hashing.parallelism": "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password",
|
||||
}
|
||||
|
||||
const errFmtSessionSecretRedisProvider = "The session secret must be set when using the %s session provider"
|
||||
const errFmtSessionRedisPortRange = "The port must be between 1 and 65535 for the %s session provider"
|
||||
const errFmtSessionRedisHostRequired = "The host must be provided when using the %s session provider"
|
||||
const errFmtSessionRedisHostOrNodesRequired = "Either the host or a node must be provided when using the %s session provider"
|
||||
|
||||
const denyPolicy = "deny"
|
||||
const bypassPolicy = "bypass"
|
||||
|
||||
|
|
|
@ -16,15 +16,21 @@ func ValidateSession(configuration *schema.SessionConfiguration, validator *sche
|
|||
}
|
||||
|
||||
if configuration.Redis != nil {
|
||||
if configuration.Secret == "" {
|
||||
validator.Push(errors.New("Set secret of the session object"))
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(configuration.Redis.Host, "/") && configuration.Redis.Port == 0 {
|
||||
validator.Push(errors.New("A redis port different than 0 must be provided"))
|
||||
if configuration.Redis.HighAvailability != nil {
|
||||
if configuration.Redis.HighAvailability.SentinelName != "" {
|
||||
validateRedisSentinel(configuration, validator)
|
||||
} else {
|
||||
validator.Push(fmt.Errorf("Session provider redis is configured for high availability but doesn't have a sentinel_name which is required"))
|
||||
}
|
||||
} else {
|
||||
validateRedis(configuration, validator)
|
||||
}
|
||||
}
|
||||
|
||||
validateSession(configuration, validator)
|
||||
}
|
||||
|
||||
func validateSession(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||
if configuration.Expiration == "" {
|
||||
configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour
|
||||
} else if _, err := utils.ParseDurationString(configuration.Expiration); err != nil {
|
||||
|
@ -51,3 +57,56 @@ func ValidateSession(configuration *schema.SessionConfiguration, validator *sche
|
|||
validator.Push(errors.New("The domain of the session must be the root domain you're protecting instead of a wildcard domain"))
|
||||
}
|
||||
}
|
||||
|
||||
func validateRedis(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||
if configuration.Redis.Host == "" {
|
||||
validator.Push(fmt.Errorf(errFmtSessionRedisHostRequired, "redis"))
|
||||
}
|
||||
|
||||
if configuration.Secret == "" {
|
||||
validator.Push(fmt.Errorf(errFmtSessionSecretRedisProvider, "redis"))
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(configuration.Redis.Host, "/") && configuration.Redis.Port == 0 {
|
||||
validator.Push(errors.New("A redis port different than 0 must be provided"))
|
||||
} else if configuration.Redis.Port < 0 || configuration.Redis.Port > 65535 {
|
||||
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, "redis"))
|
||||
}
|
||||
|
||||
if configuration.Redis.MaximumActiveConnections <= 0 {
|
||||
configuration.Redis.MaximumActiveConnections = 8
|
||||
}
|
||||
}
|
||||
|
||||
func validateRedisSentinel(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||
if configuration.Redis.Port == 0 {
|
||||
configuration.Redis.Port = 26379
|
||||
} else if configuration.Redis.Port < 0 || configuration.Redis.Port > 65535 {
|
||||
validator.Push(fmt.Errorf(errFmtSessionRedisPortRange, "redis sentinel"))
|
||||
}
|
||||
|
||||
validateHighAvailability(configuration, validator, "redis sentinel")
|
||||
}
|
||||
|
||||
func validateHighAvailability(configuration *schema.SessionConfiguration, validator *schema.StructValidator, provider string) {
|
||||
if configuration.Redis.Host == "" && len(configuration.Redis.HighAvailability.Nodes) == 0 {
|
||||
validator.Push(fmt.Errorf(errFmtSessionRedisHostOrNodesRequired, provider))
|
||||
}
|
||||
|
||||
if configuration.Secret == "" {
|
||||
validator.Push(fmt.Errorf(errFmtSessionSecretRedisProvider, provider))
|
||||
}
|
||||
|
||||
for i, node := range configuration.Redis.HighAvailability.Nodes {
|
||||
if node.Host == "" {
|
||||
validator.Push(fmt.Errorf("The %s nodes require a host set but you have not set the host for one or more nodes", provider))
|
||||
break
|
||||
}
|
||||
|
||||
if node.Port == 0 {
|
||||
if provider == "redis sentinel" {
|
||||
configuration.Redis.HighAvailability.Nodes[i].Port = 26379
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
)
|
||||
|
@ -22,7 +24,8 @@ func TestShouldSetDefaultSessionName(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
assert.Equal(t, schema.DefaultSessionConfiguration.Name, config.Name)
|
||||
}
|
||||
|
||||
|
@ -32,7 +35,8 @@ func TestShouldSetDefaultSessionInactivity(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
||||
}
|
||||
|
||||
|
@ -42,7 +46,8 @@ func TestShouldSetDefaultSessionExpiration(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
|
||||
}
|
||||
|
||||
|
@ -64,10 +69,47 @@ func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
|
||||
assert.Equal(t, 8, config.Redis.MaximumActiveConnections)
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorWhenRedisIsUsedAndPasswordNotSet(t *testing.T) {
|
||||
func TestShouldRaiseErrorWithInvalidRedisPortLow(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "authelia-port-1",
|
||||
Port: -1,
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, validator.Errors(), 1)
|
||||
|
||||
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis"))
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorWithInvalidRedisPortHigh(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "authelia-port-1",
|
||||
Port: 65536,
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, validator.Errors(), 1)
|
||||
|
||||
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis"))
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorWhenRedisIsUsedAndSecretNotSet(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
config.Secret = ""
|
||||
|
@ -85,8 +127,9 @@ func TestShouldRaiseErrorWhenRedisIsUsedAndPasswordNotSet(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "Set secret of the session object")
|
||||
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionSecretRedisProvider, "redis"))
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
|
||||
|
@ -106,10 +149,214 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "A redis port different than 0 must be provided")
|
||||
}
|
||||
|
||||
func TestShouldRaiseOneErrorWhenRedisHighAvailabilityHasNodesWithNoHost(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "redis",
|
||||
Port: 6379,
|
||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||
SentinelName: "authelia-sentinel",
|
||||
SentinelPassword: "abc123",
|
||||
Nodes: []schema.RedisNode{
|
||||
{
|
||||
Port: 26379,
|
||||
},
|
||||
{
|
||||
Port: 26379,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, errors, 1)
|
||||
|
||||
assert.EqualError(t, errors[0], "The redis sentinel nodes require a host set but you have not set the host for one or more nodes")
|
||||
}
|
||||
|
||||
func TestShouldRaiseOneErrorWhenRedisHighAvailabilityDoesNotHaveSentinelName(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "redis",
|
||||
Port: 6379,
|
||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||
SentinelPassword: "abc123",
|
||||
},
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, errors, 1)
|
||||
|
||||
assert.EqualError(t, errors[0], "Session provider redis is configured for high availability but doesn't have a sentinel_name which is required")
|
||||
}
|
||||
|
||||
func TestShouldUpdateDefaultPortWhenRedisSentinelHasNodes(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "redis",
|
||||
Port: 6379,
|
||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||
SentinelName: "authelia-sentinel",
|
||||
SentinelPassword: "abc123",
|
||||
Nodes: []schema.RedisNode{
|
||||
{
|
||||
Host: "node-1",
|
||||
Port: 333,
|
||||
},
|
||||
{
|
||||
Host: "node-2",
|
||||
},
|
||||
{
|
||||
Host: "node-3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
|
||||
assert.Equal(t, 333, config.Redis.HighAvailability.Nodes[0].Port)
|
||||
assert.Equal(t, 26379, config.Redis.HighAvailability.Nodes[1].Port)
|
||||
assert.Equal(t, 26379, config.Redis.HighAvailability.Nodes[2].Port)
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorsWhenRedisSentinelOptionsIncorrectlyConfigured(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Secret = ""
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Port: 65536,
|
||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||
SentinelName: "sentinel",
|
||||
SentinelPassword: "abc123",
|
||||
Nodes: []schema.RedisNode{
|
||||
{
|
||||
Host: "node1",
|
||||
Port: 26379,
|
||||
},
|
||||
},
|
||||
RouteByLatency: true,
|
||||
RouteRandomly: true,
|
||||
},
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, errors, 2)
|
||||
|
||||
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis sentinel"))
|
||||
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRedisProvider, "redis sentinel"))
|
||||
|
||||
validator.Clear()
|
||||
|
||||
config.Redis.Port = -1
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
errors = validator.Errors()
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, errors, 2)
|
||||
|
||||
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisPortRange, "redis sentinel"))
|
||||
assert.EqualError(t, errors[1], fmt.Sprintf(errFmtSessionSecretRedisProvider, "redis sentinel"))
|
||||
}
|
||||
|
||||
func TestShouldNotRaiseErrorsAndSetDefaultPortWhenRedisSentinelPortBlank(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "mysentinelHost",
|
||||
Port: 0,
|
||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||
SentinelName: "sentinel",
|
||||
SentinelPassword: "abc123",
|
||||
Nodes: []schema.RedisNode{
|
||||
{
|
||||
Host: "node1",
|
||||
Port: 26379,
|
||||
},
|
||||
},
|
||||
RouteByLatency: true,
|
||||
RouteRandomly: true,
|
||||
},
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
|
||||
assert.Equal(t, 26379, config.Redis.Port)
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorWhenRedisHostAndHighAvailabilityNodesEmpty(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Port: 26379,
|
||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||
SentinelName: "sentinel",
|
||||
SentinelPassword: "abc123",
|
||||
RouteByLatency: true,
|
||||
RouteRandomly: true,
|
||||
},
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, validator.Errors(), 1)
|
||||
|
||||
assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtSessionRedisHostOrNodesRequired, "redis sentinel"))
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorsWhenRedisHostNotSet(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
||||
config.Redis = &schema.RedisSessionConfiguration{
|
||||
Port: 6379,
|
||||
}
|
||||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
errors := validator.Errors()
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
require.Len(t, errors, 1)
|
||||
|
||||
assert.EqualError(t, errors[0], fmt.Sprintf(errFmtSessionRedisHostRequired, "redis"))
|
||||
}
|
||||
|
||||
func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
|
||||
validator := schema.NewStructValidator()
|
||||
config := newDefaultSessionConfig()
|
||||
|
@ -117,6 +364,7 @@ func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "Set domain of the session object")
|
||||
}
|
||||
|
@ -128,6 +376,7 @@ func TestShouldRaiseErrorWhenDomainIsWildcard(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "The domain of the session must be the root domain you're protecting instead of a wildcard domain")
|
||||
}
|
||||
|
@ -140,6 +389,7 @@ func TestShouldRaiseErrorWhenBadInactivityAndExpirationSet(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.Len(t, validator.Errors(), 2)
|
||||
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session expiration string: Could not convert the input string of -1 into a duration")
|
||||
assert.EqualError(t, validator.Errors()[1], "Error occurred parsing session inactivity string: Could not convert the input string of -1 into a duration")
|
||||
|
@ -152,6 +402,7 @@ func TestShouldRaiseErrorWhenBadRememberMeDurationSet(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.Len(t, validator.Errors(), 1)
|
||||
assert.EqualError(t, validator.Errors()[0], "Error occurred parsing session remember_me_duration string: Could not convert the input string of 1 year into a duration")
|
||||
}
|
||||
|
@ -162,6 +413,7 @@ func TestShouldSetDefaultRememberMeDuration(t *testing.T) {
|
|||
|
||||
ValidateSession(&config, validator)
|
||||
|
||||
assert.Len(t, validator.Errors(), 0)
|
||||
assert.False(t, validator.HasWarnings())
|
||||
assert.False(t, validator.HasErrors())
|
||||
assert.Equal(t, config.RememberMeDuration, schema.DefaultSessionConfiguration.RememberMeDuration)
|
||||
}
|
||||
|
|
|
@ -617,7 +617,7 @@ func TestShouldDestroySessionWhenInactiveForTooLong(t *testing.T) {
|
|||
|
||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
||||
// Reload the session provider since the configuration is indirect.
|
||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session)
|
||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
||||
|
||||
userSession := mock.Ctx.GetSession()
|
||||
|
@ -650,7 +650,7 @@ func TestShouldDestroySessionWhenInactiveForTooLongUsingDurationNotation(t *test
|
|||
|
||||
mock.Ctx.Configuration.Session.Inactivity = "10s"
|
||||
// Reload the session provider since the configuration is indirect.
|
||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session)
|
||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
||||
|
||||
userSession := mock.Ctx.GetSession()
|
||||
|
@ -747,7 +747,7 @@ func TestShouldRedirectWhenSessionInactiveForTooLongAndRDParamProvided(t *testin
|
|||
|
||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
||||
// Reload the session provider since the configuration is indirect.
|
||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session)
|
||||
mock.Ctx.Providers.SessionProvider = session.NewProvider(mock.Ctx.Configuration.Session, nil)
|
||||
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
||||
|
||||
past := clock.Now().Add(-1 * time.Hour)
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestShouldCallNextWithAutheliaCtx(t *testing.T) {
|
|||
ctx := &fasthttp.RequestCtx{}
|
||||
configuration := schema.Configuration{}
|
||||
userProvider := mocks.NewMockUserProvider(ctrl)
|
||||
sessionProvider := session.NewProvider(configuration.Session)
|
||||
sessionProvider := session.NewProvider(configuration.Session, nil)
|
||||
providers := middlewares.Providers{
|
||||
UserProvider: userProvider,
|
||||
SessionProvider: sessionProvider,
|
||||
|
|
|
@ -108,7 +108,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
|||
configuration.AccessControl)
|
||||
|
||||
providers.SessionProvider = session.NewProvider(
|
||||
configuration.Session)
|
||||
configuration.Session, nil)
|
||||
|
||||
providers.Regulator = regulation.NewRegulator(configuration.Regulation, providers.StorageProvider, &mockAuthelia.Clock)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/internal/logging"
|
||||
"github.com/authelia/authelia/internal/utils"
|
||||
)
|
||||
|
||||
|
@ -21,42 +23,51 @@ type Provider struct {
|
|||
}
|
||||
|
||||
// NewProvider instantiate a session provider given a configuration.
|
||||
func NewProvider(configuration schema.SessionConfiguration) *Provider {
|
||||
providerConfig := NewProviderConfig(configuration)
|
||||
func NewProvider(configuration schema.SessionConfiguration, certPool *x509.CertPool) *Provider {
|
||||
providerConfig := NewProviderConfig(configuration, certPool)
|
||||
|
||||
provider := new(Provider)
|
||||
provider.sessionHolder = fasthttpsession.New(providerConfig.config)
|
||||
|
||||
logger := logging.Logger()
|
||||
|
||||
duration, err := utils.ParseDurationString(configuration.RememberMeDuration)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
provider.RememberMe = duration
|
||||
|
||||
duration, err = utils.ParseDurationString(configuration.Inactivity)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
provider.Inactivity = duration
|
||||
|
||||
var providerImpl fasthttpsession.Provider
|
||||
if providerConfig.redisConfig != nil {
|
||||
|
||||
switch {
|
||||
case providerConfig.redisConfig != nil:
|
||||
providerImpl, err = redis.New(*providerConfig.redisConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
case providerConfig.redisSentinelConfig != nil:
|
||||
providerImpl, err = redis.NewFailoverCluster(*providerConfig.redisSentinelConfig)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
default:
|
||||
providerImpl, err = memory.New(memory.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = provider.sessionHolder.SetProvider(providerImpl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
return provider
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/authelia/session/v2"
|
||||
"github.com/authelia/session/v2/providers/redis"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/authelia/authelia/internal/configuration/schema"
|
||||
|
@ -13,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
// NewProviderConfig creates a configuration for creating the session provider.
|
||||
func NewProviderConfig(configuration schema.SessionConfiguration) ProviderConfig {
|
||||
func NewProviderConfig(configuration schema.SessionConfiguration, certPool *x509.CertPool) ProviderConfig {
|
||||
config := session.NewDefaultConfig()
|
||||
|
||||
// Override the cookie name.
|
||||
|
@ -35,42 +37,88 @@ func NewProviderConfig(configuration schema.SessionConfiguration) ProviderConfig
|
|||
|
||||
var redisConfig *redis.Config
|
||||
|
||||
var redisSentinelConfig *redis.FailoverConfig
|
||||
|
||||
var providerName string
|
||||
|
||||
// If redis configuration is provided, then use the redis provider.
|
||||
if configuration.Redis != nil {
|
||||
providerName = "redis"
|
||||
switch {
|
||||
case configuration.Redis != nil:
|
||||
serializer := NewEncryptingSerializer(configuration.Secret)
|
||||
network := "tcp"
|
||||
|
||||
var addr string
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
if configuration.Redis.Port == 0 {
|
||||
network = "unix"
|
||||
addr = configuration.Redis.Host
|
||||
if configuration.Redis.TLS != nil {
|
||||
tlsConfig = utils.NewTLSConfig(configuration.Redis.TLS, tls.VersionTLS12, certPool)
|
||||
}
|
||||
|
||||
if configuration.Redis.HighAvailability != nil && configuration.Redis.HighAvailability.SentinelName != "" {
|
||||
addrs := make([]string, 0)
|
||||
|
||||
if configuration.Redis.Host != "" {
|
||||
addrs = append(addrs, fmt.Sprintf("%s:%d", strings.ToLower(configuration.Redis.Host), configuration.Redis.Port))
|
||||
}
|
||||
|
||||
for _, node := range configuration.Redis.HighAvailability.Nodes {
|
||||
addr := fmt.Sprintf("%s:%d", strings.ToLower(node.Host), node.Port)
|
||||
if !utils.IsStringInSlice(addr, addrs) {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
|
||||
providerName = "redis-sentinel"
|
||||
redisSentinelConfig = &redis.FailoverConfig{
|
||||
MasterName: configuration.Redis.HighAvailability.SentinelName,
|
||||
SentinelAddrs: addrs,
|
||||
SentinelPassword: configuration.Redis.HighAvailability.SentinelPassword,
|
||||
RouteByLatency: configuration.Redis.HighAvailability.RouteByLatency,
|
||||
RouteRandomly: configuration.Redis.HighAvailability.RouteRandomly,
|
||||
Username: configuration.Redis.Username,
|
||||
Password: configuration.Redis.Password,
|
||||
DB: configuration.Redis.DatabaseIndex, // DB is the fasthttp/session property for the Redis DB Index.
|
||||
PoolSize: configuration.Redis.MaximumActiveConnections,
|
||||
MinIdleConns: configuration.Redis.MinimumIdleConnections,
|
||||
IdleTimeout: 300,
|
||||
TLSConfig: tlsConfig,
|
||||
KeyPrefix: "authelia-session",
|
||||
}
|
||||
} else {
|
||||
addr = fmt.Sprintf("%s:%d", configuration.Redis.Host, configuration.Redis.Port)
|
||||
providerName = "redis"
|
||||
network := "tcp"
|
||||
|
||||
var addr string
|
||||
|
||||
if configuration.Redis.Port == 0 {
|
||||
network = "unix"
|
||||
addr = configuration.Redis.Host
|
||||
} else {
|
||||
addr = fmt.Sprintf("%s:%d", configuration.Redis.Host, configuration.Redis.Port)
|
||||
}
|
||||
|
||||
redisConfig = &redis.Config{
|
||||
Network: network,
|
||||
Addr: addr,
|
||||
Username: configuration.Redis.Username,
|
||||
Password: configuration.Redis.Password,
|
||||
DB: configuration.Redis.DatabaseIndex, // DB is the fasthttp/session property for the Redis DB Index.
|
||||
PoolSize: configuration.Redis.MaximumActiveConnections,
|
||||
MinIdleConns: configuration.Redis.MinimumIdleConnections,
|
||||
IdleTimeout: 300,
|
||||
TLSConfig: tlsConfig,
|
||||
KeyPrefix: "authelia-session",
|
||||
}
|
||||
}
|
||||
|
||||
redisConfig = &redis.Config{
|
||||
Network: network,
|
||||
Addr: addr,
|
||||
Password: configuration.Redis.Password,
|
||||
// DB is the fasthttp/session property for the Redis DB Index.
|
||||
DB: configuration.Redis.DatabaseIndex,
|
||||
PoolSize: 8,
|
||||
IdleTimeout: 300,
|
||||
KeyPrefix: "authelia-session",
|
||||
}
|
||||
config.EncodeFunc = serializer.Encode
|
||||
config.DecodeFunc = serializer.Decode
|
||||
} else { // if no option is provided, use the memory provider.
|
||||
default:
|
||||
providerName = "memory"
|
||||
}
|
||||
|
||||
return ProviderConfig{
|
||||
config: config,
|
||||
redisConfig: redisConfig,
|
||||
providerName: providerName,
|
||||
config,
|
||||
redisConfig,
|
||||
redisSentinelConfig,
|
||||
providerName,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ package session
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/authelia/session/v2"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
@ -20,7 +20,7 @@ func TestShouldCreateInMemorySessionProvider(t *testing.T) {
|
|||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
providerConfig := NewProviderConfig(configuration)
|
||||
providerConfig := NewProviderConfig(configuration, nil)
|
||||
|
||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
||||
|
@ -31,8 +31,7 @@ func TestShouldCreateInMemorySessionProvider(t *testing.T) {
|
|||
assert.Equal(t, "memory", providerConfig.providerName)
|
||||
}
|
||||
|
||||
func TestShouldCreateRedisSessionProvider(t *testing.T) {
|
||||
// The redis configuration is not provided so we create a in-memory provider.
|
||||
func TestShouldCreateRedisSessionProviderTLS(t *testing.T) {
|
||||
configuration := schema.SessionConfiguration{}
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
|
@ -41,9 +40,14 @@ func TestShouldCreateRedisSessionProvider(t *testing.T) {
|
|||
Host: "redis.example.com",
|
||||
Port: 6379,
|
||||
Password: "pass",
|
||||
TLS: &schema.TLSConfig{
|
||||
ServerName: "redis.fqdn.example.com",
|
||||
MinimumVersion: "TLS1.3",
|
||||
},
|
||||
}
|
||||
providerConfig := NewProviderConfig(configuration)
|
||||
providerConfig := NewProviderConfig(configuration, nil)
|
||||
|
||||
assert.Nil(t, providerConfig.redisSentinelConfig)
|
||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
||||
assert.Equal(t, true, providerConfig.config.Secure)
|
||||
|
@ -57,10 +61,131 @@ func TestShouldCreateRedisSessionProvider(t *testing.T) {
|
|||
assert.Equal(t, "pass", pConfig.Password)
|
||||
// DbNumber is the fasthttp/session property for the Redis DB Index
|
||||
assert.Equal(t, 0, pConfig.DB)
|
||||
assert.Equal(t, 0, pConfig.PoolSize)
|
||||
assert.Equal(t, 0, pConfig.MinIdleConns)
|
||||
|
||||
require.NotNil(t, pConfig.TLSConfig)
|
||||
require.Equal(t, uint16(tls.VersionTLS13), pConfig.TLSConfig.MinVersion)
|
||||
require.Equal(t, "redis.fqdn.example.com", pConfig.TLSConfig.ServerName)
|
||||
require.False(t, pConfig.TLSConfig.InsecureSkipVerify)
|
||||
}
|
||||
|
||||
func TestShouldCreateRedisSessionProvider(t *testing.T) {
|
||||
configuration := schema.SessionConfiguration{}
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "redis.example.com",
|
||||
Port: 6379,
|
||||
Password: "pass",
|
||||
}
|
||||
providerConfig := NewProviderConfig(configuration, nil)
|
||||
|
||||
assert.Nil(t, providerConfig.redisSentinelConfig)
|
||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
||||
assert.Equal(t, true, providerConfig.config.Secure)
|
||||
assert.Equal(t, time.Duration(40)*time.Second, providerConfig.config.Expiration)
|
||||
assert.True(t, providerConfig.config.IsSecureFunc(nil))
|
||||
|
||||
assert.Equal(t, "redis", providerConfig.providerName)
|
||||
|
||||
pConfig := providerConfig.redisConfig
|
||||
assert.Equal(t, "redis.example.com:6379", pConfig.Addr)
|
||||
assert.Equal(t, "pass", pConfig.Password)
|
||||
// DbNumber is the fasthttp/session property for the Redis DB Index
|
||||
assert.Equal(t, 0, pConfig.DB)
|
||||
assert.Equal(t, 0, pConfig.PoolSize)
|
||||
assert.Equal(t, 0, pConfig.MinIdleConns)
|
||||
|
||||
assert.Nil(t, pConfig.TLSConfig)
|
||||
}
|
||||
|
||||
func TestShouldCreateRedisSentinelSessionProviderWithoutDuplicateHosts(t *testing.T) {
|
||||
configuration := schema.SessionConfiguration{}
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "REDIS.example.com",
|
||||
Port: 26379,
|
||||
Password: "pass",
|
||||
MaximumActiveConnections: 8,
|
||||
MinimumIdleConnections: 2,
|
||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||
SentinelName: "mysent",
|
||||
SentinelPassword: "mypass",
|
||||
Nodes: []schema.RedisNode{
|
||||
{
|
||||
Host: "redis2.example.com",
|
||||
Port: 26379,
|
||||
},
|
||||
{
|
||||
Host: "redis.example.com",
|
||||
Port: 26379,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
providerConfig := NewProviderConfig(configuration, nil)
|
||||
|
||||
assert.Len(t, providerConfig.redisSentinelConfig.SentinelAddrs, 2)
|
||||
assert.Equal(t, providerConfig.redisSentinelConfig.SentinelAddrs[0], "redis.example.com:26379")
|
||||
assert.Equal(t, providerConfig.redisSentinelConfig.SentinelAddrs[1], "redis2.example.com:26379")
|
||||
}
|
||||
|
||||
func TestShouldCreateRedisSentinelSessionProvider(t *testing.T) {
|
||||
configuration := schema.SessionConfiguration{}
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
configuration.Redis = &schema.RedisSessionConfiguration{
|
||||
Host: "redis.example.com",
|
||||
Port: 26379,
|
||||
Password: "pass",
|
||||
MaximumActiveConnections: 8,
|
||||
MinimumIdleConnections: 2,
|
||||
HighAvailability: &schema.RedisHighAvailabilityConfiguration{
|
||||
SentinelName: "mysent",
|
||||
SentinelPassword: "mypass",
|
||||
Nodes: []schema.RedisNode{
|
||||
{
|
||||
Host: "redis2.example.com",
|
||||
Port: 26379,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
providerConfig := NewProviderConfig(configuration, nil)
|
||||
|
||||
assert.Nil(t, providerConfig.redisConfig)
|
||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
||||
assert.Equal(t, true, providerConfig.config.Secure)
|
||||
assert.Equal(t, time.Duration(40)*time.Second, providerConfig.config.Expiration)
|
||||
assert.True(t, providerConfig.config.IsSecureFunc(nil))
|
||||
|
||||
assert.Equal(t, "redis-sentinel", providerConfig.providerName)
|
||||
|
||||
pConfig := providerConfig.redisSentinelConfig
|
||||
assert.Equal(t, "redis.example.com:26379", pConfig.SentinelAddrs[0])
|
||||
assert.Equal(t, "redis2.example.com:26379", pConfig.SentinelAddrs[1])
|
||||
assert.Equal(t, "pass", pConfig.Password)
|
||||
assert.Equal(t, "mysent", pConfig.MasterName)
|
||||
assert.Equal(t, "mypass", pConfig.SentinelPassword)
|
||||
assert.False(t, pConfig.RouteRandomly)
|
||||
assert.False(t, pConfig.RouteByLatency)
|
||||
assert.Equal(t, 8, pConfig.PoolSize)
|
||||
assert.Equal(t, 2, pConfig.MinIdleConns)
|
||||
|
||||
// DbNumber is the fasthttp/session property for the Redis DB Index
|
||||
assert.Equal(t, 0, pConfig.DB)
|
||||
assert.Nil(t, pConfig.TLSConfig)
|
||||
}
|
||||
|
||||
func TestShouldCreateRedisSessionProviderWithUnixSocket(t *testing.T) {
|
||||
// The redis configuration is not provided so we create a in-memory provider.
|
||||
configuration := schema.SessionConfiguration{}
|
||||
configuration.Domain = testDomain
|
||||
configuration.Name = testName
|
||||
|
@ -70,7 +195,10 @@ func TestShouldCreateRedisSessionProviderWithUnixSocket(t *testing.T) {
|
|||
Port: 0,
|
||||
Password: "pass",
|
||||
}
|
||||
providerConfig := NewProviderConfig(configuration)
|
||||
|
||||
providerConfig := NewProviderConfig(configuration, nil)
|
||||
|
||||
assert.Nil(t, providerConfig.redisSentinelConfig)
|
||||
|
||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
||||
|
@ -85,6 +213,7 @@ func TestShouldCreateRedisSessionProviderWithUnixSocket(t *testing.T) {
|
|||
assert.Equal(t, "pass", pConfig.Password)
|
||||
// DbNumber is the fasthttp/session property for the Redis DB Index
|
||||
assert.Equal(t, 0, pConfig.DB)
|
||||
assert.Nil(t, pConfig.TLSConfig)
|
||||
}
|
||||
|
||||
func TestShouldSetDbNumber(t *testing.T) {
|
||||
|
@ -98,7 +227,11 @@ func TestShouldSetDbNumber(t *testing.T) {
|
|||
Password: "pass",
|
||||
DatabaseIndex: 5,
|
||||
}
|
||||
providerConfig := NewProviderConfig(configuration)
|
||||
|
||||
providerConfig := NewProviderConfig(configuration, nil)
|
||||
|
||||
assert.Nil(t, providerConfig.redisSentinelConfig)
|
||||
|
||||
assert.Equal(t, "redis", providerConfig.providerName)
|
||||
pConfig := providerConfig.redisConfig
|
||||
// DbNumber is the fasthttp/session property for the Redis DB Index
|
||||
|
@ -114,7 +247,7 @@ func TestShouldUseEncryptingSerializerWithRedis(t *testing.T) {
|
|||
Password: "pass",
|
||||
DatabaseIndex: 5,
|
||||
}
|
||||
providerConfig := NewProviderConfig(configuration)
|
||||
providerConfig := NewProviderConfig(configuration, nil)
|
||||
|
||||
payload := session.Dict{}
|
||||
payload.Set("key", "value")
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestShouldInitializerSession(t *testing.T) {
|
|||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
|
||||
provider := NewProvider(configuration)
|
||||
provider := NewProvider(configuration, nil)
|
||||
session, err := provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -32,7 +32,7 @@ func TestShouldUpdateSession(t *testing.T) {
|
|||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
|
||||
provider := NewProvider(configuration)
|
||||
provider := NewProvider(configuration, nil)
|
||||
session, _ := provider.GetSession(ctx)
|
||||
|
||||
session.Username = testUsername
|
||||
|
@ -57,7 +57,7 @@ func TestShouldDestroySessionAndWipeSessionData(t *testing.T) {
|
|||
configuration.Name = testName
|
||||
configuration.Expiration = testExpiration
|
||||
|
||||
provider := NewProvider(configuration)
|
||||
provider := NewProvider(configuration, nil)
|
||||
session, err := provider.GetSession(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -12,9 +12,10 @@ import (
|
|||
|
||||
// ProviderConfig is the configuration used to create the session provider.
|
||||
type ProviderConfig struct {
|
||||
config session.Config
|
||||
redisConfig *redis.Config
|
||||
providerName string
|
||||
config session.Config
|
||||
redisConfig *redis.Config
|
||||
redisSentinelConfig *redis.FailoverConfig
|
||||
providerName string
|
||||
}
|
||||
|
||||
// U2FRegistration is a serializable version of a U2F registration.
|
||||
|
|
|
@ -85,9 +85,19 @@ session:
|
|||
inactivity: 300 # 5 minutes
|
||||
domain: example.com
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
||||
password: authelia
|
||||
username: authelia
|
||||
password: redis-user-password
|
||||
high_availability:
|
||||
sentinel_name: authelia
|
||||
sentinel_password: sentinel-server-password
|
||||
nodes:
|
||||
- host: redis-sentinel-0
|
||||
port: 26379
|
||||
- host: redis-sentinel-1
|
||||
port: 26379
|
||||
- host: redis-sentinel-2
|
||||
port: 26379
|
||||
|
||||
remember_me_duration: 1y
|
||||
|
||||
regulation:
|
||||
|
|
|
@ -20,6 +20,11 @@ session:
|
|||
expiration: 3600 # 1 hour
|
||||
inactivity: 300 # 5 minutes
|
||||
remember_me_duration: 1y
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
||||
username: authelia
|
||||
password: redis-user-password
|
||||
|
||||
storage:
|
||||
local:
|
||||
|
|
|
@ -55,6 +55,16 @@ func (de *DockerEnvironment) Restart(service string) error {
|
|||
return de.createCommandWithStdout(fmt.Sprintf("restart %s", service)).Run()
|
||||
}
|
||||
|
||||
// Stop a docker service.
|
||||
func (de *DockerEnvironment) Stop(service string) error {
|
||||
return de.createCommandWithStdout(fmt.Sprintf("stop %s", service)).Run()
|
||||
}
|
||||
|
||||
// Start a docker service.
|
||||
func (de *DockerEnvironment) Start(service string) error {
|
||||
return de.createCommandWithStdout(fmt.Sprintf("start %s", service)).Run()
|
||||
}
|
||||
|
||||
// Down destroy a docker environment.
|
||||
func (de *DockerEnvironment) Down() error {
|
||||
return de.createCommandWithStdout("down -v").Run()
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
version: '3'
|
||||
services:
|
||||
redis-node-0:
|
||||
image: redis:6.2-alpine
|
||||
command: /entrypoint.sh master
|
||||
expose:
|
||||
- "6379"
|
||||
volumes:
|
||||
- ./example/compose/redis/templates:/templates
|
||||
- ./example/compose/redis/users.acl:/data/users.acl
|
||||
- ./example/compose/redis/entrypoint.sh:/entrypoint.sh
|
||||
networks:
|
||||
authelianet:
|
||||
aliases:
|
||||
- redis-node-0.example.com
|
||||
ipv4_address: 192.168.240.110
|
||||
redis-node-1:
|
||||
image: redis:6.2-alpine
|
||||
command: /entrypoint.sh slave
|
||||
depends_on:
|
||||
- redis-node-0
|
||||
expose:
|
||||
- "6379"
|
||||
volumes:
|
||||
- ./example/compose/redis/templates:/templates
|
||||
- ./example/compose/redis/users.acl:/data/users.acl
|
||||
- ./example/compose/redis/entrypoint.sh:/entrypoint.sh
|
||||
networks:
|
||||
authelianet:
|
||||
aliases:
|
||||
- redis-node-1.example.com
|
||||
ipv4_address: 192.168.240.111
|
||||
redis-node-2:
|
||||
image: redis:6.2-alpine
|
||||
command: /entrypoint.sh slave
|
||||
depends_on:
|
||||
- redis-node-0
|
||||
expose:
|
||||
- "6379"
|
||||
volumes:
|
||||
- ./example/compose/redis/templates:/templates
|
||||
- ./example/compose/redis/users.acl:/data/users.acl
|
||||
- ./example/compose/redis/entrypoint.sh:/entrypoint.sh
|
||||
networks:
|
||||
authelianet:
|
||||
aliases:
|
||||
- redis-node-2.example.com
|
||||
ipv4_address: 192.168.240.112
|
||||
redis-sentinel-0:
|
||||
image: redis:6.2-alpine
|
||||
command: /entrypoint.sh sentinel
|
||||
depends_on:
|
||||
- redis-node-1
|
||||
- redis-node-2
|
||||
expose:
|
||||
- "26379"
|
||||
volumes:
|
||||
- ./example/compose/redis/templates:/templates
|
||||
- ./example/compose/redis/entrypoint.sh:/entrypoint.sh
|
||||
networks:
|
||||
authelianet:
|
||||
aliases:
|
||||
- redis-sentinel-0.example.com
|
||||
ipv4_address: 192.168.240.120
|
||||
redis-sentinel-1:
|
||||
image: redis:6.2-alpine
|
||||
command: /entrypoint.sh sentinel
|
||||
depends_on:
|
||||
- redis-node-1
|
||||
- redis-node-2
|
||||
expose:
|
||||
- "26379"
|
||||
volumes:
|
||||
- ./example/compose/redis/templates:/templates
|
||||
- ./example/compose/redis/entrypoint.sh:/entrypoint.sh
|
||||
networks:
|
||||
authelianet:
|
||||
aliases:
|
||||
- redis-sentinel-1.example.com
|
||||
ipv4_address: 192.168.240.121
|
||||
redis-sentinel-2:
|
||||
image: redis:6.2-alpine
|
||||
command: /entrypoint.sh sentinel
|
||||
depends_on:
|
||||
- redis-node-1
|
||||
- redis-node-2
|
||||
expose:
|
||||
- "26379"
|
||||
volumes:
|
||||
- ./example/compose/redis/templates:/templates
|
||||
- ./example/compose/redis/entrypoint.sh:/entrypoint.sh
|
||||
networks:
|
||||
authelianet:
|
||||
aliases:
|
||||
- redis-sentinel-2.example.com
|
||||
ipv4_address: 192.168.240.122
|
|
@ -1,9 +1,13 @@
|
|||
version: '3'
|
||||
services:
|
||||
redis:
|
||||
image: redis:4.0-alpine
|
||||
command: redis-server --requirepass authelia
|
||||
ports:
|
||||
- "6379:6379"
|
||||
image: redis:6.2-alpine
|
||||
command: /entrypoint.sh master
|
||||
expose:
|
||||
- "6379"
|
||||
volumes:
|
||||
- ./example/compose/redis/templates:/templates
|
||||
- ./example/compose/redis/users.acl:/data/users.acl
|
||||
- ./example/compose/redis/entrypoint.sh:/entrypoint.sh
|
||||
networks:
|
||||
- authelianet
|
||||
- authelianet
|
15
internal/suites/example/compose/redis/entrypoint.sh
Executable file
15
internal/suites/example/compose/redis/entrypoint.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
|
||||
MODE=$1
|
||||
|
||||
cp /templates/${MODE}.conf /data/redis.conf
|
||||
chown -R redis:redis /data
|
||||
|
||||
if [ "${MODE}" == "master" ] || [ "${MODE}" == "slave" ]; then
|
||||
redis-server /data/redis.conf
|
||||
elif [ "${MODE}" == "sentinel" ]; then
|
||||
redis-server /data/redis.conf --sentinel
|
||||
else
|
||||
echo "invalid argument: entrypoint.sh [master|slave|sentinel]"
|
||||
exit 1
|
||||
fi
|
2017
internal/suites/example/compose/redis/templates/master.conf
Normal file
2017
internal/suites/example/compose/redis/templates/master.conf
Normal file
File diff suppressed because it is too large
Load Diff
346
internal/suites/example/compose/redis/templates/sentinel.conf
Normal file
346
internal/suites/example/compose/redis/templates/sentinel.conf
Normal file
|
@ -0,0 +1,346 @@
|
|||
###########################################################################
|
||||
# Source: https://raw.githubusercontent.com/redis/redis/6.2/sentinel.conf #
|
||||
###########################################################################
|
||||
|
||||
# Example sentinel.conf
|
||||
|
||||
# *** IMPORTANT ***
|
||||
#
|
||||
# By default Sentinel will not be reachable from interfaces different than
|
||||
# localhost, either use the 'bind' directive to bind to a list of network
|
||||
# interfaces, or disable protected mode with "protected-mode no" by
|
||||
# adding it to this configuration file.
|
||||
#
|
||||
# Before doing that MAKE SURE the instance is protected from the outside
|
||||
# world via firewalling or other means.
|
||||
#
|
||||
# For example you may use one of the following:
|
||||
#
|
||||
# bind 127.0.0.1 192.168.1.1
|
||||
#
|
||||
protected-mode no
|
||||
bind 0.0.0.0
|
||||
|
||||
# port <sentinel-port>
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis-sentinel.pid when
|
||||
# daemonized.
|
||||
daemonize no
|
||||
|
||||
# When running daemonized, Redis Sentinel writes a pid file in
|
||||
# /var/run/redis-sentinel.pid by default. You can specify a custom pid file
|
||||
# location here.
|
||||
pidfile /var/run/redis-sentinel.pid
|
||||
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
# Sentinel to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile ""
|
||||
|
||||
# sentinel announce-ip <ip>
|
||||
# sentinel announce-port <port>
|
||||
#
|
||||
# The above two configuration directives are useful in environments where,
|
||||
# because of NAT, Sentinel is reachable from outside via a non-local address.
|
||||
#
|
||||
# When announce-ip is provided, the Sentinel will claim the specified IP address
|
||||
# in HELLO messages used to gossip its presence, instead of auto-detecting the
|
||||
# local address as it usually does.
|
||||
#
|
||||
# Similarly when announce-port is provided and is valid and non-zero, Sentinel
|
||||
# will announce the specified TCP port.
|
||||
#
|
||||
# The two options don't need to be used together, if only announce-ip is
|
||||
# provided, the Sentinel will announce the specified IP and the server port
|
||||
# as specified by the "port" option. If only announce-port is provided, the
|
||||
# Sentinel will announce the auto-detected local IP and the specified port.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# sentinel announce-ip 1.2.3.4
|
||||
|
||||
# dir <working-directory>
|
||||
# Every long running process should have a well-defined working directory.
|
||||
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
|
||||
# for the process to don't interfere with administrative tasks such as
|
||||
# unmounting filesystems.
|
||||
dir /tmp
|
||||
|
||||
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
|
||||
#
|
||||
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
|
||||
# (Objectively Down) state only if at least <quorum> sentinels agree.
|
||||
#
|
||||
# Note that whatever is the ODOWN quorum, a Sentinel will require to
|
||||
# be elected by the majority of the known Sentinels in order to
|
||||
# start a failover, so no failover can be performed in minority.
|
||||
#
|
||||
# Replicas are auto-discovered, so you don't need to specify replicas in
|
||||
# any way. Sentinel itself will rewrite this configuration file adding
|
||||
# the replicas using additional configuration options.
|
||||
# Also note that the configuration file is rewritten when a
|
||||
# replica is promoted to master.
|
||||
#
|
||||
# Note: master name should not include special characters or spaces.
|
||||
# The valid charset is A-z 0-9 and the three characters ".-_".
|
||||
sentinel monitor authelia 192.168.240.110 6379 2
|
||||
|
||||
# sentinel auth-pass <master-name> <password>
|
||||
#
|
||||
# Set the password to use to authenticate with the master and replicas.
|
||||
# Useful if there is a password set in the Redis instances to monitor.
|
||||
#
|
||||
# Note that the master password is also used for replicas, so it is not
|
||||
# possible to set a different password in masters and replicas instances
|
||||
# if you want to be able to monitor these instances with Sentinel.
|
||||
#
|
||||
# However you can have Redis instances without the authentication enabled
|
||||
# mixed with Redis instances requiring the authentication (as long as the
|
||||
# password set is the same for all the instances requiring the password) as
|
||||
# the AUTH command will have no effect in Redis instances with authentication
|
||||
# switched off.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
sentinel auth-pass authelia sentinel-client-password
|
||||
|
||||
sentinel auth-user authelia sentinel
|
||||
#
|
||||
# This is useful in order to authenticate to instances having ACL capabilities,
|
||||
# that is, running Redis 6.0 or greater. When just auth-pass is provided the
|
||||
# Sentinel instance will authenticate to Redis using the old "AUTH <pass>"
|
||||
# method. When also an username is provided, it will use "AUTH <user> <pass>".
|
||||
# In the Redis servers side, the ACL to provide just minimal access to
|
||||
# Sentinel instances, should be configured along the following lines:
|
||||
#
|
||||
# user sentinel-user >somepassword +client +subscribe +publish \
|
||||
# +ping +info +multi +slaveof +config +client +exec on
|
||||
|
||||
# sentinel down-after-milliseconds <master-name> <milliseconds>
|
||||
#
|
||||
# Number of milliseconds the master (or any attached replica or sentinel) should
|
||||
# be unreachable (as in, not acceptable reply to PING, continuously, for the
|
||||
# specified period) in order to consider it in S_DOWN state (Subjectively
|
||||
# Down).
|
||||
#
|
||||
# Default is 30 seconds.
|
||||
sentinel down-after-milliseconds authelia 1000
|
||||
|
||||
# IMPORTANT NOTE: starting with Redis 6.2 ACL capability is supported for
|
||||
# Sentinel mode, please refer to the Redis website https://redis.io/topics/acl
|
||||
# for more details.
|
||||
|
||||
# Sentinel's ACL users are defined in the following format:
|
||||
#
|
||||
# user <username> ... acl rules ...
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# user worker +@admin +@connection ~* on >ffa9203c493aa99
|
||||
#
|
||||
# For more information about ACL configuration please refer to the Redis
|
||||
# website at https://redis.io/topics/acl and redis server configuration
|
||||
# template redis.conf.
|
||||
|
||||
# ACL LOG
|
||||
#
|
||||
# The ACL Log tracks failed commands and authentication events associated
|
||||
# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
|
||||
# by ACLs. The ACL Log is stored in memory. You can reclaim memory with
|
||||
# ACL LOG RESET. Define the maximum entry length of the ACL Log below.
|
||||
acllog-max-len 128
|
||||
|
||||
# Using an external ACL file
|
||||
#
|
||||
# Instead of configuring users here in this file, it is possible to use
|
||||
# a stand-alone file just listing users. The two methods cannot be mixed:
|
||||
# if you configure users here and at the same time you activate the external
|
||||
# ACL file, the server will refuse to start.
|
||||
#
|
||||
# The format of the external ACL user file is exactly the same as the
|
||||
# format that is used inside redis.conf to describe users.
|
||||
#
|
||||
# aclfile /data/sentinel-users.acl
|
||||
|
||||
requirepass sentinel-server-password
|
||||
#
|
||||
# You can configure Sentinel itself to require a password, however when doing
|
||||
# so Sentinel will try to authenticate with the same password to all the
|
||||
# other Sentinels. So you need to configure all your Sentinels in a given
|
||||
# group with the same "requirepass" password. Check the following documentation
|
||||
# for more info: https://redis.io/topics/sentinel
|
||||
#
|
||||
# IMPORTANT NOTE: starting with Redis 6.2 "requirepass" is a compatibility
|
||||
# layer on top of the ACL system. The option effect will be just setting
|
||||
# the password for the default user. Clients will still authenticate using
|
||||
# AUTH <password> as usually, or more explicitly with AUTH default <password>
|
||||
# if they follow the new protocol: both will work.
|
||||
#
|
||||
# New config files are advised to use separate authentication control for
|
||||
# incoming connections (via ACL), and for outgoing connections (via
|
||||
# sentinel-user and sentinel-pass)
|
||||
#
|
||||
# The requirepass is not compatable with aclfile option and the ACL LOAD
|
||||
# command, these will cause requirepass to be ignored.
|
||||
|
||||
# sentinel sentinel-user sentinel
|
||||
#
|
||||
# You can configure Sentinel to authenticate with other Sentinels with specific
|
||||
# user name.
|
||||
|
||||
sentinel sentinel-pass sentinel-server-password
|
||||
#
|
||||
# The password for Sentinel to authenticate with other Sentinels. If sentinel-user
|
||||
# is not configured, Sentinel will use 'default' user with sentinel-pass to authenticate.
|
||||
|
||||
# sentinel parallel-syncs <master-name> <numreplicas>
|
||||
#
|
||||
# How many replicas we can reconfigure to point to the new replica simultaneously
|
||||
# during the failover. Use a low number if you use the replicas to serve query
|
||||
# to avoid that all the replicas will be unreachable at about the same
|
||||
# time while performing the synchronization with the master.
|
||||
sentinel parallel-syncs authelia 1
|
||||
|
||||
# sentinel failover-timeout <master-name> <milliseconds>
|
||||
#
|
||||
# Specifies the failover timeout in milliseconds. It is used in many ways:
|
||||
#
|
||||
# - The time needed to re-start a failover after a previous failover was
|
||||
# already tried against the same master by a given Sentinel, is two
|
||||
# times the failover timeout.
|
||||
#
|
||||
# - The time needed for a replica replicating to a wrong master according
|
||||
# to a Sentinel current configuration, to be forced to replicate
|
||||
# with the right master, is exactly the failover timeout (counting since
|
||||
# the moment a Sentinel detected the misconfiguration).
|
||||
#
|
||||
# - The time needed to cancel a failover that is already in progress but
|
||||
# did not produced any configuration change (SLAVEOF NO ONE yet not
|
||||
# acknowledged by the promoted replica).
|
||||
#
|
||||
# - The maximum time a failover in progress waits for all the replicas to be
|
||||
# reconfigured as replicas of the new master. However even after this time
|
||||
# the replicas will be reconfigured by the Sentinels anyway, but not with
|
||||
# the exact parallel-syncs progression as specified.
|
||||
#
|
||||
# Default is 3 minutes.
|
||||
sentinel failover-timeout authelia 2000
|
||||
|
||||
# SCRIPTS EXECUTION
|
||||
#
|
||||
# sentinel notification-script and sentinel reconfig-script are used in order
|
||||
# to configure scripts that are called to notify the system administrator
|
||||
# or to reconfigure clients after a failover. The scripts are executed
|
||||
# with the following rules for error handling:
|
||||
#
|
||||
# If script exits with "1" the execution is retried later (up to a maximum
|
||||
# number of times currently set to 10).
|
||||
#
|
||||
# If script exits with "2" (or an higher value) the script execution is
|
||||
# not retried.
|
||||
#
|
||||
# If script terminates because it receives a signal the behavior is the same
|
||||
# as exit code 1.
|
||||
#
|
||||
# A script has a maximum running time of 60 seconds. After this limit is
|
||||
# reached the script is terminated with a SIGKILL and the execution retried.
|
||||
|
||||
# NOTIFICATION SCRIPT
|
||||
#
|
||||
# sentinel notification-script <master-name> <script-path>
|
||||
#
|
||||
# Call the specified notification script for any sentinel event that is
|
||||
# generated in the WARNING level (for instance -sdown, -odown, and so forth).
|
||||
# This script should notify the system administrator via email, SMS, or any
|
||||
# other messaging system, that there is something wrong with the monitored
|
||||
# Redis systems.
|
||||
#
|
||||
# The script is called with just two arguments: the first is the event type
|
||||
# and the second the event description.
|
||||
#
|
||||
# The script must exist and be executable in order for sentinel to start if
|
||||
# this option is provided.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# sentinel notification-script mymaster /var/redis/notify.sh
|
||||
|
||||
# CLIENTS RECONFIGURATION SCRIPT
|
||||
#
|
||||
# sentinel client-reconfig-script <master-name> <script-path>
|
||||
#
|
||||
# When the master changed because of a failover a script can be called in
|
||||
# order to perform application-specific tasks to notify the clients that the
|
||||
# configuration has changed and the master is at a different address.
|
||||
#
|
||||
# The following arguments are passed to the script:
|
||||
#
|
||||
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
|
||||
#
|
||||
# <state> is currently always "failover"
|
||||
# <role> is either "leader" or "observer"
|
||||
#
|
||||
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
|
||||
# the old address of the master and the new address of the elected replica
|
||||
# (now a master).
|
||||
#
|
||||
# This script should be resistant to multiple invocations.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
|
||||
|
||||
# SECURITY
|
||||
#
|
||||
# By default SENTINEL SET will not be able to change the notification-script
|
||||
# and client-reconfig-script at runtime. This avoids a trivial security issue
|
||||
# where clients can set the script to anything and trigger a failover in order
|
||||
# to get the program executed.
|
||||
|
||||
sentinel deny-scripts-reconfig yes
|
||||
|
||||
# REDIS COMMANDS RENAMING
|
||||
#
|
||||
# Sometimes the Redis server has certain commands, that are needed for Sentinel
|
||||
# to work correctly, renamed to unguessable strings. This is often the case
|
||||
# of CONFIG and SLAVEOF in the context of providers that provide Redis as
|
||||
# a service, and don't want the customers to reconfigure the instances outside
|
||||
# of the administration console.
|
||||
#
|
||||
# In such case it is possible to tell Sentinel to use different command names
|
||||
# instead of the normal ones. For example if the master "mymaster", and the
|
||||
# associated replicas, have "CONFIG" all renamed to "GUESSME", I could use:
|
||||
#
|
||||
# SENTINEL rename-command mymaster CONFIG GUESSME
|
||||
#
|
||||
# After such configuration is set, every time Sentinel would use CONFIG it will
|
||||
# use GUESSME instead. Note that there is no actual need to respect the command
|
||||
# case, so writing "config guessme" is the same in the example above.
|
||||
#
|
||||
# SENTINEL SET can also be used in order to perform this configuration at runtime.
|
||||
#
|
||||
# In order to set a command back to its original name (undo the renaming), it
|
||||
# is possible to just rename a command to itself:
|
||||
#
|
||||
# SENTINEL rename-command mymaster CONFIG CONFIG
|
||||
|
||||
# HOSTNAMES SUPPORT
|
||||
#
|
||||
# Normally Sentinel uses only IP addresses and requires SENTINEL MONITOR
|
||||
# to specify an IP address. Also, it requires the Redis replica-announce-ip
|
||||
# keyword to specify only IP addresses.
|
||||
#
|
||||
# You may enable hostnames support by enabling resolve-hostnames. Note
|
||||
# that you must make sure your DNS is configured properly and that DNS
|
||||
# resolution does not introduce very long delays.
|
||||
#
|
||||
SENTINEL resolve-hostnames no
|
||||
|
||||
# When resolve-hostnames is enabled, Sentinel still uses IP addresses
|
||||
# when exposing instances to users, configuration files, etc. If you want
|
||||
# to retain the hostnames when announced, enable announce-hostnames below.
|
||||
#
|
||||
SENTINEL announce-hostnames no
|
2017
internal/suites/example/compose/redis/templates/slave.conf
Normal file
2017
internal/suites/example/compose/redis/templates/slave.conf
Normal file
File diff suppressed because it is too large
Load Diff
3
internal/suites/example/compose/redis/users.acl
Normal file
3
internal/suites/example/compose/redis/users.acl
Normal file
|
@ -0,0 +1,3 @@
|
|||
user authelia +@admin +@all ~* &* on >redis-user-password
|
||||
user repl +@admin +@all ~* &* on >repl-password
|
||||
user sentinel +@admin +@all ~* &* on >sentinel-client-password
|
|
@ -13,7 +13,7 @@ var haDockerEnvironment = NewDockerEnvironment([]string{
|
|||
"internal/suites/example/compose/authelia/docker-compose.backend.{}.yml",
|
||||
"internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml",
|
||||
"internal/suites/example/compose/mariadb/docker-compose.yml",
|
||||
"internal/suites/example/compose/redis/docker-compose.yml",
|
||||
"internal/suites/example/compose/redis-sentinel/docker-compose.yml",
|
||||
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
||||
"internal/suites/example/compose/nginx/portal/docker-compose.yml",
|
||||
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||
|
@ -57,7 +57,7 @@ func init() {
|
|||
SetUp: setup,
|
||||
SetUpTimeout: 5 * time.Minute,
|
||||
OnSetupTimeout: displayAutheliaLogs,
|
||||
TestTimeout: 5 * time.Minute,
|
||||
TestTimeout: 6 * time.Minute,
|
||||
TearDown: teardown,
|
||||
TearDownTimeout: 2 * time.Minute,
|
||||
OnError: displayAutheliaLogs,
|
||||
|
|
|
@ -47,6 +47,92 @@ func (s *HighAvailabilityWebDriverSuite) SetupTest() {
|
|||
s.verifyIsHome(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActive() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
|
||||
defer cancel()
|
||||
|
||||
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
err := haDockerEnvironment.Restart("redis-node-0")
|
||||
s.Require().NoError(err)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrimaryRedisNodeFailure() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
|
||||
defer cancel()
|
||||
|
||||
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
|
||||
err := haDockerEnvironment.Stop("redis-node-0")
|
||||
s.Require().NoError(err)
|
||||
|
||||
defer func() {
|
||||
err = haDockerEnvironment.Start("redis-node-0")
|
||||
s.Require().NoError(err)
|
||||
}()
|
||||
|
||||
// Allow fail over to occur.
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
// Verify the user is still authenticated
|
||||
s.doVisit(s.T(), GetLoginBaseURL())
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
|
||||
// Then logout and login again to check we can see the secret.
|
||||
s.doLogout(ctx, s.T())
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, fmt.Sprintf("%s/secret.html", SecureBaseURL))
|
||||
s.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserSessionActiveWithPrimaryRedisSentinelFailureAndSecondaryRedisNodeFailure() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
|
||||
defer cancel()
|
||||
|
||||
secret := s.doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
s.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
|
||||
err := haDockerEnvironment.Stop("redis-sentinel-0")
|
||||
s.Require().NoError(err)
|
||||
|
||||
defer func() {
|
||||
err = haDockerEnvironment.Start("redis-sentinel-0")
|
||||
s.Require().NoError(err)
|
||||
}()
|
||||
|
||||
err = haDockerEnvironment.Stop("redis-node-2")
|
||||
s.Require().NoError(err)
|
||||
|
||||
defer func() {
|
||||
err = haDockerEnvironment.Start("redis-node-2")
|
||||
s.Require().NoError(err)
|
||||
}()
|
||||
|
||||
// Allow fail over to occur.
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
s.doVisit(s.T(), HomeBaseURL)
|
||||
s.verifyIsHome(ctx, s.T())
|
||||
|
||||
// Verify the user is still authenticated
|
||||
s.doVisit(s.T(), GetLoginBaseURL())
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
}
|
||||
|
||||
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserDataInDB() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
|
||||
defer cancel()
|
||||
|
|
|
@ -24,7 +24,10 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() {
|
|||
wds, err := StartWebDriver()
|
||||
s.Require().NoError(err)
|
||||
|
||||
defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
defer func() {
|
||||
err = wds.Stop()
|
||||
s.Require().NoError(err)
|
||||
}()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
|
||||
wds.doVisit(s.T(), targetURL)
|
||||
|
@ -42,7 +45,10 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon1FA() {
|
|||
wds, err := StartWebDriverWithProxy("http://proxy-client1.example.com:3128", GetWebDriverPort())
|
||||
s.Require().NoError(err)
|
||||
|
||||
defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
defer func() {
|
||||
err = wds.Stop()
|
||||
s.Require().NoError(err)
|
||||
}()
|
||||
|
||||
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
|
||||
wds.doVisit(s.T(), targetURL)
|
||||
|
@ -61,7 +67,10 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon0FA() {
|
|||
wds, err := StartWebDriverWithProxy("http://proxy-client2.example.com:3128", GetWebDriverPort())
|
||||
s.Require().NoError(err)
|
||||
|
||||
defer wds.Stop() //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||
defer func() {
|
||||
err = wds.Stop()
|
||||
s.Require().NoError(err)
|
||||
}()
|
||||
|
||||
wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
|
||||
wds.verifySecretAuthorized(ctx, s.T())
|
||||
|
|
|
@ -7,35 +7,36 @@ import (
|
|||
|
||||
var traefik2SuiteName = "Traefik2"
|
||||
|
||||
func init() {
|
||||
dockerEnvironment := NewDockerEnvironment([]string{
|
||||
"internal/suites/docker-compose.yml",
|
||||
"internal/suites/Traefik2/docker-compose.yml",
|
||||
"internal/suites/example/compose/authelia/docker-compose.backend.{}.yml",
|
||||
"internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml",
|
||||
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
||||
"internal/suites/example/compose/traefik2/docker-compose.yml",
|
||||
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||
"internal/suites/example/compose/httpbin/docker-compose.yml",
|
||||
})
|
||||
var traefik2DockerEnvironment = NewDockerEnvironment([]string{
|
||||
"internal/suites/docker-compose.yml",
|
||||
"internal/suites/Traefik2/docker-compose.yml",
|
||||
"internal/suites/example/compose/authelia/docker-compose.backend.{}.yml",
|
||||
"internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml",
|
||||
"internal/suites/example/compose/redis/docker-compose.yml",
|
||||
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
||||
"internal/suites/example/compose/traefik2/docker-compose.yml",
|
||||
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||
"internal/suites/example/compose/httpbin/docker-compose.yml",
|
||||
})
|
||||
|
||||
func init() {
|
||||
setup := func(suitePath string) error {
|
||||
if err := dockerEnvironment.Up(); err != nil {
|
||||
if err := traefik2DockerEnvironment.Up(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return waitUntilAutheliaIsReady(dockerEnvironment, traefik2SuiteName)
|
||||
return waitUntilAutheliaIsReady(traefik2DockerEnvironment, traefik2SuiteName)
|
||||
}
|
||||
|
||||
displayAutheliaLogs := func() error {
|
||||
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
|
||||
backendLogs, err := traefik2DockerEnvironment.Logs("authelia-backend", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(backendLogs)
|
||||
|
||||
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
|
||||
frontendLogs, err := traefik2DockerEnvironment.Logs("authelia-frontend", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -46,7 +47,7 @@ func init() {
|
|||
}
|
||||
|
||||
teardown := func(suitePath string) error {
|
||||
err := dockerEnvironment.Down()
|
||||
err := traefik2DockerEnvironment.Down()
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package suites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
@ -26,6 +29,34 @@ func (s *Traefik2Suite) TestCustomHeaders() {
|
|||
suite.Run(s.T(), NewCustomHeadersScenario())
|
||||
}
|
||||
|
||||
func (s *Traefik2Suite) TestShouldKeepSessionAfterRedisRestart() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
wds, err := StartWebDriver()
|
||||
s.Require().NoError(err)
|
||||
|
||||
defer func() {
|
||||
err = wds.Stop()
|
||||
s.Require().NoError(err)
|
||||
}()
|
||||
|
||||
secret := wds.doRegisterThenLogout(ctx, s.T(), "john", "password")
|
||||
|
||||
wds.doLoginTwoFactor(ctx, s.T(), "john", "password", false, secret, "")
|
||||
|
||||
wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
|
||||
wds.verifySecretAuthorized(ctx, s.T())
|
||||
|
||||
err = traefik2DockerEnvironment.Restart("redis")
|
||||
s.Require().NoError(err)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
wds.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
|
||||
wds.verifySecretAuthorized(ctx, s.T())
|
||||
}
|
||||
|
||||
func TestTraefik2Suite(t *testing.T) {
|
||||
suite.Run(t, NewTraefik2Suite())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user