Protect Ingress with oauth2-proxy (K3s + ingress-nginx)

Placeholders

Install K3s (without Traefik)

curl -sfL https://get.k3s.io | sh -s - server --disable traefik

Install ingress-nginx with snippet annotations enabled

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.allowSnippetAnnotations=true \
  --set controller.config.annotations-risk-level=Critical

Why: allowSnippetAnnotations=true + annotations-risk-level=Critical are required to use nginx.ingress.kubernetes.io/configuration-snippet and the Lua block below.

Deploy oauth2-proxy (generic OIDC)

kubectl create namespace <AUTH_NAMESPACE>

kubectl -n <AUTH_NAMESPACE> create secret generic oauth2-proxy-secret \
  --from-literal=client-id='<OAUTH_CLIENT_ID>' \
  --from-literal=client-secret='<OAUTH_CLIENT_SECRET>' \
  --from-literal=cookie-secret='<BASE64_32_BYTE_SECRET>'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
  namespace: <AUTH_NAMESPACE>
spec:
  replicas: 1
  selector:
    matchLabels: { app: oauth2-proxy }
  template:
    metadata:
      labels: { app: oauth2-proxy }
    spec:
      containers:
        - name: oauth2-proxy
          image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
          args:
            - --provider=oidc
            - --oidc-issuer-url=<TENANT_OR_ISSUER_URL>
            - --redirect-url=https://<DOMAIN>/oauth2/callback
            - --email-domain=*
            - --upstream=file:/dev/null
            - --cookie-secure=true
            - --cookie-domain=<DOMAIN>
            - --cookie-samesite=lax
            - --set-xauthrequest=true
            - --pass-authorization-header=true
            - --pass-access-token=true
            - --http-address=0.0.0.0:4180
          env:
            - name: OAUTH2_PROXY_CLIENT_ID
              valueFrom: { secretKeyRef: { name: oauth2-proxy-secret, key: client-id } }
            - name: OAUTH2_PROXY_CLIENT_SECRET
              valueFrom: { secretKeyRef: { name: oauth2-proxy-secret, key: client-secret } }
            - name: OAUTH2_PROXY_COOKIE_SECRET
              valueFrom: { secretKeyRef: { name: oauth2-proxy-secret, key: cookie-secret } }
          ports:
            - containerPort: 4180
---
apiVersion: v1
kind: Service
metadata:
  name: oauth2-proxy
  namespace: <AUTH_NAMESPACE>
spec:
  selector: { app: oauth2-proxy }
  ports:
    - name: http
      port: 80
      targetPort: 4180

Expose /oauth2/* on the same host

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: oauth2-proxy
  namespace: <AUTH_NAMESPACE>
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["<DOMAIN>"]
      secretName: <TLS_SECRET_NAME>
  rules:
    - host: <DOMAIN>
      http:
        paths:
          - path: /oauth2
            pathType: Prefix
            backend:
              service:
                name: oauth2-proxy
                port:
                  number: 80

Protect the app Ingress with oauth2-proxy

Uses auth-request annotations and a Lua snippet to copy an upstream cookie to the client.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-frontend
  namespace: <APP_NAMESPACE>
  annotations:
    nginx.ingress.kubernetes.io/auth-response-headers: Authorization
    nginx.ingress.kubernetes.io/auth-signin: https://$host/oauth2/start?rd=$escaped_request_uri
    nginx.ingress.kubernetes.io/auth-url: https://$host/oauth2/auth
    nginx.ingress.kubernetes.io/configuration-snippet: |
      auth_request_set $name_upstream_1 $upstream_cookie_name_1;

      access_by_lua_block {
        if ngx.var.name_upstream_1 ~= "" then
          ngx.header["Set-Cookie"] = "name_1=" .. ngx.var.name_upstream_1 .. ngx.var.auth_cookie:match("(; .*)")
        end
      }
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["<DOMAIN>"]
      secretName: <TLS_SECRET_NAME>
  rules:
    - host: <DOMAIN>
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: <APP_SERVICE_NAME>
                port:
                  number: <APP_SERVICE_PORT>

Verify

# Ingress and oauth2-proxy
kubectl -n <APP_NAMESPACE> get ingress app-frontend -o wide
kubectl -n <AUTH_NAMESPACE> get deploy,svc,ingress

# Test locally (replace with your domain)
curl -I https://<DOMAIN>/ | sed -n '1,20p'
curl -I https://<DOMAIN>/oauth2/auth | sed -n '1,20p'

Common pitfalls