A proxy provider built by hand in `ak shell` never runs ProxyProvider.set_oauth_defaults() — the REST serializer does, the ORM does not — so the row looks fully configured while redirect_uris is empty (they are derived from external_host by _get_callback_url()), grant_types is [], and authorization_flow is NULL. Each one kills a guarded login in a different, unrelated-looking way, all before any credential is requested: authentik's own "Redirect URI Error" page, then a bodiless HTTP 400 from /application/o/authorize/ once the URIs are filled by hand, and a 500 NoneType.slug in the flow planner. abs-proxy (abs.franksdank.cloud) hit the first two on 2026-11-03. The script calls the vendor's own set_oauth_defaults() and points the authorization flow at default-provider-authorization-implicit-consent for every proxy provider, idempotently; a provider that already matches is untouched, so a deliberate deviation is never flattened. `--check` is the census (exit 1 on drift, writes nothing) for gate use. Applied live 2026-11-03: abs-proxy fixed, the other ten (REST-created) already compliant, census now reports drift: 0. This is authentik DB state and no deployable file carries it — a blueprint re-import or an ak-shell re-creation reverts it and the symptom returns, so the law and the failure signatures are recorded on the outpost page as provisioning rule 3.
96 lines
4.4 KiB
Bash
Executable File
96 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Census and repair the OAuth2 defaults on every authentik PROXY provider.
|
|
#
|
|
# A proxy provider built by hand in `ak shell` never runs
|
|
# ProxyProvider.set_oauth_defaults() — the REST serializer calls it, the ORM does
|
|
# not — so the row looks perfectly configured (external_host, internal_host,
|
|
# application bound, embedded-outpost claim present) while three OAuth facts are
|
|
# blank. Each one kills the guarded login in a different, unrelated-looking way,
|
|
# all BEFORE any credential is asked for:
|
|
#
|
|
# * redirect_uris empty -> authentik answers its own page:
|
|
# "Redirect URI Error — The request fails due to a missing, invalid, or
|
|
# mismatching redirection URI (redirect_uri)". Measured on abs-proxy
|
|
# (abs.franksdank.cloud) 2026-11-03.
|
|
# * grant_types empty -> the authorize endpoint refuses the outpost's
|
|
# authorization-code request with a bodiless HTTP 400 (Chromium renders
|
|
# "This page isn't working / HTTP ERROR 400"). Same provider, one step
|
|
# later, after the URIs were filled in by hand.
|
|
# * authorization_flow NULL -> the authorize view crashes building its flow
|
|
# planner: AttributeError 'NoneType' object has no attribute 'slug', HTTP
|
|
# 500. That is what authentik-grafana-sso-fix.sh exists for.
|
|
#
|
|
# So: a guard that shows ANY of those three pages is not an Anubis problem and
|
|
# not a Caddy problem — run this script before touching the route.
|
|
#
|
|
# Repair calls the VENDOR's own set_oauth_defaults() (grant_types = authz code +
|
|
# client credentials + password, confidential client, the stock scope mappings,
|
|
# redirect_uris derived from external_host by _get_callback_url) and points the
|
|
# authorization flow at default-provider-authorization-implicit-consent, the flow
|
|
# every working provider here uses. Providers that already match are untouched,
|
|
# so a deliberate per-provider deviation is never silently flattened.
|
|
#
|
|
# This is authentik DB state. It is NOT in any deployable file: a blueprint
|
|
# re-import, or an ak-shell re-creation of a provider, reverts these facts and
|
|
# the symptom returns verbatim. Re-run after any such rebuild. Idempotent.
|
|
#
|
|
# Usage: authentik-proxy-provider-defaults.sh [--check]
|
|
# no argument repair what drifts, print one line per provider
|
|
# --check census only, exit 1 if anything drifts (gate use)
|
|
# override host: AUTHENTIK_HOST=…
|
|
set -euo pipefail
|
|
HOST="${AUTHENTIK_HOST:-dankclaw}"
|
|
MODE="${1:-}"
|
|
case "$MODE" in
|
|
"" | --check) : ;;
|
|
*) echo "usage: $(basename "$0") [--check]" >&2; exit 2 ;;
|
|
esac
|
|
|
|
ssh "$HOST" 'podman exec -i authentik-server ak shell' <<PYEOF
|
|
from authentik.flows.models import Flow
|
|
from authentik.providers.proxy.models import ProxyProvider, _get_callback_url
|
|
|
|
CHECK = $( [ "$MODE" = "--check" ] && echo True || echo False )
|
|
FLOW = "default-provider-authorization-implicit-consent"
|
|
WANT_GRANTS = ["authorization_code", "client_credentials", "password"]
|
|
|
|
flow = Flow.objects.filter(slug=FLOW).first()
|
|
if not flow and not CHECK:
|
|
raise SystemExit("flow %s is missing; nothing was changed" % FLOW)
|
|
|
|
drift = 0
|
|
for p in ProxyProvider.objects.all().order_by("pk"):
|
|
want_uris = [u.url for u in _get_callback_url(p.external_host)]
|
|
have_uris = [u.url for u in p.redirect_uris]
|
|
bad = []
|
|
if [str(g) for g in p.grant_types] != WANT_GRANTS:
|
|
bad.append("grant_types=%s" % [str(g) for g in p.grant_types])
|
|
if have_uris != want_uris:
|
|
bad.append("redirect_uris=%s" % have_uris)
|
|
if p.authorization_flow_id is None:
|
|
bad.append("authorization_flow=None")
|
|
if not bad:
|
|
print("ok %s (ext %s)" % (p.name, p.external_host))
|
|
continue
|
|
drift += 1
|
|
if CHECK:
|
|
print("DRIFT %s: %s" % (p.name, "; ".join(bad)))
|
|
continue
|
|
print("fix %s: %s" % (p.name, "; ".join(bad)))
|
|
p.set_oauth_defaults()
|
|
if p.authorization_flow_id is None:
|
|
p.authorization_flow = flow
|
|
p.save()
|
|
after = ProxyProvider.objects.get(pk=p.pk)
|
|
print(" -> grants %s, %d redirect URIs, flow %s"
|
|
% ([str(g) for g in after.grant_types], len(after.redirect_uris),
|
|
after.authorization_flow and after.authorization_flow.slug))
|
|
|
|
print("providers checked, drift: %d" % drift)
|
|
raise SystemExit(1 if (CHECK and drift) else 0)
|
|
PYEOF
|
|
|
|
echo "Verify: walk the guarded vhost in a browser — Anubis solves, authentik"
|
|
echo "renders its login form (no Redirect URI Error, no HTTP 400), and the"
|
|
echo "callback lands back on the app."
|