Ingress Controller Guide
Orchestra routes three types of traffic through the ingress layer:
- Frontend —
app.<domain>→ static nginx bundle - API —
api.<domain>→ FastAPI server - Workshop sessions —
<session-name>.<domain>→ per-session containers (created dynamically by the operator)
All three require auth to be applied at the ingress, not inside the application. The chart automates auth wiring for Traefik and nginx-ingress. Choose one before installing.
Why GKE native ingress is not supported
Section titled “Why GKE native ingress is not supported”GKE’s built-in ingress controller (ingressClassName: gce) provisions a new
Google Cloud Load Balancer for every Ingress resource. Workshop sessions
create one Ingress per active session — at scale that means dozens of LBs,
minutes of provisioning time per launch, and significant cost.
Use Traefik or nginx-ingress on GKE. Both run as in-cluster pods and update their routing tables in seconds when a new Ingress appears.
Traefik (recommended)
Section titled “Traefik (recommended)”Traefik is the primary supported controller. The chart creates Traefik
Middleware CRDs that implement ForwardAuth, which delegates auth decisions to
oauth2-proxy before forwarding a request upstream. How they are applied depends
on oauth2Proxy.fullProxy (see What the chart creates).
Install Traefik
Section titled “Install Traefik”helm repo add traefik https://helm.traefik.io/traefikhelm repo updatehelm install traefik traefik/traefik \ --namespace traefik \ --create-namespace \ --set service.type=LoadBalancerWait for an external IP:
kubectl get svc -n traefik traefik -wPoint your DNS wildcard record at that IP:
*.orchestra.example.edu A <EXTERNAL-IP>Chart values
Section titled “Chart values”ingress: controller: traefik className: traefik tls: enabled: true clusterIssuer: letsencrypt-prodWhat the chart creates
Section titled “What the chart creates”When ingress.controller=traefik and oauth2Proxy.enabled=true, the chart
creates up to four Traefik Middleware objects in the release namespace:
| Middleware | Purpose |
|---|---|
orchestra-auth | ForwardAuth — sends every request to oauth2-proxy for validation. On success, injects X-Auth-Request-User, X-Auth-Request-Email, and X-Auth-Request-Access-Token. |
orchestra-auth-headers | Strips any incoming X-Auth-Request-* headers before ForwardAuth runs. Prevents clients from forging identity headers. |
orchestra-auth-signin | Redirect-on-401 — on a 401-403 from ForwardAuth, sends the browser to /oauth2/start. Only created when fullProxy=false. Traefik v3’s errors middleware keeps the original status code, so this is a 401 + Location header rather than a true 302 — use fullProxy=true for a seamless redirect. |
orchestra-api-cors | CORS for the API ingress. Intercepts OPTIONS preflight and returns 200 with CORS headers without hitting ForwardAuth (browsers strip cookies from preflight, so ForwardAuth would always 401). FastAPI’s own CORS is disabled in production to avoid duplicate headers. |
The middlewares applied to each ingress differ, and the auth annotations are
only added when fullProxy=false (in full-proxy mode the frontend ingress
routes straight to oauth2-proxy, so no auth middleware is needed there):
# Frontend ingress (fullProxy=false): signin → header-strip → authtraefik.ingress.kubernetes.io/router.middlewares: >- orchestra-system-orchestra-auth-signin@kubernetescrd, orchestra-system-orchestra-auth-headers@kubernetescrd, orchestra-system-orchestra-auth@kubernetescrd# API ingress: cors → header-strip → auth (no auth-signin)# The frontend JS handles the redirect when the API returns 401.traefik.ingress.kubernetes.io/router.middlewares: >- orchestra-system-orchestra-api-cors@kubernetescrd, orchestra-system-orchestra-auth-headers@kubernetescrd, orchestra-system-orchestra-auth@kubernetescrdPer-session workshop Ingress resources
Section titled “Per-session workshop Ingress resources”The operator creates an Ingress for each active workshop session. Those
Ingresses need the same middleware annotations to enforce auth on session URLs.
Configure the operator to apply the annotation automatically (future feature — currently requires a manual annotation in the WorkshopTemplate if sessions should be auth-protected at the ingress level; the API enforces ownership regardless).
nginx-ingress
Section titled “nginx-ingress”nginx-ingress implements auth via subrequest: nginx calls oauth2-proxy’s
/oauth2/auth endpoint before forwarding each request. On success, oauth2-proxy
responds with X-Auth-Request-Email in headers, which nginx copies onto the
upstream request.
Install nginx-ingress
Section titled “Install nginx-ingress”helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginxhelm repo updatehelm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx \ --create-namespace \ --set controller.service.type=LoadBalancerWait for external IP, then create the DNS wildcard record as above.
Chart values
Section titled “Chart values”ingress: controller: nginx className: nginx tls: enabled: true clusterIssuer: letsencrypt-prodWhat the chart creates
Section titled “What the chart creates”No custom CRDs are needed. The chart adds these annotations to every Ingress:
nginx.ingress.kubernetes.io/auth-url: "http://<release>-oauth2-proxy.<ns>.svc.cluster.local:4180/oauth2/auth"nginx.ingress.kubernetes.io/auth-signin: "https://app.<domain>/oauth2/start?rd=$escaped_request_uri"nginx.ingress.kubernetes.io/auth-response-headers: "X-Auth-Request-Email,X-Auth-Request-User,X-Auth-Request-Access-Token"nginx-ingress automatically strips the same headers from client requests before the auth subrequest runs, so identity spoofing is not possible.
Custom controller
Section titled “Custom controller”Use controller: custom when you have your own ingress setup (Cloudflare
Access, Istio, corporate proxy, etc.). No auth annotations are added by the
chart. Populate ingress.annotations with whatever your controller needs and
set oauth2Proxy.enabled=false.
ingress: controller: custom className: nginx # or whatever your controller uses annotations: my-proxy.example.com/auth-enabled: "true"oauth2Proxy: enabled: falseYour proxy must forward X-Auth-Request-Email: <user@domain> to the API.
This is the only identity signal Orchestra uses.
TLS and wildcard certificates
Section titled “TLS and wildcard certificates”Each workshop session is served at a unique subdomain
(<session>.orchestra.example.edu). A standard cert-manager Certificate
covers only the hosts it lists, so you need a wildcard cert.
Option A — cert-manager with DNS-01 (recommended)
Section titled “Option A — cert-manager with DNS-01 (recommended)”Create a Certificate resource using a DNS-01 solver. DNS-01 is required for
wildcard SAN entries (HTTP-01 cannot validate *.domain).
apiVersion: cert-manager.io/v1kind: Certificatemetadata: name: orchestra-wildcard namespace: orchestra-systemspec: secretName: orchestra-wildcard-tls dnsNames: - "*.orchestra.example.edu" - "orchestra.example.edu" # include apex if needed issuerRef: name: letsencrypt-prod kind: ClusterIssuerThe ClusterIssuer solver block depends on your DNS provider — your DNS does
not need to be at the same provider as your cluster. The GCP Autopilot
guide has tabbed instructions for Cloudflare,
Google Cloud DNS, and a pointer to other supported providers.
Option B — Default TLS store (Traefik)
Section titled “Option B — Default TLS store (Traefik)”Configure Traefik’s default TLS store to use a pre-existing wildcard secret.
Any Ingress without a tls block will use it automatically.
apiVersion: traefik.io/v1alpha1kind: TLSStoremetadata: name: default namespace: traefikspec: defaultCertificate: secretName: orchestra-wildcard-tlsThen set ingress.tls.enabled=false in Orchestra’s values — Traefik provides
TLS from its default store, so no per-Ingress tls block is needed.
Auth flow diagram
Section titled “Auth flow diagram”Browser → Traefik/nginx │ ├─ strip X-Auth-Request-* from client headers ├─ subrequest → oauth2-proxy /oauth2/auth │ ├─ valid cookie → 200 + X-Auth-Request-Email │ └─ missing/expired → 401 → redirect to /oauth2/start │ ├─ copy X-Auth-Request-Email onto request └─ forward to API / frontend / workshop sessionThe Orchestra API trusts X-Auth-Request-Email unconditionally — it assumes
the ingress layer has already validated it. Never expose the API directly to
the internet without the auth proxy in front.