infra/ingress_factory: add auth = "app" mode for self-authed backends

Adds a fourth auth tier alongside required/public/none. "app" is
functionally identical to "none" — no Authentik middleware attached —
but the distinct name records intent at the call site: this backend
has its own user login (NextAuth, Django, OAuth, bearer-token API,
etc.) and Authentik would only break it.

Why the new tier: with only required/none, every "the app has its
own auth so drop Authentik" decision looked identical at the call
site to "this is an OAuth callback / webhook receiver / native-client
API". Future readers couldn't tell whether a stack was intentionally
unauthenticated or relying on backend auth. Now they can.

Migrates the 8 stacks flipped earlier this session (novelapp, immich,
linkwarden, tandoor, freshrss, affine, actualbudget, ebooks/audiobookshelf)
from "none" to "app". Confirmed no-op: `tg plan` on novelapp showed
"No changes" — same middleware chain, same live state.

The variable description and the .claude/CLAUDE.md Auth section now
spell out the anti-exposure rule: only pick "app" or "none" AFTER
verifying the app has its own user auth ("app") or the endpoint is
intentionally public ("none"). Default stays "required" so accidental
omission fails closed.

[ci skip]
This commit is contained in:
Viktor Barzin 2026-05-11 18:59:11 +00:00
parent dafd7a18bc
commit 459b00fa74
10 changed files with 73 additions and 37 deletions

View file

@ -156,10 +156,10 @@ resource "kubernetes_service" "actualbudget" {
module "ingress" {
source = "../../../modules/kubernetes/ingress_factory"
# auth = "none": Actual Budget enforces a server password + per-user login
# auth = "app": Actual Budget enforces a server password + per-user login
# on its own sync API. Authentik forward-auth was 302-ing the mobile/web
# sync clients; Actual's own auth gates users.
auth = "none"
auth = "app"
namespace = "actualbudget"
name = "budget-${var.name}"
tls_secret_name = var.tls_secret_name

View file

@ -359,10 +359,10 @@ resource "kubernetes_service" "affine" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
# auth = "none": AFFiNE has its own workspace auth + bearer-token API
# auth = "app": AFFiNE has its own workspace auth + bearer-token API
# used by desktop/mobile sync clients. Authentik forward-auth was 302-ing
# those API callers; AFFiNE's own auth gates users.
auth = "none"
auth = "app"
dns_type = "non-proxied"
namespace = kubernetes_namespace.affine.metadata[0].name
name = "affine"

View file

@ -662,10 +662,10 @@ resource "kubernetes_service" "audiobookshelf" {
module "audiobookshelf_ingress" {
source = "../../modules/kubernetes/ingress_factory"
# auth = "none": Audiobookshelf has its own user/password login + API
# auth = "app": Audiobookshelf has its own user/password login + API
# tokens used by the iOS/Android Audiobookshelf app. Authentik forward-auth
# was 302-ing the mobile clients; ABS's own auth gates users.
auth = "none"
auth = "app"
dns_type = "non-proxied"
namespace = kubernetes_namespace.ebooks.metadata[0].name
name = "audiobookshelf"

View file

@ -229,10 +229,10 @@ resource "kubernetes_service" "freshrss" {
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
# auth = "none": FreshRSS has built-in user login and exposes Fever +
# auth = "app": FreshRSS has built-in user login and exposes Fever +
# GReader APIs (/api/fever.php, /api/greader.php) used by mobile RSS
# readers like Reeder/FeedMe. Authentik forward-auth was 302-ing those.
auth = "none"
auth = "app"
dns_type = "proxied"
namespace = "freshrss"
name = "rss"

View file

@ -738,10 +738,10 @@ resource "kubernetes_service" "immich-machine-learning" {
module "ingress-immich" {
source = "../../modules/kubernetes/ingress_factory"
# auth = "none": Immich has its own user auth + bearer-token API. Authentik
# auth = "app": Immich has its own user auth + bearer-token API. Authentik
# forward-auth on `/api/*` was 302-ing the iOS/Android Immich app and any
# external API consumer. App-level auth is the gate now.
auth = "none"
auth = "app"
dns_type = "non-proxied"
namespace = kubernetes_namespace.immich.metadata[0].name
name = "immich"

View file

@ -229,10 +229,10 @@ resource "kubernetes_service" "linkwarden" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
# auth = "none": Linkwarden uses NextAuth (NEXTAUTH_SECRET/URL set above)
# auth = "app": Linkwarden uses NextAuth (NEXTAUTH_SECRET/URL set above)
# and exposes /api/* for its mobile clients. Authentik forward-auth would
# 302 those callers; app-level NextAuth gates users.
auth = "none"
auth = "app"
dns_type = "proxied"
namespace = kubernetes_namespace.linkwarden.metadata[0].name
name = "linkwarden"

View file

@ -224,11 +224,11 @@ resource "kubernetes_service" "novelapp" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
# auth = "none": novelapp handles its own auth via NextAuth + Google OAuth
# auth = "app": novelapp handles its own auth via NextAuth + Google OAuth
# (AUTH_URL/AUTH_SECRET/GOOGLE_CLIENT_{ID,SECRET} env vars above). Putting
# Authentik forward-auth in front double-gates the app and breaks iOS/Android
# webview clients that can't complete the Authentik 302/cookie dance.
auth = "none"
auth = "app"
dns_type = "non-proxied"
namespace = kubernetes_namespace.novelapp.metadata[0].name
name = "novelapp"

View file

@ -259,10 +259,10 @@ resource "kubernetes_service" "tandoor" {
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
# auth = "none": Tandoor uses Django auth (SECRET_KEY set above) and exposes
# auth = "app": Tandoor uses Django auth (SECRET_KEY set above) and exposes
# /api/* with token auth for its mobile clients. Authentik forward-auth was
# 302-ing those callers; Django session/token auth gates users.
auth = "none"
auth = "app"
dns_type = "proxied"
namespace = kubernetes_namespace.tandoor.metadata[0].name
name = "tandoor"