setPublicOriginHeaders (key-cape@3b0446e) annotated only the token
exchange with X-Forwarded-Proto/Host so Authelia can derive the public
issuer in a split-horizon deployment. idTokenVerifier's separate
provider-metadata/JWKS fetch never got the same treatment, so an
in-cluster discovery request reached Authelia unmarked and Authelia
4.38 failed closed on it ("invalid X-Forwarded-Proto header value
'http'"), breaking every login regardless of client.
Extracted setPublicOriginHeaders to a package-level function and gave
idTokenVerifier the same resolved public base URL so its discovery/JWKS
requests carry the same headers as the token exchange.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 169987@bnt-lap001
Assistant-Session: 322ef1ef-9048-4021-8570-b6d6f6347999
4.9 KiB
| id | type | title | domain | repo | status | owner | created | updated | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|
| ADHOC-2026-09-27 | workplan | Fresh MFA no longer backdated to a stale kc_login cookie (INFD-IN-0005) | infotech | key-cape | finished | claude | 2026-09-27 | 2026-09-27 | 5217dd06-73cb-50f8-ba64-528e421b29de |
Stop completeAuthorization from reusing a stale prior session's IssuedAt after real MFA
id: ADHOC-2026-09-27-T01
status: done
priority: high
state_hub_task_id: "ce98e4af-cfaa-5620-8f21-15ade18ee9e8"
informed-decision intake INFD-IN-0005 (open, origin_ref: SECRETS-WP-0010):
platform-root's approval of infd-20260927-b01 was refused by flex-auth's
compact_sitting policy with policy_denied, despite platform-root having just
completed a fresh key-cape MFA sign-in. flex-auth's 900-second review-freshness
check reads assurance.at, which key-cape derives from the PKCE session's
AuthTime.
Root cause in src/internal/server/oidc/authorize.go,
AuthorizeHandler.completeAuthorization: whenever a kc_login cookie already
existed for the same username, its IssuedAt was unconditionally reused as
authTime — even on the two call sites that had just called
MFA.ValidateMFAToken successfully in this exact request (the callback's
mfaToken != "" branch and serveMFASubmission). A user who had an older
browser session and then stepped up with a fresh MFA challenge got the old
session's timestamp stamped onto the new, just-proven authentication —
understating freshness for every downstream binding-grade check, exactly as
INFD-IN-0005 describes ("completeAuthorization currently copies a prior
timestamp... only after actual reauthentication").
The reuse itself is correct when MFA was not just performed in this
request (decision.MFAVerified carried forward from an existing AAL2 session,
via domain.DecideAssurance) — reporting mint time there would overstate
freshness (TestAssuranceReportsAuthenticationTimeNotMintTime). The two cases
were conflated under a single mfaVerified bool.
Fix: added a freshMFA bool parameter to completeAuthorization, true only
at the two call sites that just validated an MFA token in this request, false
at the carried-forward-assurance call site. authTime now falls back to the
prior session's IssuedAt only when !freshMFA.
Added TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie,
which seeds a 4-hour-old kc_login cookie for the same user, then submits a
valid MFA token and asserts the resulting AuthTime is within a minute of now.
Confirmed it fails against the pre-fix code (AuthTime ~4h old) and passes
after. Full suite (go test ./...) green, no other test needed adjustment —
the carried-forward-assurance case (TestPolicy_..., TestLogout_...,
assurance_time_test.go) is unaffected since none of it exercises a fresh MFA
submission with a pre-existing cookie for the same user.
Does not touch flex-auth (its 900s window is correct; it was consuming a bad
timestamp) or informed-decision (correctly refuses to interpret the PDP's
denial). INFD-IN-0005 can close once informed-decision confirms platform-root
can approve after a fresh sign-in against this build.
Discovery/JWKS fetch was missing the split-horizon origin headers
id: ADHOC-2026-09-27-T02
status: done
priority: high
Rolling out T01 (image digest sha256:248e449a...) broke every KeyCape login,
including unrelated clients (user-engine-portal), with auth_failure /
provider_metadata_unavailable at /api/oidc/token on the KeyCape side and
Authelia logging invalid X-Forwarded-Proto header value 'http' on
GET /.well-known/openid-configuration from KeyCape's pod IP.
Root cause, unrelated to T01's change: AutheliaAdapter.setPublicOriginHeaders
(the NK-WP-0041-T02/path-B fix, key-cape@3b0446e) annotates only the token
exchange (newTokenRequest) with X-Forwarded-Proto/X-Forwarded-Host so
Authelia can derive the public issuer for a split-horizon (in-cluster HTTP,
public HTTPS) deployment. idTokenVerifier.get in verify.go — used for the
provider-metadata and JWKS fetches that verify every upstream ID token — never
got the same treatment; it built a bare request with only an Accept header.
Authelia 4.38 fails closed on the resulting unmarked in-cluster request. This
gap predates today's session (present since 3b0446e); it surfaced now because
this was the first login attempted since that image went live.
Fix: extracted setPublicOriginHeaders from an AutheliaAdapter method to a
package-level function taking the target public base URL explicitly, and gave
idTokenVerifier its own resolved publicBaseURL (same
BrowserBaseURL-else-BaseURL fallback as the adapter) so get() applies
the same headers. Added TestDiscoveryFetch_CarriesPublicOriginHeaders
(confirmed it fails without the fix, passes with it). Full suite green.
New image key-cape@<pending> still needs a rollout to sso-mfa/k8s/keycape/deployment.yaml
before platform-root can sign in again.