root: fix ssl settings for read replicas not being applied (#12341) * root: fix ssl settings for read replicas not being applied * fix * slight refactor --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens L. <jens@goauthentik.io>
This commit is contained in:
committed by
GitHub
parent
0edd7531a1
commit
50ad69bdad
@ -5,6 +5,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from copy import deepcopy
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from glob import glob
|
from glob import glob
|
||||||
@ -336,6 +337,58 @@ def redis_url(db: int) -> str:
|
|||||||
return _redis_url
|
return _redis_url
|
||||||
|
|
||||||
|
|
||||||
|
def django_db_config(config: ConfigLoader | None = None) -> dict:
|
||||||
|
if not config:
|
||||||
|
config = CONFIG
|
||||||
|
db = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "authentik.root.db",
|
||||||
|
"HOST": config.get("postgresql.host"),
|
||||||
|
"NAME": config.get("postgresql.name"),
|
||||||
|
"USER": config.get("postgresql.user"),
|
||||||
|
"PASSWORD": config.get("postgresql.password"),
|
||||||
|
"PORT": config.get("postgresql.port"),
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslmode": config.get("postgresql.sslmode"),
|
||||||
|
"sslrootcert": config.get("postgresql.sslrootcert"),
|
||||||
|
"sslcert": config.get("postgresql.sslcert"),
|
||||||
|
"sslkey": config.get("postgresql.sslkey"),
|
||||||
|
},
|
||||||
|
"TEST": {
|
||||||
|
"NAME": config.get("postgresql.test.name"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.get_bool("postgresql.use_pgpool", False):
|
||||||
|
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
||||||
|
|
||||||
|
if config.get_bool("postgresql.use_pgbouncer", False):
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
|
||||||
|
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
||||||
|
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
|
||||||
|
db["default"]["CONN_MAX_AGE"] = None # persistent
|
||||||
|
|
||||||
|
for replica in config.get_keys("postgresql.read_replicas"):
|
||||||
|
_database = deepcopy(db["default"])
|
||||||
|
for setting, current_value in db["default"].items():
|
||||||
|
if isinstance(current_value, dict):
|
||||||
|
continue
|
||||||
|
override = config.get(
|
||||||
|
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=UNSET
|
||||||
|
)
|
||||||
|
if override is not UNSET:
|
||||||
|
_database[setting] = override
|
||||||
|
for setting in db["default"]["OPTIONS"].keys():
|
||||||
|
override = config.get(
|
||||||
|
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=UNSET
|
||||||
|
)
|
||||||
|
if override is not UNSET:
|
||||||
|
_database["OPTIONS"][setting] = override
|
||||||
|
db[f"replica_{replica}"] = _database
|
||||||
|
return db
|
||||||
|
|
||||||
|
|
||||||
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))
|
||||||
|
|||||||
@ -9,7 +9,14 @@ from unittest import mock
|
|||||||
from django.conf import ImproperlyConfigured
|
from django.conf import ImproperlyConfigured
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from authentik.lib.config import ENV_PREFIX, UNSET, Attr, AttrEncoder, ConfigLoader
|
from authentik.lib.config import (
|
||||||
|
ENV_PREFIX,
|
||||||
|
UNSET,
|
||||||
|
Attr,
|
||||||
|
AttrEncoder,
|
||||||
|
ConfigLoader,
|
||||||
|
django_db_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestConfig(TestCase):
|
class TestConfig(TestCase):
|
||||||
@ -175,3 +182,201 @@ class TestConfig(TestCase):
|
|||||||
config = ConfigLoader()
|
config = ConfigLoader()
|
||||||
config.set("foo.bar", "baz")
|
config.set("foo.bar", "baz")
|
||||||
self.assertEqual(list(config.get_keys("foo")), ["bar"])
|
self.assertEqual(list(config.get_keys("foo")), ["bar"])
|
||||||
|
|
||||||
|
def test_db_default(self):
|
||||||
|
"""Test default DB Config"""
|
||||||
|
config = ConfigLoader()
|
||||||
|
config.set("postgresql.host", "foo")
|
||||||
|
config.set("postgresql.name", "foo")
|
||||||
|
config.set("postgresql.user", "foo")
|
||||||
|
config.set("postgresql.password", "foo")
|
||||||
|
config.set("postgresql.port", "foo")
|
||||||
|
config.set("postgresql.sslmode", "foo")
|
||||||
|
config.set("postgresql.sslrootcert", "foo")
|
||||||
|
config.set("postgresql.sslcert", "foo")
|
||||||
|
config.set("postgresql.sslkey", "foo")
|
||||||
|
config.set("postgresql.test.name", "foo")
|
||||||
|
conf = django_db_config(config)
|
||||||
|
self.assertEqual(
|
||||||
|
conf,
|
||||||
|
{
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "authentik.root.db",
|
||||||
|
"HOST": "foo",
|
||||||
|
"NAME": "foo",
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslcert": "foo",
|
||||||
|
"sslkey": "foo",
|
||||||
|
"sslmode": "foo",
|
||||||
|
"sslrootcert": "foo",
|
||||||
|
},
|
||||||
|
"PASSWORD": "foo",
|
||||||
|
"PORT": "foo",
|
||||||
|
"TEST": {"NAME": "foo"},
|
||||||
|
"USER": "foo",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_db_read_replicas(self):
|
||||||
|
"""Test read replicas"""
|
||||||
|
config = ConfigLoader()
|
||||||
|
config.set("postgresql.host", "foo")
|
||||||
|
config.set("postgresql.name", "foo")
|
||||||
|
config.set("postgresql.user", "foo")
|
||||||
|
config.set("postgresql.password", "foo")
|
||||||
|
config.set("postgresql.port", "foo")
|
||||||
|
config.set("postgresql.sslmode", "foo")
|
||||||
|
config.set("postgresql.sslrootcert", "foo")
|
||||||
|
config.set("postgresql.sslcert", "foo")
|
||||||
|
config.set("postgresql.sslkey", "foo")
|
||||||
|
config.set("postgresql.test.name", "foo")
|
||||||
|
# Read replica
|
||||||
|
config.set("postgresql.read_replicas.0.host", "bar")
|
||||||
|
conf = django_db_config(config)
|
||||||
|
self.assertEqual(
|
||||||
|
conf,
|
||||||
|
{
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "authentik.root.db",
|
||||||
|
"HOST": "foo",
|
||||||
|
"NAME": "foo",
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslcert": "foo",
|
||||||
|
"sslkey": "foo",
|
||||||
|
"sslmode": "foo",
|
||||||
|
"sslrootcert": "foo",
|
||||||
|
},
|
||||||
|
"PASSWORD": "foo",
|
||||||
|
"PORT": "foo",
|
||||||
|
"TEST": {"NAME": "foo"},
|
||||||
|
"USER": "foo",
|
||||||
|
},
|
||||||
|
"replica_0": {
|
||||||
|
"ENGINE": "authentik.root.db",
|
||||||
|
"HOST": "bar",
|
||||||
|
"NAME": "foo",
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslcert": "foo",
|
||||||
|
"sslkey": "foo",
|
||||||
|
"sslmode": "foo",
|
||||||
|
"sslrootcert": "foo",
|
||||||
|
},
|
||||||
|
"PASSWORD": "foo",
|
||||||
|
"PORT": "foo",
|
||||||
|
"TEST": {"NAME": "foo"},
|
||||||
|
"USER": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_db_read_replicas_pgpool(self):
|
||||||
|
"""Test read replicas"""
|
||||||
|
config = ConfigLoader()
|
||||||
|
config.set("postgresql.host", "foo")
|
||||||
|
config.set("postgresql.name", "foo")
|
||||||
|
config.set("postgresql.user", "foo")
|
||||||
|
config.set("postgresql.password", "foo")
|
||||||
|
config.set("postgresql.port", "foo")
|
||||||
|
config.set("postgresql.sslmode", "foo")
|
||||||
|
config.set("postgresql.sslrootcert", "foo")
|
||||||
|
config.set("postgresql.sslcert", "foo")
|
||||||
|
config.set("postgresql.sslkey", "foo")
|
||||||
|
config.set("postgresql.test.name", "foo")
|
||||||
|
config.set("postgresql.use_pgpool", True)
|
||||||
|
# Read replica
|
||||||
|
config.set("postgresql.read_replicas.0.host", "bar")
|
||||||
|
# This isn't supported
|
||||||
|
config.set("postgresql.read_replicas.0.use_pgpool", False)
|
||||||
|
conf = django_db_config(config)
|
||||||
|
self.assertEqual(
|
||||||
|
conf,
|
||||||
|
{
|
||||||
|
"default": {
|
||||||
|
"DISABLE_SERVER_SIDE_CURSORS": True,
|
||||||
|
"ENGINE": "authentik.root.db",
|
||||||
|
"HOST": "foo",
|
||||||
|
"NAME": "foo",
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslcert": "foo",
|
||||||
|
"sslkey": "foo",
|
||||||
|
"sslmode": "foo",
|
||||||
|
"sslrootcert": "foo",
|
||||||
|
},
|
||||||
|
"PASSWORD": "foo",
|
||||||
|
"PORT": "foo",
|
||||||
|
"TEST": {"NAME": "foo"},
|
||||||
|
"USER": "foo",
|
||||||
|
},
|
||||||
|
"replica_0": {
|
||||||
|
"DISABLE_SERVER_SIDE_CURSORS": True,
|
||||||
|
"ENGINE": "authentik.root.db",
|
||||||
|
"HOST": "bar",
|
||||||
|
"NAME": "foo",
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslcert": "foo",
|
||||||
|
"sslkey": "foo",
|
||||||
|
"sslmode": "foo",
|
||||||
|
"sslrootcert": "foo",
|
||||||
|
},
|
||||||
|
"PASSWORD": "foo",
|
||||||
|
"PORT": "foo",
|
||||||
|
"TEST": {"NAME": "foo"},
|
||||||
|
"USER": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_db_read_replicas_diff_ssl(self):
|
||||||
|
"""Test read replicas (with different SSL Settings)"""
|
||||||
|
"""Test read replicas"""
|
||||||
|
config = ConfigLoader()
|
||||||
|
config.set("postgresql.host", "foo")
|
||||||
|
config.set("postgresql.name", "foo")
|
||||||
|
config.set("postgresql.user", "foo")
|
||||||
|
config.set("postgresql.password", "foo")
|
||||||
|
config.set("postgresql.port", "foo")
|
||||||
|
config.set("postgresql.sslmode", "foo")
|
||||||
|
config.set("postgresql.sslrootcert", "foo")
|
||||||
|
config.set("postgresql.sslcert", "foo")
|
||||||
|
config.set("postgresql.sslkey", "foo")
|
||||||
|
config.set("postgresql.test.name", "foo")
|
||||||
|
# Read replica
|
||||||
|
config.set("postgresql.read_replicas.0.host", "bar")
|
||||||
|
config.set("postgresql.read_replicas.0.sslcert", "bar")
|
||||||
|
conf = django_db_config(config)
|
||||||
|
self.assertEqual(
|
||||||
|
conf,
|
||||||
|
{
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "authentik.root.db",
|
||||||
|
"HOST": "foo",
|
||||||
|
"NAME": "foo",
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslcert": "foo",
|
||||||
|
"sslkey": "foo",
|
||||||
|
"sslmode": "foo",
|
||||||
|
"sslrootcert": "foo",
|
||||||
|
},
|
||||||
|
"PASSWORD": "foo",
|
||||||
|
"PORT": "foo",
|
||||||
|
"TEST": {"NAME": "foo"},
|
||||||
|
"USER": "foo",
|
||||||
|
},
|
||||||
|
"replica_0": {
|
||||||
|
"ENGINE": "authentik.root.db",
|
||||||
|
"HOST": "bar",
|
||||||
|
"NAME": "foo",
|
||||||
|
"OPTIONS": {
|
||||||
|
"sslcert": "bar",
|
||||||
|
"sslkey": "foo",
|
||||||
|
"sslmode": "foo",
|
||||||
|
"sslrootcert": "foo",
|
||||||
|
},
|
||||||
|
"PASSWORD": "foo",
|
||||||
|
"PORT": "foo",
|
||||||
|
"TEST": {"NAME": "foo"},
|
||||||
|
"USER": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from sentry_sdk import set_tag
|
|||||||
from xmlsec import enable_debug_trace
|
from xmlsec import enable_debug_trace
|
||||||
|
|
||||||
from authentik import __version__
|
from authentik import __version__
|
||||||
from authentik.lib.config import CONFIG, redis_url
|
from authentik.lib.config import CONFIG, django_db_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
|
||||||
@ -295,47 +295,7 @@ CHANNEL_LAYERS = {
|
|||||||
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
||||||
|
|
||||||
ORIGINAL_BACKEND = "django_prometheus.db.backends.postgresql"
|
ORIGINAL_BACKEND = "django_prometheus.db.backends.postgresql"
|
||||||
DATABASES = {
|
DATABASES = django_db_config()
|
||||||
"default": {
|
|
||||||
"ENGINE": "authentik.root.db",
|
|
||||||
"HOST": CONFIG.get("postgresql.host"),
|
|
||||||
"NAME": CONFIG.get("postgresql.name"),
|
|
||||||
"USER": CONFIG.get("postgresql.user"),
|
|
||||||
"PASSWORD": CONFIG.get("postgresql.password"),
|
|
||||||
"PORT": CONFIG.get("postgresql.port"),
|
|
||||||
"OPTIONS": {
|
|
||||||
"sslmode": CONFIG.get("postgresql.sslmode"),
|
|
||||||
"sslrootcert": CONFIG.get("postgresql.sslrootcert"),
|
|
||||||
"sslcert": CONFIG.get("postgresql.sslcert"),
|
|
||||||
"sslkey": CONFIG.get("postgresql.sslkey"),
|
|
||||||
},
|
|
||||||
"TEST": {
|
|
||||||
"NAME": CONFIG.get("postgresql.test.name"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if CONFIG.get_bool("postgresql.use_pgpool", False):
|
|
||||||
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
|
||||||
|
|
||||||
if CONFIG.get_bool("postgresql.use_pgbouncer", False):
|
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
|
|
||||||
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
|
|
||||||
DATABASES["default"]["CONN_MAX_AGE"] = None # persistent
|
|
||||||
|
|
||||||
for replica in CONFIG.get_keys("postgresql.read_replicas"):
|
|
||||||
_database = DATABASES["default"].copy()
|
|
||||||
for setting in DATABASES["default"].keys():
|
|
||||||
default = object()
|
|
||||||
if setting in ("TEST",):
|
|
||||||
continue
|
|
||||||
override = CONFIG.get(
|
|
||||||
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=default
|
|
||||||
)
|
|
||||||
if override is not default:
|
|
||||||
_database[setting] = override
|
|
||||||
DATABASES[f"replica_{replica}"] = _database
|
|
||||||
|
|
||||||
DATABASE_ROUTERS = (
|
DATABASE_ROUTERS = (
|
||||||
"authentik.tenants.db.FailoverRouter",
|
"authentik.tenants.db.FailoverRouter",
|
||||||
|
|||||||
Reference in New Issue
Block a user