root: make redis settings more consistent (#9335)

* make redis settings more consistent

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add support to go

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rewrite url

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix redis connect in wait_for_db

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* censor password when logging error

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* reword docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add redis url generation helper

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2024-04-18 16:49:41 +02:00
committed by GitHub
parent 2e9df96a62
commit a742331484
7 changed files with 84 additions and 48 deletions

View File

@ -14,7 +14,7 @@ from pathlib import Path
from sys import argv, stderr from sys import argv, stderr
from time import time from time import time
from typing import Any from typing import Any
from urllib.parse import urlparse from urllib.parse import quote_plus, urlparse
import yaml import yaml
from django.conf import ImproperlyConfigured from django.conf import ImproperlyConfigured
@ -331,6 +331,26 @@ class ConfigLoader:
CONFIG = ConfigLoader() CONFIG = ConfigLoader()
def redis_url(db: int) -> str:
"""Helper to create a Redis URL for a specific database"""
_redis_protocol_prefix = "redis://"
_redis_tls_requirements = ""
if CONFIG.get_bool("redis.tls", False):
_redis_protocol_prefix = "rediss://"
_redis_tls_requirements = f"?ssl_cert_reqs={CONFIG.get('redis.tls_reqs')}"
if _redis_ca := CONFIG.get("redis.tls_ca_cert", None):
_redis_tls_requirements += f"&ssl_ca_certs={_redis_ca}"
_redis_url = (
f"{_redis_protocol_prefix}"
f"{quote_plus(CONFIG.get('redis.username'))}:"
f"{quote_plus(CONFIG.get('redis.password'))}@"
f"{quote_plus(CONFIG.get('redis.host'))}:"
f"{CONFIG.get_int('redis.port')}"
f"/{db}{_redis_tls_requirements}"
)
return _redis_url
if __name__ == "__main__": if __name__ == "__main__":
if len(argv) < 2: # noqa: PLR2004 if len(argv) < 2: # noqa: PLR2004
print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder)) print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder))

View File

@ -35,6 +35,7 @@ redis:
password: "" password: ""
tls: false tls: false
tls_reqs: "none" tls_reqs: "none"
tls_ca_cert: null
# broker: # broker:
# url: "" # url: ""

View File

@ -5,13 +5,12 @@ import os
from collections import OrderedDict from collections import OrderedDict
from hashlib import sha512 from hashlib import sha512
from pathlib import Path from pathlib import Path
from urllib.parse import quote_plus
from celery.schedules import crontab from celery.schedules import crontab
from sentry_sdk import set_tag from sentry_sdk import set_tag
from authentik import ENV_GIT_HASH_KEY, __version__ from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG, redis_url
from authentik.lib.logging import get_logger_config, structlog_configure from authentik.lib.logging import get_logger_config, structlog_configure
from authentik.lib.sentry import sentry_init from authentik.lib.sentry import sentry_init
from authentik.lib.utils.reflection import get_env from authentik.lib.utils.reflection import get_env
@ -195,25 +194,15 @@ REST_FRAMEWORK = {
}, },
} }
_redis_protocol_prefix = "redis://"
_redis_celery_tls_requirements = ""
if CONFIG.get_bool("redis.tls", False):
_redis_protocol_prefix = "rediss://"
_redis_celery_tls_requirements = f"?ssl_cert_reqs={CONFIG.get('redis.tls_reqs')}"
_redis_url = (
f"{_redis_protocol_prefix}"
f"{quote_plus(CONFIG.get('redis.username'))}:"
f"{quote_plus(CONFIG.get('redis.password'))}@"
f"{quote_plus(CONFIG.get('redis.host'))}:"
f"{CONFIG.get_int('redis.port')}"
)
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": "django_redis.cache.RedisCache", "BACKEND": "django_redis.cache.RedisCache",
"LOCATION": CONFIG.get("cache.url") or f"{_redis_url}/{CONFIG.get('redis.db')}", "LOCATION": CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db")),
"TIMEOUT": CONFIG.get_int("cache.timeout", 300), "TIMEOUT": CONFIG.get_int("cache.timeout", 300),
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, "OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
"KEY_PREFIX": "authentik_cache", "KEY_PREFIX": "authentik_cache",
"KEY_FUNCTION": "django_tenants.cache.make_key", "KEY_FUNCTION": "django_tenants.cache.make_key",
"REVERSE_KEY_FUNCTION": "django_tenants.cache.reverse_key", "REVERSE_KEY_FUNCTION": "django_tenants.cache.reverse_key",
@ -276,7 +265,7 @@ CHANNEL_LAYERS = {
"default": { "default": {
"BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer", "BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer",
"CONFIG": { "CONFIG": {
"hosts": [CONFIG.get("channel.url", f"{_redis_url}/{CONFIG.get('redis.db')}")], "hosts": [CONFIG.get("channel.url") or redis_url(CONFIG.get("redis.db"))],
"prefix": "authentik_channels_", "prefix": "authentik_channels_",
}, },
}, },
@ -376,11 +365,9 @@ CELERY = {
"beat_scheduler": "authentik.tenants.scheduler:TenantAwarePersistentScheduler", "beat_scheduler": "authentik.tenants.scheduler:TenantAwarePersistentScheduler",
"task_create_missing_queues": True, "task_create_missing_queues": True,
"task_default_queue": "authentik", "task_default_queue": "authentik",
"broker_url": CONFIG.get("broker.url") "broker_url": CONFIG.get("broker.url") or redis_url(CONFIG.get("redis.db")),
or f"{_redis_url}/{CONFIG.get('redis.db')}{_redis_celery_tls_requirements}", "result_backend": CONFIG.get("result_backend.url") or redis_url(CONFIG.get("redis.db")),
"broker_transport_options": CONFIG.get_dict_from_b64_json("broker.transport_options"), "broker_transport_options": CONFIG.get_dict_from_b64_json("broker.transport_options"),
"result_backend": CONFIG.get("result_backend.url")
or f"{_redis_url}/{CONFIG.get('redis.db')}{_redis_celery_tls_requirements}",
} }
# Sentry integration # Sentry integration

