feat(auth): add Authentik JWT authentication middleware #521
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
thwap/thwap-pagesd!521
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/auth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Add Authentik JWT authentication middleware and owner-based authorization to the daemon's API.
internal/authpackage: RS256 signature verification against Authentik's JWKS (fetched and cached via keyfunc), optionaliss/audclaim checks,exprequired.Middleware.Require()rejects requests without a validAuthorization: Bearertoken with 401.preferred_usernameonly; members ofauthentik.admin_groupmay act on any owner. Cross-owner access returns 403.authentik.{issuer,audience,jwks_url,admin_group}section, injected asTHWAP_PAGESD_AUTHENTIK_*env vars and added to the Helm chart values. Auth is disabled (endpoints stay open) whenjwks_urlis unset, preserving existing behavior.golang-jwt/jwt/v5,MicahParks/keyfunc/v3.Why
Phase 9 task #118 — production readiness: the registration API is now secured against Authentik-issued tokens.
Testing
go build ./...go vet ./...go test -race -count=1 ./...— full suite passes (new: auth unit tests + handler-level auth tests)gofmt -l ./cmd ./internal ./pkgcleangolangci-lint run— 0 issueshelm lint+helm templaterender authentik env keysBreaking Changes
None when auth is unconfigured. When
authentik.jwks_urlis set, the site endpoints require a valid bearer token.Notes
Closes #116
Closes #117
Closes #118
Blocking Issues
cmd/thwap-pagesd/main.go:144— auth is enabled whenJWKSURL != "" || Issuer != "". If an operator sets onlyauthentik.issuer(documented in the README as an optional "empty = skip check" value, withjwks_urlbeing the enable switch),keyfunc.NewDefaultCtx(ctx, []string{""})fails at startup withfailed to parse given URL "": parse "": empty url, so the daemon refuses to boot. This contradicts the README's "Auth is disabled (endpoints stay open) whenjwks_urlis unset". Gate the middleware onJWKSURL != ""only;issuer/audience/admin_groupare meaningless without a JWKS source anyway. Verified with a small standalone test against the vendored keyfunc.Suggestions
internal/auth/auth.go:88—jwt.WithLeeway(0)rejects valid tokens near expiry under clock skew. Consider a small leeway (e.g. 30s). Filed as #523.internal/auth/auth.go:64— failed auth (missing/invalid token) returns 401 with no log line, so rejected attempts are invisible. Consider logging via the centralized logger. Filed as #522.internal/httpapi/sites.go:109—deploymentsresolves the owner via aStatus()call, then callsDeployments()again: two downstream calls per request plus a small TOCTOU window. Filed as #525.cmd/thwap-pagesd/main.go— the auth middleware'sClose()is never called; the keyfunc JWKS refresher goroutine leaks for the process lifetime. Filed as #524 (ties into graceful shutdown #120).Praise
bearerTokencases, and handler-level tests exercising 401/403/200 for owner, cross-owner, and admin paths.preferred_usernamemaps to 403 for non-admins, andWithValidMethods(RS256)plusWithExpirationRequiredtighten the default parser appropriately.configmap.yamlrenders theconfigsection generically, so the newauthentikblock becomesTHWAP_PAGESD_AUTHENTIK_*env vars without template changes; the config env test confirms the mapping.Addressed the blocking review item.
Blocking fix: authentication is now gated on
authentik.jwks_urlalone via a newauthEnabled()helper (cmd/thwap-pagesd/main.go). Setting onlyauthentik.issuer(or audience/admin_group) no longer enables the middleware, so an operator can't trigger the empty-URL JWKS boot failure. Added a regression test covering jwks-only, all-set, issuer-only, audience-only, and unset configurations.Suggestions #522–#525 were filed as issues by the auditor and are tracked there — they are out of scope for this fix (the leeway, logging, and Close() items tie into follow-up work).
Verification:
go build ./...,go vet ./...,go test -race -count=1 ./...(all 12 packages pass),gofmt -lclean,golangci-lint run0 issues, pre-commit hooks pass. Re-requesting review.Re-review
Blocking issue resolved:
cmd/thwap-pagesd/main.gonow gates middleware construction onauthEnabled(cfg), which returnscfg.Authentik.JWKSURL != ""only. Setting justissuer/audience/admin_groupno longer enables auth and no longer crashes startup with the empty-URL parse error. The rationale is documented in a comment, andTestAuthEnabledcovers jwks-only, all-set, issuer-only, audience-only, and unset configs.Verified locally:
go build ./...,go vet ./..., andgo test -race -count=1 ./...all pass, including the newcmd/thwap-pagesdtests.Non-blocking suggestions remain tracked as #522, #523, #524, #525. No new findings from the fix commit.
Approving.