Ask Authelia for sign-in state on the public sign-in host.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 38s
Account journey acceptance / journeys (push) Successful in 9s

Authelia rejects the cluster address. Keep the connection inside the cluster and name login.coulomb.social, which matches the session cookie domain.

Assistant: grok
Assistant-Session: 01a0d25d-d358-7e13-b84a-d007fbb7e34f
This commit is contained in:
tegwick 2026-09-27 00:26:50 +02:00
parent 560cdeed46
commit 104405ccbf
3 changed files with 76 additions and 16 deletions

View file

@ -18,6 +18,7 @@ from user_engine.oidc import cookie_value
_IDENTITY_NAME = re.compile(r"^[A-Za-z0-9._@+-]{1,200}$")
_SESSION_TOKEN = re.compile(r"^[A-Za-z0-9._~+/=-]{1,4096}$")
_PUBLIC_HOST = re.compile(r"[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+")
_CLUSTER_HOST = ".svc.cluster.local"
@ -51,6 +52,13 @@ def validate_identity_state_url(url: str) -> str:
return url
def validate_identity_state_host(host: str) -> str:
"""Accept the public sign-in host Authelia uses for its session cookie."""
if not _PUBLIC_HOST.fullmatch(host) or host.endswith(_CLUSTER_HOST):
raise ValueError("identity state host must be the public sign-in hostname")
return host
def authelia_session_token(cookie_header: str) -> str | None:
value = cookie_value(cookie_header, "authelia_session")
if value is None or _SESSION_TOKEN.fullmatch(value) is None:
@ -78,15 +86,16 @@ class _RefuseRedirects(HTTPRedirectHandler):
raise URLError("identity state endpoint must not redirect")
def _read_state(url: str, token: str, timeout: float) -> bytes:
request = Request(
url,
headers={
"Accept": "application/json",
"Cookie": f"authelia_session={token}",
},
method="GET",
)
def _read_state(url: str, token: str, timeout: float, *, host: str | None = None) -> bytes:
headers = {
"Accept": "application/json",
"Cookie": f"authelia_session={token}",
}
# Authelia rejects the cluster DNS name. The public sign-in host matches
# the session cookie domain, while the connection stays on the cluster URL.
if host:
headers["Host"] = host
request = Request(url, headers=headers, method="GET")
opener = build_opener(_RefuseRedirects)
with opener.open(request, timeout=timeout) as response:
return response.read(8192)
@ -95,10 +104,18 @@ def _read_state(url: str, token: str, timeout: float) -> bytes:
class AutheliaIdentityState:
"""Confirm the username on one Authelia session cookie."""
def __init__(self, state_url: str, *, timeout: float = 2.0, reader=_read_state) -> None:
def __init__(
self,
state_url: str,
*,
host: str | None = None,
timeout: float = 2.0,
reader=_read_state,
) -> None:
if timeout <= 0:
raise ValueError("identity state timeout must be positive")
self.state_url = validate_identity_state_url(state_url)
self.host = validate_identity_state_host(host) if host else None
self.timeout = timeout
self._reader = reader
@ -107,7 +124,12 @@ class AutheliaIdentityState:
if token is None:
return None
try:
raw = self._reader(self.state_url, token, self.timeout)
raw = self._reader(
self.state_url,
token,
self.timeout,
**({"host": self.host} if self.host else {}),
)
payload = json.loads(raw.decode("utf-8"))
except (HTTPError, URLError, TimeoutError, OSError, UnicodeError, json.JSONDecodeError, ValueError):
return None

View file

@ -120,7 +120,10 @@ def create_application() -> PortalApplication:
os.environ.get("USER_ENGINE_REGISTRATION_RATE_WINDOW_SECONDS", "60")
),
identity_lookup=(
AutheliaIdentityState(os.environ["USER_ENGINE_IDENTITY_STATE_URL"])
AutheliaIdentityState(
os.environ["USER_ENGINE_IDENTITY_STATE_URL"],
host=os.environ.get("USER_ENGINE_IDENTITY_STATE_HOST") or None,
)
if os.environ.get("USER_ENGINE_IDENTITY_STATE_URL")
else None
),