View File

@ -25,13 +25,14 @@ type Config struct {
} }
type RedisConfig struct { type RedisConfig struct {
Host string `yaml:"host" env:"HOST, overwrite"` Host string `yaml:"host" env:"HOST, overwrite"`
Port int `yaml:"port" env:"PORT, overwrite"` Port int `yaml:"port" env:"PORT, overwrite"`
DB int `yaml:"db" env:"DB, overwrite"` DB int `yaml:"db" env:"DB, overwrite"`
Username string `yaml:"username" env:"USERNAME, overwrite"` Username string `yaml:"username" env:"USERNAME, overwrite"`
Password string `yaml:"password" env:"PASSWORD, overwrite"` Password string `yaml:"password" env:"PASSWORD, overwrite"`
TLS bool `yaml:"tls" env:"TLS, overwrite"` TLS bool `yaml:"tls" env:"TLS, overwrite"`
TLSReqs string `yaml:"tls_reqs" env:"TLS_REQS, overwrite"` TLSReqs string `yaml:"tls_reqs" env:"TLS_REQS, overwrite"`
TLSCaCert *string `yaml:"tls_ca_certs" env:"TLS_CA_CERT, overwrite"`
} }
type ListenConfig struct { type ListenConfig struct {

View File

@ -2,6 +2,8 @@ package application
import ( import (
"context" "context"
"crypto/tls"
"crypto/x509"
"fmt" "fmt"
"math" "math"
"net/http" "net/http"
@ -19,6 +21,7 @@ import (
"goauthentik.io/internal/outpost/proxyv2/codecs" "goauthentik.io/internal/outpost/proxyv2/codecs"
"goauthentik.io/internal/outpost/proxyv2/constants" "goauthentik.io/internal/outpost/proxyv2/constants"
"goauthentik.io/internal/outpost/proxyv2/redisstore" "goauthentik.io/internal/outpost/proxyv2/redisstore"
"goauthentik.io/internal/utils"
) )
const RedisKeyPrefix = "authentik_proxy_session_" const RedisKeyPrefix = "authentik_proxy_session_"
@ -31,11 +34,40 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
maxAge = int(*t) + 1 maxAge = int(*t) + 1
} }
if a.isEmbedded { if a.isEmbedded {
var tls *tls.Config
if config.Get().Redis.TLS {
tls = utils.GetTLSConfig()
switch strings.ToLower(config.Get().Redis.TLSReqs) {
case "none":
case "false":
tls.InsecureSkipVerify = true
case "required":
break
}
ca := config.Get().Redis.TLSCaCert
if ca != nil {
// Get the SystemCertPool, continue with an empty pool on error
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}
certs, err := os.ReadFile(*ca)
if err != nil {
a.log.WithError(err).Fatalf("Failed to append %s to RootCAs", *ca)
}
// Append our cert to the system pool
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
a.log.Println("No certs appended, using system certs only")
}
tls.RootCAs = rootCAs
}
}
client := redis.NewClient(&redis.Options{ client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port),
Username: config.Get().Redis.Username, Username: config.Get().Redis.Username,
Password: config.Get().Redis.Password, Password: config.Get().Redis.Password,
DB: config.Get().Redis.DB, DB: config.Get().Redis.DB,
TLSConfig: tls,
}) })
// New default RedisStore // New default RedisStore

