feat(tripit): #96 cutover — /api self-authenticates (remove forward-auth, add strip-auth-headers)
All checks were successful
ci/woodpecker/push/default Pipeline was successful

ADR-0028 #96 (website half): /api drops Authentik forward-auth so the browser can carry a TripIt session cookie (the outpost 302'd cookie-only requests). The app self-authenticates (TripIt-session-first in get_current_user); no session -> 401 -> SPA landing. strip-auth-headers is REQUIRED now: with forward-auth gone, the hybrid forward-auth arm would otherwise trust a client-injected X-authentik-email — stripping inbound X-authentik-* closes that. /metrics split into its own still-gated ingress. Shell keeps Authentik bearers on tripit-api.* until #94; full AUTH_MODE collapse follows then. Verified live: no-session->401, valid TripIt cookie->200, injected header->401, Shell->200.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-19 08:27:39 +00:00
parent 8559c4574a
commit fbf6f11038

View file

@ -798,28 +798,56 @@ module "ingress" {
] ]
} }
# Authenticated API + metrics on the SAME host stay behind Authentik forward-auth # /api is served by TripIt's OWN authentication now (ADR-0028 #96 cutover):
# (tripit ADR-0020). The SPA above is public, but every /api route trusts the # Authentik forward-auth is REMOVED so the website can carry a TripIt session
# X-authentik-email the outpost injects (AUTH_MODE=hybrid) so /api MUST remain # cookie (the outpost would 302 a cookie-only request away). The app
# gated; this is the security boundary. /metrics is gated too (it is scraped # self-authenticates get_current_user accepts a TripIt session FIRST; a request
# in-cluster via the Service and never needs to be public). These prefixes are # with no session 401s and the SPA shows the landing page. strip-auth-headers is
# longer than "/" so Traefik routes them here; the auth=none carve-outs below # REQUIRED here: with forward-auth gone, AUTH_MODE=hybrid's forward-auth arm would
# (calendar, emails/confirm, planner/slack) are longer still and keep winning for # otherwise trust a client-injected X-authentik-email stripping inbound
# their own sub-paths. # X-authentik-* closes that header-injection bypass. (The Shell keeps using
# Authentik bearers on the separate tripit-api.* host until #94; the full
# AUTH_MODE collapse to TripIt-session-only follows then.) anti_ai_scraping=false
# so Anubis PoW doesn't break programmatic API calls. The auth=none carve-outs
# below (calendar, emails/confirm, planner/slack) are longer prefixes and keep
# winning for their own sub-paths.
module "ingress_app_api" { module "ingress_app_api" {
source = "../../modules/kubernetes/ingress_factory" source = "../../modules/kubernetes/ingress_factory"
auth = "required" # auth = "none": /api self-authenticates via TripIt's own session (ADR-0028 #96);
dns_type = "none" # main module.ingress owns the DNS record for this host # strip-auth-headers (below) blocks any client-injected X-authentik-* so the
namespace = kubernetes_namespace.tripit.metadata[0].name # hybrid forward-auth arm cannot be tricked. No session => 401 => SPA landing.
name = "tripit-app-api" auth = "none"
service_name = "tripit" anti_ai_scraping = false
full_host = "tripit.viktorbarzin.me" dns_type = "none" # main module.ingress owns the DNS record for this host
ingress_path = ["/api", "/metrics"] namespace = kubernetes_namespace.tripit.metadata[0].name
port = 8080 name = "tripit-app-api"
tls_secret_name = var.tls_secret_name service_name = "tripit"
full_host = "tripit.viktorbarzin.me"
ingress_path = ["/api"]
port = 8080
tls_secret_name = var.tls_secret_name
# Same photo-thumbnail burst profile as before keep the dedicated limiter. # Same photo-thumbnail burst profile as before keep the dedicated limiter.
skip_default_rate_limit = true skip_default_rate_limit = true
extra_middlewares = ["traefik-tripit-rate-limit@kubernetescrd"] extra_middlewares = [
"traefik-strip-auth-headers@kubernetescrd",
"traefik-tripit-rate-limit@kubernetescrd",
]
}
# /metrics stays gated behind forward-auth (it is scraped in-cluster via the
# Service and never needs to be public); split out of the /api ingress by the
# #96 cutover, which made /api self-authenticated.
module "ingress_metrics" {
source = "../../modules/kubernetes/ingress_factory"
auth = "required"
dns_type = "none"
namespace = kubernetes_namespace.tripit.metadata[0].name
name = "tripit-metrics"
service_name = "tripit"
full_host = "tripit.viktorbarzin.me"
ingress_path = ["/metrics"]
port = 8080
tls_secret_name = var.tls_secret_name
} }
# Calendar feed carve-out for the same host: path /api/calendar served by the # Calendar feed carve-out for the same host: path /api/calendar served by the