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{
|
var hostEntries = []HostEntry{
|
||||||
// For authelia backend.
|
// For authelia backend.
|
||||||
{Domain: "authelia.example.com", IP: "192.168.240.50"},
|
{Domain: "authelia.example.com", IP: "192.168.240.50"},
|
||||||
|
|
||||||
// For common tests.
|
// For common tests.
|
||||||
{Domain: "login.example.com", IP: "192.168.240.100"},
|
{Domain: "login.example.com", IP: "192.168.240.100"},
|
||||||
{Domain: "admin.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: "secure.example.com", IP: "192.168.240.100"},
|
||||||
{Domain: "mail.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"},
|
{Domain: "duo.example.com", IP: "192.168.240.100"},
|
||||||
|
|
||||||
// For Traefik suite.
|
// For Traefik suite.
|
||||||
{Domain: "traefik.example.com", IP: "192.168.240.100"},
|
{Domain: "traefik.example.com", IP: "192.168.240.100"},
|
||||||
|
|
||||||
// For HAProxy suite.
|
// For HAProxy suite.
|
||||||
{Domain: "haproxy.example.com", IP: "192.168.240.100"},
|
{Domain: "haproxy.example.com", IP: "192.168.240.100"},
|
||||||
|
|
||||||
// For testing network ACLs.
|
// For testing network ACLs.
|
||||||
{Domain: "proxy-client1.example.com", IP: "192.168.240.201"},
|
{Domain: "proxy-client1.example.com", IP: "192.168.240.201"},
|
||||||
{Domain: "proxy-client2.example.com", IP: "192.168.240.202"},
|
{Domain: "proxy-client2.example.com", IP: "192.168.240.202"},
|
||||||
{Domain: "proxy-client3.example.com", IP: "192.168.240.203"},
|
{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.
|
// Kubernetes dashboard.
|
||||||
{Domain: "kubernetes.example.com", IP: "192.168.240.110"},
|
{Domain: "kubernetes.example.com", IP: "192.168.240.110"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ func startServer() {
|
||||||
|
|
||||||
clock := utils.RealClock{}
|
clock := utils.RealClock{}
|
||||||
authorizer := authorization.NewAuthorizer(config.AccessControl)
|
authorizer := authorization.NewAuthorizer(config.AccessControl)
|
||||||
sessionProvider := session.NewProvider(config.Session)
|
sessionProvider := session.NewProvider(config.Session, autheliaCertPool)
|
||||||
regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)
|
regulator := regulation.NewRegulator(config.Regulation, storageProvider, clock)
|
||||||
|
|
||||||
providers := middlewares.Providers{
|
providers := middlewares.Providers{
|
||||||
|
|
|
@ -8,6 +8,11 @@ port: 9091
|
||||||
# tls_key: /config/ssl/key.pem
|
# tls_key: /config/ssl/key.pem
|
||||||
# tls_cert: /config/ssl/cert.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
|
# The theme to display: light, dark, grey
|
||||||
theme: light
|
theme: light
|
||||||
|
|
||||||
|
@ -118,6 +123,8 @@ authentication_backend:
|
||||||
# server_name: ldap.example.com
|
# server_name: ldap.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
|
skip_verify: false
|
||||||
|
|
||||||
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
||||||
|
@ -326,7 +333,7 @@ session:
|
||||||
# The name of the session cookie. (default: authelia_session).
|
# The name of the session cookie. (default: authelia_session).
|
||||||
name: authelia_session
|
name: authelia_session
|
||||||
|
|
||||||
# The secret to encrypt the session data. This is only used with Redis.
|
# The secret to encrypt the session data. This is only used with Redis / Redis Sentinel.
|
||||||
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
secret: insecure_session_secret
|
secret: insecure_session_secret
|
||||||
|
|
||||||
|
@ -348,19 +355,65 @@ session:
|
||||||
# is restricted to the subdomain of the issuer.
|
# is restricted to the subdomain of the issuer.
|
||||||
domain: example.com
|
domain: example.com
|
||||||
|
|
||||||
# The redis connection details
|
## The redis connection details
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
# Use a unix socket instead
|
## Use a unix socket instead
|
||||||
# host: /var/run/redis/redis.sock
|
# 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
|
password: authelia
|
||||||
# This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
|
|
||||||
|
## This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
|
||||||
database_index: 0
|
database_index: 0
|
||||||
|
|
||||||
|
## 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.
|
# Configuration of the authentication regulation mechanism.
|
||||||
#
|
#
|
||||||
# This mechanism prevents attackers from brute forcing the first factor.
|
# 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 for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||||
# server_name: smtp.example.com
|
# 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
|
skip_verify: false
|
||||||
|
|
||||||
# Minimum TLS version for either StartTLS or SMTPS.
|
# Minimum TLS version for either StartTLS or SMTPS.
|
||||||
|
|
|
@ -48,11 +48,60 @@ session:
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
# Use a unix socket instead
|
## Use a unix socket instead
|
||||||
# host: /var/run/redis/redis.sock
|
# 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
|
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
|
### Security
|
||||||
|
@ -75,3 +124,8 @@ host: "[fd00:1111:2222:3333::1]"
|
||||||
## Loading a password from a secret instead of inside the configuration
|
## 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/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
|
||||||
github.com/Workiva/go-datastructures v1.0.52
|
github.com/Workiva/go-datastructures v1.0.52
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
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/deckarep/golang-set v1.7.1
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/duosecurity/duo_api_golang v0.0.0-20201112143038-0e07e9f869e3
|
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/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 h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
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.5.7 h1:cdF7cod8Lgw7KavtyQstP511Sov10FafIuGnx+w3a/M=
|
||||||
github.com/authelia/session/v2 v2.4.1/go.mod h1:YzuxG4Aj5aFVxO49g9Upvye4BNaTNFjg6rIEgZJ3BQI=
|
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 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/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
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/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/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/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-20210217113430-85d3b37fb239 h1:aTxmMsYGLUZfj0EsWaJ1s0HnctxCgjRw3A+TFoO1Tsc=
|
||||||
github.com/savsgio/dictpool v0.0.0-20210105101557-9da1bc2fbfce/go.mod h1:TNr2IIMnYd9/KYEpTVHVrnfmjizlKPTSgkWUbjyof+A=
|
github.com/savsgio/dictpool v0.0.0-20210217113430-85d3b37fb239/go.mod h1:CfPSewBwpXF/05Izyk9s379O1ysmtUajFVr1nOD83Fs=
|
||||||
github.com/savsgio/gotils v0.0.0-20210105085219-0567298fdcac/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8=
|
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 h1:9AQ3IfP72fCdbYAJNNwovzXrarhaWtxosEuN1fpent0=
|
||||||
github.com/savsgio/gotils v0.0.0-20210225112730-595c7e5a8a7a/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8=
|
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=
|
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/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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
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 h1:OpwH5KDOJ9cS2bq8fD+KfT4IrksK0llvkHf4MZx42jQ=
|
||||||
github.com/valyala/fasthttp v1.22.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU=
|
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=
|
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_key: /config/ssl/key.pem
|
||||||
# tls_cert: /config/ssl/cert.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
|
# The theme to display: light, dark, grey
|
||||||
theme: light
|
theme: light
|
||||||
|
|
||||||
|
@ -118,6 +123,8 @@ authentication_backend:
|
||||||
# server_name: ldap.example.com
|
# server_name: ldap.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
|
skip_verify: false
|
||||||
|
|
||||||
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
# Minimum TLS version for either Secure LDAP or LDAP StartTLS.
|
||||||
|
@ -326,7 +333,7 @@ session:
|
||||||
# The name of the session cookie. (default: authelia_session).
|
# The name of the session cookie. (default: authelia_session).
|
||||||
name: authelia_session
|
name: authelia_session
|
||||||
|
|
||||||
# The secret to encrypt the session data. This is only used with Redis.
|
# The secret to encrypt the session data. This is only used with Redis / Redis Sentinel.
|
||||||
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
# Secret can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
secret: insecure_session_secret
|
secret: insecure_session_secret
|
||||||
|
|
||||||
|
@ -348,19 +355,65 @@ session:
|
||||||
# is restricted to the subdomain of the issuer.
|
# is restricted to the subdomain of the issuer.
|
||||||
domain: example.com
|
domain: example.com
|
||||||
|
|
||||||
# The redis connection details
|
## The redis connection details
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
# Use a unix socket instead
|
## Use a unix socket instead
|
||||||
# host: /var/run/redis/redis.sock
|
# 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
|
password: authelia
|
||||||
# This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
|
|
||||||
|
## This is the Redis DB Index https://redis.io/commands/select (sometimes referred to as database number, DB, etc).
|
||||||
database_index: 0
|
database_index: 0
|
||||||
|
|
||||||
|
## 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.
|
# Configuration of the authentication regulation mechanism.
|
||||||
#
|
#
|
||||||
# This mechanism prevents attackers from brute forcing the first factor.
|
# 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 for certificate validation (in case you are using the IP or non-FQDN in the host option).
|
||||||
# server_name: smtp.example.com
|
# 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
|
skip_verify: false
|
||||||
|
|
||||||
# Minimum TLS version for either StartTLS or SMTPS.
|
# Minimum TLS version for either StartTLS or SMTPS.
|
||||||
|
|
|
@ -1,11 +1,31 @@
|
||||||
package schema
|
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.
|
// RedisSessionConfiguration represents the configuration related to redis session store.
|
||||||
type RedisSessionConfiguration struct {
|
type RedisSessionConfiguration struct {
|
||||||
Host string `mapstructure:"host"`
|
Host string `mapstructure:"host"`
|
||||||
Port int64 `mapstructure:"port"`
|
Port int `mapstructure:"port"`
|
||||||
|
Username string `mapstructure:"username"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
DatabaseIndex int `mapstructure:"database_index"`
|
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.
|
// SessionConfiguration represents the configuration related to user sessions.
|
||||||
|
|
|
@ -42,8 +42,24 @@ var validKeys = []string{
|
||||||
// Redis Session Keys.
|
// Redis Session Keys.
|
||||||
"session.redis.host",
|
"session.redis.host",
|
||||||
"session.redis.port",
|
"session.redis.port",
|
||||||
|
"session.redis.username",
|
||||||
"session.redis.password",
|
"session.redis.password",
|
||||||
"session.redis.database_index",
|
"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.
|
// Local Storage Keys.
|
||||||
"storage.local.path",
|
"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",
|
"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 denyPolicy = "deny"
|
||||||
const bypassPolicy = "bypass"
|
const bypassPolicy = "bypass"
|
||||||
|
|
||||||
|
|
|
@ -16,15 +16,21 @@ func ValidateSession(configuration *schema.SessionConfiguration, validator *sche
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.Redis != nil {
|
if configuration.Redis != nil {
|
||||||
if configuration.Secret == "" {
|
if configuration.Redis.HighAvailability != nil {
|
||||||
validator.Push(errors.New("Set secret of the session object"))
|
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 {
|
||||||
if !strings.HasPrefix(configuration.Redis.Host, "/") && configuration.Redis.Port == 0 {
|
validateRedis(configuration, validator)
|
||||||
validator.Push(errors.New("A redis port different than 0 must be provided"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateSession(configuration, validator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSession(configuration *schema.SessionConfiguration, validator *schema.StructValidator) {
|
||||||
if configuration.Expiration == "" {
|
if configuration.Expiration == "" {
|
||||||
configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour
|
configuration.Expiration = schema.DefaultSessionConfiguration.Expiration // 1 hour
|
||||||
} else if _, err := utils.ParseDurationString(configuration.Expiration); err != nil {
|
} 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"))
|
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
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/configuration/schema"
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
@ -22,7 +24,8 @@ func TestShouldSetDefaultSessionName(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
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)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Name, config.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +35,8 @@ func TestShouldSetDefaultSessionInactivity(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
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)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Inactivity, config.Inactivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +46,8 @@ func TestShouldSetDefaultSessionExpiration(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
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)
|
assert.Equal(t, schema.DefaultSessionConfiguration.Expiration, config.Expiration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,10 +69,47 @@ func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
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()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultSessionConfig()
|
config := newDefaultSessionConfig()
|
||||||
config.Secret = ""
|
config.Secret = ""
|
||||||
|
@ -85,8 +127,9 @@ func TestShouldRaiseErrorWhenRedisIsUsedAndPasswordNotSet(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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) {
|
func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
|
||||||
|
@ -106,10 +149,214 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
assert.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "A redis port different than 0 must be provided")
|
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) {
|
func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultSessionConfig()
|
config := newDefaultSessionConfig()
|
||||||
|
@ -117,6 +364,7 @@ func TestShouldRaiseErrorWhenDomainNotSet(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
assert.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "Set domain of the session object")
|
assert.EqualError(t, validator.Errors()[0], "Set domain of the session object")
|
||||||
}
|
}
|
||||||
|
@ -128,6 +376,7 @@ func TestShouldRaiseErrorWhenDomainIsWildcard(t *testing.T) {
|
||||||
|
|
||||||
ValidateSession(&config, validator)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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")
|
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)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 2)
|
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()[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")
|
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)
|
ValidateSession(&config, validator)
|
||||||
|
|
||||||
|
assert.False(t, validator.HasWarnings())
|
||||||
assert.Len(t, validator.Errors(), 1)
|
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")
|
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)
|
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)
|
assert.Equal(t, config.RememberMeDuration, schema.DefaultSessionConfiguration.RememberMeDuration)
|
||||||
}
|
}
|
||||||
|
|
|
@ -617,7 +617,7 @@ func TestShouldDestroySessionWhenInactiveForTooLong(t *testing.T) {
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
||||||
// Reload the session provider since the configuration is indirect.
|
// 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)
|
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
|
@ -650,7 +650,7 @@ func TestShouldDestroySessionWhenInactiveForTooLongUsingDurationNotation(t *test
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = "10s"
|
mock.Ctx.Configuration.Session.Inactivity = "10s"
|
||||||
// Reload the session provider since the configuration is indirect.
|
// 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)
|
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
||||||
|
|
||||||
userSession := mock.Ctx.GetSession()
|
userSession := mock.Ctx.GetSession()
|
||||||
|
@ -747,7 +747,7 @@ func TestShouldRedirectWhenSessionInactiveForTooLongAndRDParamProvided(t *testin
|
||||||
|
|
||||||
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
mock.Ctx.Configuration.Session.Inactivity = testInactivity
|
||||||
// Reload the session provider since the configuration is indirect.
|
// 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)
|
assert.Equal(t, time.Second*10, mock.Ctx.Providers.SessionProvider.Inactivity)
|
||||||
|
|
||||||
past := clock.Now().Add(-1 * time.Hour)
|
past := clock.Now().Add(-1 * time.Hour)
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestShouldCallNextWithAutheliaCtx(t *testing.T) {
|
||||||
ctx := &fasthttp.RequestCtx{}
|
ctx := &fasthttp.RequestCtx{}
|
||||||
configuration := schema.Configuration{}
|
configuration := schema.Configuration{}
|
||||||
userProvider := mocks.NewMockUserProvider(ctrl)
|
userProvider := mocks.NewMockUserProvider(ctrl)
|
||||||
sessionProvider := session.NewProvider(configuration.Session)
|
sessionProvider := session.NewProvider(configuration.Session, nil)
|
||||||
providers := middlewares.Providers{
|
providers := middlewares.Providers{
|
||||||
UserProvider: userProvider,
|
UserProvider: userProvider,
|
||||||
SessionProvider: sessionProvider,
|
SessionProvider: sessionProvider,
|
||||||
|
|
|
@ -108,7 +108,7 @@ func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {
|
||||||
configuration.AccessControl)
|
configuration.AccessControl)
|
||||||
|
|
||||||
providers.SessionProvider = session.NewProvider(
|
providers.SessionProvider = session.NewProvider(
|
||||||
configuration.Session)
|
configuration.Session, nil)
|
||||||
|
|
||||||
providers.Regulator = regulation.NewRegulator(configuration.Regulation, providers.StorageProvider, &mockAuthelia.Clock)
|
providers.Regulator = regulation.NewRegulator(configuration.Regulation, providers.StorageProvider, &mockAuthelia.Clock)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package session
|
package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/configuration/schema"
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/internal/logging"
|
||||||
"github.com/authelia/authelia/internal/utils"
|
"github.com/authelia/authelia/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,42 +23,51 @@ type Provider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProvider instantiate a session provider given a configuration.
|
// NewProvider instantiate a session provider given a configuration.
|
||||||
func NewProvider(configuration schema.SessionConfiguration) *Provider {
|
func NewProvider(configuration schema.SessionConfiguration, certPool *x509.CertPool) *Provider {
|
||||||
providerConfig := NewProviderConfig(configuration)
|
providerConfig := NewProviderConfig(configuration, certPool)
|
||||||
|
|
||||||
provider := new(Provider)
|
provider := new(Provider)
|
||||||
provider.sessionHolder = fasthttpsession.New(providerConfig.config)
|
provider.sessionHolder = fasthttpsession.New(providerConfig.config)
|
||||||
|
|
||||||
|
logger := logging.Logger()
|
||||||
|
|
||||||
duration, err := utils.ParseDurationString(configuration.RememberMeDuration)
|
duration, err := utils.ParseDurationString(configuration.RememberMeDuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
logger.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.RememberMe = duration
|
provider.RememberMe = duration
|
||||||
|
|
||||||
duration, err = utils.ParseDurationString(configuration.Inactivity)
|
duration, err = utils.ParseDurationString(configuration.Inactivity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
logger.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.Inactivity = duration
|
provider.Inactivity = duration
|
||||||
|
|
||||||
var providerImpl fasthttpsession.Provider
|
var providerImpl fasthttpsession.Provider
|
||||||
if providerConfig.redisConfig != nil {
|
|
||||||
|
switch {
|
||||||
|
case providerConfig.redisConfig != nil:
|
||||||
providerImpl, err = redis.New(*providerConfig.redisConfig)
|
providerImpl, err = redis.New(*providerConfig.redisConfig)
|
||||||
if err != nil {
|
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{})
|
providerImpl, err = memory.New(memory.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
logger.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = provider.sessionHolder.SetProvider(providerImpl)
|
err = provider.sessionHolder.SetProvider(providerImpl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
logger.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider
|
return provider
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package session
|
package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/authelia/session/v2"
|
"github.com/authelia/session/v2"
|
||||||
"github.com/authelia/session/v2/providers/redis"
|
"github.com/authelia/session/v2/providers/redis"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/configuration/schema"
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
|
@ -13,7 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewProviderConfig creates a configuration for creating the session provider.
|
// 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()
|
config := session.NewDefaultConfig()
|
||||||
|
|
||||||
// Override the cookie name.
|
// Override the cookie name.
|
||||||
|
@ -35,12 +37,53 @@ func NewProviderConfig(configuration schema.SessionConfiguration) ProviderConfig
|
||||||
|
|
||||||
var redisConfig *redis.Config
|
var redisConfig *redis.Config
|
||||||
|
|
||||||
|
var redisSentinelConfig *redis.FailoverConfig
|
||||||
|
|
||||||
var providerName string
|
var providerName string
|
||||||
|
|
||||||
// If redis configuration is provided, then use the redis provider.
|
// If redis configuration is provided, then use the redis provider.
|
||||||
if configuration.Redis != nil {
|
switch {
|
||||||
providerName = "redis"
|
case configuration.Redis != nil:
|
||||||
serializer := NewEncryptingSerializer(configuration.Secret)
|
serializer := NewEncryptingSerializer(configuration.Secret)
|
||||||
|
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
|
||||||
|
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 {
|
||||||
|
providerName = "redis"
|
||||||
network := "tcp"
|
network := "tcp"
|
||||||
|
|
||||||
var addr string
|
var addr string
|
||||||
|
@ -55,22 +98,27 @@ func NewProviderConfig(configuration schema.SessionConfiguration) ProviderConfig
|
||||||
redisConfig = &redis.Config{
|
redisConfig = &redis.Config{
|
||||||
Network: network,
|
Network: network,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
Username: configuration.Redis.Username,
|
||||||
Password: configuration.Redis.Password,
|
Password: configuration.Redis.Password,
|
||||||
// DB is the fasthttp/session property for the Redis DB Index.
|
DB: configuration.Redis.DatabaseIndex, // DB is the fasthttp/session property for the Redis DB Index.
|
||||||
DB: configuration.Redis.DatabaseIndex,
|
PoolSize: configuration.Redis.MaximumActiveConnections,
|
||||||
PoolSize: 8,
|
MinIdleConns: configuration.Redis.MinimumIdleConnections,
|
||||||
IdleTimeout: 300,
|
IdleTimeout: 300,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
KeyPrefix: "authelia-session",
|
KeyPrefix: "authelia-session",
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.EncodeFunc = serializer.Encode
|
config.EncodeFunc = serializer.Encode
|
||||||
config.DecodeFunc = serializer.Decode
|
config.DecodeFunc = serializer.Decode
|
||||||
} else { // if no option is provided, use the memory provider.
|
default:
|
||||||
providerName = "memory"
|
providerName = "memory"
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProviderConfig{
|
return ProviderConfig{
|
||||||
config: config,
|
config,
|
||||||
redisConfig: redisConfig,
|
redisConfig,
|
||||||
providerName: providerName,
|
redisSentinelConfig,
|
||||||
|
providerName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authelia/session/v2"
|
"github.com/authelia/session/v2"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ func TestShouldCreateInMemorySessionProvider(t *testing.T) {
|
||||||
configuration.Domain = testDomain
|
configuration.Domain = testDomain
|
||||||
configuration.Name = testName
|
configuration.Name = testName
|
||||||
configuration.Expiration = testExpiration
|
configuration.Expiration = testExpiration
|
||||||
providerConfig := NewProviderConfig(configuration)
|
providerConfig := NewProviderConfig(configuration, nil)
|
||||||
|
|
||||||
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
assert.Equal(t, "my_session", providerConfig.config.CookieName)
|
||||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
||||||
|
@ -31,8 +31,7 @@ func TestShouldCreateInMemorySessionProvider(t *testing.T) {
|
||||||
assert.Equal(t, "memory", providerConfig.providerName)
|
assert.Equal(t, "memory", providerConfig.providerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldCreateRedisSessionProvider(t *testing.T) {
|
func TestShouldCreateRedisSessionProviderTLS(t *testing.T) {
|
||||||
// The redis configuration is not provided so we create a in-memory provider.
|
|
||||||
configuration := schema.SessionConfiguration{}
|
configuration := schema.SessionConfiguration{}
|
||||||
configuration.Domain = testDomain
|
configuration.Domain = testDomain
|
||||||
configuration.Name = testName
|
configuration.Name = testName
|
||||||
|
@ -41,9 +40,14 @@ func TestShouldCreateRedisSessionProvider(t *testing.T) {
|
||||||
Host: "redis.example.com",
|
Host: "redis.example.com",
|
||||||
Port: 6379,
|
Port: 6379,
|
||||||
Password: "pass",
|
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, "my_session", providerConfig.config.CookieName)
|
||||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
||||||
assert.Equal(t, true, providerConfig.config.Secure)
|
assert.Equal(t, true, providerConfig.config.Secure)
|
||||||
|
@ -57,10 +61,131 @@ func TestShouldCreateRedisSessionProvider(t *testing.T) {
|
||||||
assert.Equal(t, "pass", pConfig.Password)
|
assert.Equal(t, "pass", pConfig.Password)
|
||||||
// DbNumber is the fasthttp/session property for the Redis DB Index
|
// DbNumber is the fasthttp/session property for the Redis DB Index
|
||||||
assert.Equal(t, 0, pConfig.DB)
|
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) {
|
func TestShouldCreateRedisSessionProviderWithUnixSocket(t *testing.T) {
|
||||||
// The redis configuration is not provided so we create a in-memory provider.
|
|
||||||
configuration := schema.SessionConfiguration{}
|
configuration := schema.SessionConfiguration{}
|
||||||
configuration.Domain = testDomain
|
configuration.Domain = testDomain
|
||||||
configuration.Name = testName
|
configuration.Name = testName
|
||||||
|
@ -70,7 +195,10 @@ func TestShouldCreateRedisSessionProviderWithUnixSocket(t *testing.T) {
|
||||||
Port: 0,
|
Port: 0,
|
||||||
Password: "pass",
|
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, "my_session", providerConfig.config.CookieName)
|
||||||
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
assert.Equal(t, testDomain, providerConfig.config.Domain)
|
||||||
|
@ -85,6 +213,7 @@ func TestShouldCreateRedisSessionProviderWithUnixSocket(t *testing.T) {
|
||||||
assert.Equal(t, "pass", pConfig.Password)
|
assert.Equal(t, "pass", pConfig.Password)
|
||||||
// DbNumber is the fasthttp/session property for the Redis DB Index
|
// DbNumber is the fasthttp/session property for the Redis DB Index
|
||||||
assert.Equal(t, 0, pConfig.DB)
|
assert.Equal(t, 0, pConfig.DB)
|
||||||
|
assert.Nil(t, pConfig.TLSConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldSetDbNumber(t *testing.T) {
|
func TestShouldSetDbNumber(t *testing.T) {
|
||||||
|
@ -98,7 +227,11 @@ func TestShouldSetDbNumber(t *testing.T) {
|
||||||
Password: "pass",
|
Password: "pass",
|
||||||
DatabaseIndex: 5,
|
DatabaseIndex: 5,
|
||||||
}
|
}
|
||||||
providerConfig := NewProviderConfig(configuration)
|
|
||||||
|
providerConfig := NewProviderConfig(configuration, nil)
|
||||||
|
|
||||||
|
assert.Nil(t, providerConfig.redisSentinelConfig)
|
||||||
|
|
||||||
assert.Equal(t, "redis", providerConfig.providerName)
|
assert.Equal(t, "redis", providerConfig.providerName)
|
||||||
pConfig := providerConfig.redisConfig
|
pConfig := providerConfig.redisConfig
|
||||||
// DbNumber is the fasthttp/session property for the Redis DB Index
|
// DbNumber is the fasthttp/session property for the Redis DB Index
|
||||||
|
@ -114,7 +247,7 @@ func TestShouldUseEncryptingSerializerWithRedis(t *testing.T) {
|
||||||
Password: "pass",
|
Password: "pass",
|
||||||
DatabaseIndex: 5,
|
DatabaseIndex: 5,
|
||||||
}
|
}
|
||||||
providerConfig := NewProviderConfig(configuration)
|
providerConfig := NewProviderConfig(configuration, nil)
|
||||||
|
|
||||||
payload := session.Dict{}
|
payload := session.Dict{}
|
||||||
payload.Set("key", "value")
|
payload.Set("key", "value")
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestShouldInitializerSession(t *testing.T) {
|
||||||
configuration.Name = testName
|
configuration.Name = testName
|
||||||
configuration.Expiration = testExpiration
|
configuration.Expiration = testExpiration
|
||||||
|
|
||||||
provider := NewProvider(configuration)
|
provider := NewProvider(configuration, nil)
|
||||||
session, err := provider.GetSession(ctx)
|
session, err := provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func TestShouldUpdateSession(t *testing.T) {
|
||||||
configuration.Name = testName
|
configuration.Name = testName
|
||||||
configuration.Expiration = testExpiration
|
configuration.Expiration = testExpiration
|
||||||
|
|
||||||
provider := NewProvider(configuration)
|
provider := NewProvider(configuration, nil)
|
||||||
session, _ := provider.GetSession(ctx)
|
session, _ := provider.GetSession(ctx)
|
||||||
|
|
||||||
session.Username = testUsername
|
session.Username = testUsername
|
||||||
|
@ -57,7 +57,7 @@ func TestShouldDestroySessionAndWipeSessionData(t *testing.T) {
|
||||||
configuration.Name = testName
|
configuration.Name = testName
|
||||||
configuration.Expiration = testExpiration
|
configuration.Expiration = testExpiration
|
||||||
|
|
||||||
provider := NewProvider(configuration)
|
provider := NewProvider(configuration, nil)
|
||||||
session, err := provider.GetSession(ctx)
|
session, err := provider.GetSession(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
type ProviderConfig struct {
|
type ProviderConfig struct {
|
||||||
config session.Config
|
config session.Config
|
||||||
redisConfig *redis.Config
|
redisConfig *redis.Config
|
||||||
|
redisSentinelConfig *redis.FailoverConfig
|
||||||
providerName string
|
providerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,9 +85,19 @@ session:
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
domain: example.com
|
domain: example.com
|
||||||
redis:
|
redis:
|
||||||
host: redis
|
username: authelia
|
||||||
port: 6379
|
password: redis-user-password
|
||||||
password: authelia
|
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
|
remember_me_duration: 1y
|
||||||
|
|
||||||
regulation:
|
regulation:
|
||||||
|
|
|
@ -20,6 +20,11 @@ session:
|
||||||
expiration: 3600 # 1 hour
|
expiration: 3600 # 1 hour
|
||||||
inactivity: 300 # 5 minutes
|
inactivity: 300 # 5 minutes
|
||||||
remember_me_duration: 1y
|
remember_me_duration: 1y
|
||||||
|
redis:
|
||||||
|
host: redis
|
||||||
|
port: 6379
|
||||||
|
username: authelia
|
||||||
|
password: redis-user-password
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
local:
|
local:
|
||||||
|
|
|
@ -55,6 +55,16 @@ func (de *DockerEnvironment) Restart(service string) error {
|
||||||
return de.createCommandWithStdout(fmt.Sprintf("restart %s", service)).Run()
|
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.
|
// Down destroy a docker environment.
|
||||||
func (de *DockerEnvironment) Down() error {
|
func (de *DockerEnvironment) Down() error {
|
||||||
return de.createCommandWithStdout("down -v").Run()
|
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'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
image: redis:4.0-alpine
|
image: redis:6.2-alpine
|
||||||
command: redis-server --requirepass authelia
|
command: /entrypoint.sh master
|
||||||
ports:
|
expose:
|
||||||
- "6379:6379"
|
- "6379"
|
||||||
|
volumes:
|
||||||
|
- ./example/compose/redis/templates:/templates
|
||||||
|
- ./example/compose/redis/users.acl:/data/users.acl
|
||||||
|
- ./example/compose/redis/entrypoint.sh:/entrypoint.sh
|
||||||
networks:
|
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.backend.{}.yml",
|
||||||
"internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml",
|
"internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml",
|
||||||
"internal/suites/example/compose/mariadb/docker-compose.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/backend/docker-compose.yml",
|
||||||
"internal/suites/example/compose/nginx/portal/docker-compose.yml",
|
"internal/suites/example/compose/nginx/portal/docker-compose.yml",
|
||||||
"internal/suites/example/compose/smtp/docker-compose.yml",
|
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||||
|
@ -57,7 +57,7 @@ func init() {
|
||||||
SetUp: setup,
|
SetUp: setup,
|
||||||
SetUpTimeout: 5 * time.Minute,
|
SetUpTimeout: 5 * time.Minute,
|
||||||
OnSetupTimeout: displayAutheliaLogs,
|
OnSetupTimeout: displayAutheliaLogs,
|
||||||
TestTimeout: 5 * time.Minute,
|
TestTimeout: 6 * time.Minute,
|
||||||
TearDown: teardown,
|
TearDown: teardown,
|
||||||
TearDownTimeout: 2 * time.Minute,
|
TearDownTimeout: 2 * time.Minute,
|
||||||
OnError: displayAutheliaLogs,
|
OnError: displayAutheliaLogs,
|
||||||
|
|
|
@ -47,6 +47,92 @@ func (s *HighAvailabilityWebDriverSuite) SetupTest() {
|
||||||
s.verifyIsHome(ctx, s.T())
|
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() {
|
func (s *HighAvailabilityWebDriverSuite) TestShouldKeepUserDataInDB() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
|
@ -24,7 +24,10 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon2FA() {
|
||||||
wds, err := StartWebDriver()
|
wds, err := StartWebDriver()
|
||||||
s.Require().NoError(err)
|
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)
|
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
|
||||||
wds.doVisit(s.T(), targetURL)
|
wds.doVisit(s.T(), targetURL)
|
||||||
|
@ -42,7 +45,10 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon1FA() {
|
||||||
wds, err := StartWebDriverWithProxy("http://proxy-client1.example.com:3128", GetWebDriverPort())
|
wds, err := StartWebDriverWithProxy("http://proxy-client1.example.com:3128", GetWebDriverPort())
|
||||||
s.Require().NoError(err)
|
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)
|
targetURL := fmt.Sprintf("%s/secret.html", SecureBaseURL)
|
||||||
wds.doVisit(s.T(), targetURL)
|
wds.doVisit(s.T(), targetURL)
|
||||||
|
@ -61,7 +67,10 @@ func (s *NetworkACLSuite) TestShouldAccessSecretUpon0FA() {
|
||||||
wds, err := StartWebDriverWithProxy("http://proxy-client2.example.com:3128", GetWebDriverPort())
|
wds, err := StartWebDriverWithProxy("http://proxy-client2.example.com:3128", GetWebDriverPort())
|
||||||
s.Require().NoError(err)
|
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.doVisit(s.T(), fmt.Sprintf("%s/secret.html", SecureBaseURL))
|
||||||
wds.verifySecretAuthorized(ctx, s.T())
|
wds.verifySecretAuthorized(ctx, s.T())
|
||||||
|
|
|
@ -7,35 +7,36 @@ import (
|
||||||
|
|
||||||
var traefik2SuiteName = "Traefik2"
|
var traefik2SuiteName = "Traefik2"
|
||||||
|
|
||||||
func init() {
|
var traefik2DockerEnvironment = NewDockerEnvironment([]string{
|
||||||
dockerEnvironment := NewDockerEnvironment([]string{
|
|
||||||
"internal/suites/docker-compose.yml",
|
"internal/suites/docker-compose.yml",
|
||||||
"internal/suites/Traefik2/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.backend.{}.yml",
|
||||||
"internal/suites/example/compose/authelia/docker-compose.frontend.{}.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/nginx/backend/docker-compose.yml",
|
||||||
"internal/suites/example/compose/traefik2/docker-compose.yml",
|
"internal/suites/example/compose/traefik2/docker-compose.yml",
|
||||||
"internal/suites/example/compose/smtp/docker-compose.yml",
|
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||||
"internal/suites/example/compose/httpbin/docker-compose.yml",
|
"internal/suites/example/compose/httpbin/docker-compose.yml",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
func init() {
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
if err := dockerEnvironment.Up(); err != nil {
|
if err := traefik2DockerEnvironment.Up(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment, traefik2SuiteName)
|
return waitUntilAutheliaIsReady(traefik2DockerEnvironment, traefik2SuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
|
backendLogs, err := traefik2DockerEnvironment.Logs("authelia-backend", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(backendLogs)
|
fmt.Println(backendLogs)
|
||||||
|
|
||||||
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
|
frontendLogs, err := traefik2DockerEnvironment.Logs("authelia-frontend", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -46,7 +47,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown := func(suitePath string) error {
|
teardown := func(suitePath string) error {
|
||||||
err := dockerEnvironment.Down()
|
err := traefik2DockerEnvironment.Down()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package suites
|
package suites
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
@ -26,6 +29,34 @@ func (s *Traefik2Suite) TestCustomHeaders() {
|
||||||
suite.Run(s.T(), NewCustomHeadersScenario())
|
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) {
|
func TestTraefik2Suite(t *testing.T) {
|
||||||
suite.Run(t, NewTraefik2Suite())
|
suite.Run(t, NewTraefik2Suite())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user