Protect Ingress with oauth2-proxy (K3s + ingress-nginx)
Placeholders
<DOMAIN>: FQDN for the app and/oauth2/*<TLS_SECRET_NAME>: TLS secret for<DOMAIN><APP_NAMESPACE>,<AUTH_NAMESPACE>: Namespace names<APP_SERVICE_NAME>,<APP_SERVICE_PORT>: Backend service<TENANT_OR_ISSUER_URL>: OIDC issuer (e.g., Azure AD, Keycloak)<OAUTH_CLIENT_ID>,<OAUTH_CLIENT_SECRET>,<BASE64_32_BYTE_SECRET>: IdP values
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
- TLS:
<TLS_SECRET_NAME>must be valid for<DOMAIN>. - Same host:
auth-urlandauth-signinpoint tohttps://$host/...– expose/oauth2/*on the same<DOMAIN>. - Secure cookies: Keep
--cookie-secure=trueand always use HTTPS. - Time sync: OIDC tokens are time-sensitive; ensure node clocks are correct.
- Do not protect
/oauth2/*itself; only app paths (e.g.,/).