View File

@ -9,7 +9,7 @@ from psycopg import OperationalError, connect
from redis import Redis from redis import Redis
from redis.exceptions import RedisError from redis.exceptions import RedisError
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG, redis_url
def check_postgres(): def check_postgres():
@ -35,24 +35,18 @@ def check_postgres():
def check_redis(): def check_redis():
REDIS_PROTOCOL_PREFIX = "redis://" url = redis_url(CONFIG.get("redis.db"))
if CONFIG.get_bool("redis.tls", False):
REDIS_PROTOCOL_PREFIX = "rediss://"
REDIS_URL = (
f"{REDIS_PROTOCOL_PREFIX}"
f"{quote_plus(CONFIG.get('redis.username'))}:"
f"{quote_plus(CONFIG.get('redis.password'))}@"
f"{quote_plus(CONFIG.get('redis.host'))}:"
f"{CONFIG.get_int('redis.port')}/{CONFIG.get('redis.db')}"
)
while True: while True:
try: try:
redis = Redis.from_url(REDIS_URL) redis = Redis.from_url(url)
redis.ping() redis.ping()
break break
except RedisError as exc: except RedisError as exc:
sleep(1) sleep(1)
CONFIG.log("info", f"Redis Connection failed, retrying... ({exc})", redis_url=REDIS_URL) sanitized_url = url.replace(quote_plus(CONFIG.get("redis.password")), "******")
CONFIG.log(
"info", f"Redis Connection failed, retrying... ({exc})", redis_url=sanitized_url
)
CONFIG.log("info", "Redis Connection successful") CONFIG.log("info", "Redis Connection successful")

View File

@ -72,7 +72,7 @@ To check if your config has been applied correctly, you can run the following co
- `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD` - `AUTHENTIK_POSTGRESQL__PASSWORD`: Database password, defaults to the environment variable `POSTGRES_PASSWORD`
- `AUTHENTIK_POSTGRESQL__USE_PGBOUNCER`: Adjust configuration to support connection to PgBouncer - `AUTHENTIK_POSTGRESQL__USE_PGBOUNCER`: Adjust configuration to support connection to PgBouncer
- `AUTHENTIK_POSTGRESQL__USE_PGPOOL`: Adjust configuration to support connection to Pgpool - `AUTHENTIK_POSTGRESQL__USE_PGPOOL`: Adjust configuration to support connection to Pgpool
- `AUTHENTIK_POSTGRESQL__SSLMODE`: Strictness of ssl verification. Defaults to `verify-ca` - `AUTHENTIK_POSTGRESQL__SSLMODE`: Strictness of ssl verification. Defaults to `"verify-ca"`
- `AUTHENTIK_POSTGRESQL__SSLROOTCERT`: CA root for server ssl verification - `AUTHENTIK_POSTGRESQL__SSLROOTCERT`: CA root for server ssl verification
- `AUTHENTIK_POSTGRESQL__SSLCERT`: Path to x509 client certificate to authenticate to server - `AUTHENTIK_POSTGRESQL__SSLCERT`: Path to x509 client certificate to authenticate to server
- `AUTHENTIK_POSTGRESQL__SSLKEY`: Path to private key of `SSLCERT` certificate - `AUTHENTIK_POSTGRESQL__SSLKEY`: Path to private key of `SSLCERT` certificate
@ -85,7 +85,8 @@ To check if your config has been applied correctly, you can run the following co
- `AUTHENTIK_REDIS__USERNAME`: Redis server username when not using configuration URL - `AUTHENTIK_REDIS__USERNAME`: Redis server username when not using configuration URL
- `AUTHENTIK_REDIS__PASSWORD`: Redis server password when not using configuration URL - `AUTHENTIK_REDIS__PASSWORD`: Redis server password when not using configuration URL
- `AUTHENTIK_REDIS__TLS`: Redis server connection using TLS when not using configuration URL - `AUTHENTIK_REDIS__TLS`: Redis server connection using TLS when not using configuration URL
- `AUTHENTIK_REDIS__TLS_REQS`: Redis server TLS connection requirements when not using configuration URL - `AUTHENTIK_REDIS__TLS_REQS`: Redis server TLS connection requirements when not using configuration URL. Defaults to `"none"`. Allowed values are `"none"` and `"required"`.
- `AUTHENTIK_REDIS__TLS_CA_CERT`: Path to the Redis server TLS CA root when not using configuration URL. Defaults to `null`.
## Result Backend Settings ## Result Backend Settings