fix(authentik): long-lived social-login sessions + shield auth from CrowdSec lockout
All checks were successful
ci/woodpecker/push/default Pipeline was successful
All checks were successful
ci/woodpecker/push/default Pipeline was successful
Viktor's passkeys all vanished and he was suddenly being asked to log in
multiple times a day instead of ~monthly. Root cause: on 2026-06-18 an ad-hoc
tripit passkey E2E test (run from the devvm as akadmin via python-httpx) cleaned
up "the demo user's" passkeys with GET /core/users/?search={demo} then DELETE
each device of users[0] — but the fuzzy search returned the REAL account, so it
wiped all 6 real passkeys. Losing passkeys forced fallback to Google login, and
the social-login stage (default-source-authentication-login) had the provider
default session_duration=seconds=0, which falls back to UNAUTHENTICATED_AGE=2h —
hence the constant re-logins. (Password + passkey logins were already weeks=4.)
Changes:
- authentik: adopt default-source-authentication-login into Terraform (import)
and pin session_duration=weeks=4, so Google/GitHub/Facebook logins last as long
as password/passkey. Immediate relief without re-enrolling.
- authentik: document the provider-schema gotcha — authentik_stage_identification
exposes no webauthn_stage / enable_remember_me attribute, so they must NOT be in
ignore_changes (commit 4e882989 removed them for this reason; re-adding breaks
every apply). The passkey break was purely the missing device records, not drift.
- edge (rybbit): shield auth so a CrowdSec hit can never wall a user out of login —
carve authentik.viktorbarzin.me + public-auth out of the zone WAF block rule,
make the LAPI->edge sync ban-only (stop downgrading captcha to a hard block),
and set exclude_crowdsec on the Authentik UI ingress (auth keeps rate-limiting).
- docs: record the session-duration change, the edge enforcement + auth carve-out
(previously undocumented), and the pre-existing broken crowdsec-cf-sync CronJob
(CF cursor pagination 400 + ~31k IPs vs list capacity -> edge list inert).
Passkey re-enrollment is a manual user action (devices are gone from the DB);
nothing auto-re-deletes them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
600f1f933c
commit
46166c63b2
6 changed files with 119 additions and 20 deletions
|
|
@ -109,16 +109,25 @@ resource "cloudflare_ruleset" "crowdsec" {
|
|||
# must exist before this ruleset is created/updated.
|
||||
depends_on = [cloudflare_list.crowdsec_ban]
|
||||
|
||||
# CrowdSec ban — block every IP in the single edge list. The sync writes BOTH
|
||||
# ban and captcha decisions into crowdsec_ban (captcha downgraded to block at
|
||||
# the edge) because the CF account allows only ONE Rules List.
|
||||
# CrowdSec ban — block every IP in the single edge list, EXCEPT on the
|
||||
# Authentik auth hosts. The sync (lapi_kv_sync.py) now writes ONLY "ban"
|
||||
# decisions into crowdsec_ban — captcha is no longer downgraded to a hard
|
||||
# block. The auth-host carve-out guarantees a CrowdSec hit can never wall a
|
||||
# user out of the login / WebAuthn flow they authenticate through: without it,
|
||||
# a false-positive ban would 403 the passkey ceremony + session-refresh XHRs
|
||||
# on every proxied host, auth included. 2026-06-20.
|
||||
rules {
|
||||
action = "block"
|
||||
expression = "(ip.src in $crowdsec_ban)"
|
||||
description = "CrowdSec: block banned IPs"
|
||||
expression = "(ip.src in $crowdsec_ban) and not (http.host in {\"authentik.viktorbarzin.me\" \"public-auth.viktorbarzin.me\"})"
|
||||
description = "CrowdSec: block banned IPs (auth hosts carved out)"
|
||||
enabled = true
|
||||
}
|
||||
# Pre-existing rule, imported and preserved verbatim (currently disabled).
|
||||
# NOTE: Cloudflare auto-attaches logging{enabled=true} to skip rules. It must
|
||||
# be declared here to match live, otherwise editing the OTHER rule re-sends
|
||||
# this one too and the v4 provider errors "Provider produced inconsistent
|
||||
# result after apply: .rules[1].logging block count changed from 0 to 1"
|
||||
# (hit 2026-06-20 when adding the auth-host carve-out above).
|
||||
rules {
|
||||
action = "skip"
|
||||
expression = "(http.host contains \"viktorbarzin.me\")"
|
||||
|
|
@ -129,6 +138,9 @@ resource "cloudflare_ruleset" "crowdsec" {
|
|||
products = ["uaBlock", "bic", "hot", "securityLevel", "rateLimit", "waf", "zoneLockdown"]
|
||||
ruleset = "current"
|
||||
}
|
||||
logging {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue