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:
		| @ -14,7 +14,7 @@ from pathlib import Path | ||||
| from sys import argv, stderr | ||||
| from time import time | ||||
| from typing import Any | ||||
| from urllib.parse import urlparse | ||||
| from urllib.parse import quote_plus, urlparse | ||||
|  | ||||
| import yaml | ||||
| from django.conf import ImproperlyConfigured | ||||
| @ -331,6 +331,26 @@ class 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 len(argv) < 2:  # noqa: PLR2004 | ||||
|         print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder)) | ||||
|  | ||||
| @ -35,6 +35,7 @@ redis: | ||||
|   password: "" | ||||
|   tls: false | ||||
|   tls_reqs: "none" | ||||
|   tls_ca_cert: null | ||||
|  | ||||
| # broker: | ||||
| #   url: "" | ||||
|  | ||||
| @ -5,13 +5,12 @@ import os | ||||
| from collections import OrderedDict | ||||
| from hashlib import sha512 | ||||
| from pathlib import Path | ||||
| from urllib.parse import quote_plus | ||||
|  | ||||
| from celery.schedules import crontab | ||||
| from sentry_sdk import set_tag | ||||
|  | ||||
| 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.sentry import sentry_init | ||||
| 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 = { | ||||
|     "default": { | ||||
|         "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), | ||||
|         "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, | ||||
|         "OPTIONS": { | ||||
|             "CLIENT_CLASS": "django_redis.client.DefaultClient", | ||||
|         }, | ||||
|         "KEY_PREFIX": "authentik_cache", | ||||
|         "KEY_FUNCTION": "django_tenants.cache.make_key", | ||||
|         "REVERSE_KEY_FUNCTION": "django_tenants.cache.reverse_key", | ||||
| @ -276,7 +265,7 @@ CHANNEL_LAYERS = { | ||||
|     "default": { | ||||
|         "BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer", | ||||
|         "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_", | ||||
|         }, | ||||
|     }, | ||||
| @ -376,11 +365,9 @@ CELERY = { | ||||
|     "beat_scheduler": "authentik.tenants.scheduler:TenantAwarePersistentScheduler", | ||||
|     "task_create_missing_queues": True, | ||||
|     "task_default_queue": "authentik", | ||||
|     "broker_url": CONFIG.get("broker.url") | ||||
|     or f"{_redis_url}/{CONFIG.get('redis.db')}{_redis_celery_tls_requirements}", | ||||
|     "broker_url": CONFIG.get("broker.url") or redis_url(CONFIG.get("redis.db")), | ||||
|     "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"), | ||||
|     "result_backend": CONFIG.get("result_backend.url") | ||||
|     or f"{_redis_url}/{CONFIG.get('redis.db')}{_redis_celery_tls_requirements}", | ||||
| } | ||||
|  | ||||
| # Sentry integration | ||||
|  | ||||
| @ -32,6 +32,7 @@ type RedisConfig struct { | ||||
| 	Password  string  `yaml:"password" env:"PASSWORD, overwrite"` | ||||
| 	TLS       bool    `yaml:"tls" env:"TLS, overwrite"` | ||||
| 	TLSReqs   string  `yaml:"tls_reqs" env:"TLS_REQS, overwrite"` | ||||
| 	TLSCaCert *string `yaml:"tls_ca_certs" env:"TLS_CA_CERT, overwrite"` | ||||
| } | ||||
|  | ||||
| type ListenConfig struct { | ||||
|  | ||||
| @ -2,6 +2,8 @@ package application | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509" | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"net/http" | ||||
| @ -19,6 +21,7 @@ import ( | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/codecs" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/constants" | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/redisstore" | ||||
| 	"goauthentik.io/internal/utils" | ||||
| ) | ||||
|  | ||||
| const RedisKeyPrefix = "authentik_proxy_session_" | ||||
| @ -31,11 +34,40 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) | ||||
| 		maxAge = int(*t) + 1 | ||||
| 	} | ||||
| 	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{ | ||||
| 			Addr:      fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), | ||||
| 			Username:  config.Get().Redis.Username, | ||||
| 			Password:  config.Get().Redis.Password, | ||||
| 			DB:        config.Get().Redis.DB, | ||||
| 			TLSConfig: tls, | ||||
| 		}) | ||||
|  | ||||
| 		// New default RedisStore | ||||
|  | ||||
| @ -9,7 +9,7 @@ from psycopg import OperationalError, connect | ||||
| from redis import Redis | ||||
| from redis.exceptions import RedisError | ||||
|  | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.lib.config import CONFIG, redis_url | ||||
|  | ||||
|  | ||||
| def check_postgres(): | ||||
| @ -35,24 +35,18 @@ def check_postgres(): | ||||
|  | ||||
|  | ||||
| def check_redis(): | ||||
|     REDIS_PROTOCOL_PREFIX = "redis://" | ||||
|     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')}" | ||||
|     ) | ||||
|     url = redis_url(CONFIG.get("redis.db")) | ||||
|     while True: | ||||
|         try: | ||||
|             redis = Redis.from_url(REDIS_URL) | ||||
|             redis = Redis.from_url(url) | ||||
|             redis.ping() | ||||
|             break | ||||
|         except RedisError as exc: | ||||
|             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") | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -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__USE_PGBOUNCER`: Adjust configuration to support connection to PgBouncer | ||||
| -   `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__SSLCERT`: Path to x509 client certificate to authenticate to server | ||||
| -   `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__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_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 | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L