Skip to content

Ingress Controller Guide

Orchestra routes three types of traffic through the ingress layer:

  1. Frontendapp.<domain> → static nginx bundle
  2. APIapi.<domain> → FastAPI server
  3. 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.

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 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).

Terminal window
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
helm install traefik traefik/traefik \
--namespace traefik \
--create-namespace \
--set service.type=LoadBalancer

Wait for an external IP:

Terminal window
kubectl get svc -n traefik traefik -w

Point your DNS wildcard record at that IP:

*.orchestra.example.edu A <EXTERNAL-IP>
ingress:
controller: traefik
className: traefik
tls:
enabled: true
clusterIssuer: letsencrypt-prod

When ingress.controller=traefik and oauth2Proxy.enabled=true, the chart creates up to four Traefik Middleware objects in the release namespace:

MiddlewarePurpose
orchestra-authForwardAuth — 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-headersStrips any incoming X-Auth-Request-* headers before ForwardAuth runs. Prevents clients from forging identity headers.
orchestra-auth-signinRedirect-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-corsCORS 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 → auth
traefik.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@kubernetescrd

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 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.

Terminal window
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.type=LoadBalancer

Wait for external IP, then create the DNS wildcard record as above.

ingress:
controller: nginx
className: nginx
tls:
enabled: true
clusterIssuer: letsencrypt-prod

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.

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: false

Your proxy must forward X-Auth-Request-Email: <user@domain> to the API. This is the only identity signal Orchestra uses.

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.

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/v1
kind: Certificate
metadata:
name: orchestra-wildcard
namespace: orchestra-system
spec:
secretName: orchestra-wildcard-tls
dnsNames:
- "*.orchestra.example.edu"
- "orchestra.example.edu" # include apex if needed
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer

The 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.

Configure Traefik’s default TLS store to use a pre-existing wildcard secret. Any Ingress without a tls block will use it automatically.

traefik-default-tls.yaml
apiVersion: traefik.io/v1alpha1
kind: TLSStore
metadata:
name: default
namespace: traefik
spec:
defaultCertificate:
secretName: orchestra-wildcard-tls

Then set ingress.tls.enabled=false in Orchestra’s values — Traefik provides TLS from its default store, so no per-Ingress tls block is needed.

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 session

The 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.