oauth2-proxy carries the authorization request in a signed cookie, so the callback can land on a different replica and still succeed, which is the opposite of the BFF failure in B-0. Sharing is therefore just sharing one Secret. Rotating it is all-or-nothing: --cookie-secret is singular, so there is no second key to read old tickets with, and the log shows both the validation failure and Error removing session, leaving the Redis session orphaned because the key cannot be derived from a ticket that will not decode. Getting there required two diagnoses: the callback 502 came from the full session riding in Set-Cookie past nginx's buffer, and every earlier attempt to read nginx config returned nothing because sudo on the host asks for a password while the guests do not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
129 lines
4.8 KiB
YAML
129 lines
4.8 KiB
YAML
# Experiment B-7 — oauth2-proxy, to measure how replicas share a cookie secret
|
|
# and what happens when it is rotated (Q1, unknown 7).
|
|
#
|
|
# This is a different shape of problem from the BFF. The BFF keeps state on the
|
|
# server, so the question was "which store". oauth2-proxy keeps no server state
|
|
# at all: the whole session rides in a cookie that is signed and encrypted with
|
|
# --cookie-secret. So there is nothing to share and nothing to lose on restart —
|
|
# instead, every replica must hold the *same* secret, and changing it invalidates
|
|
# every cookie at once.
|
|
#
|
|
# kubectl apply -f deploy/lab/k8s/b7-oauth2-proxy.yaml
|
|
#
|
|
# app2.hyeonworks.com is borrowed from Grafana for the duration of this
|
|
# experiment; the certificate only covers auth / app1 / app2, so a fourth name
|
|
# is not available. Grafana's Ingress is restored afterwards.
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: oauth2-proxy-secrets
|
|
namespace: keycloak-lab
|
|
type: Opaque
|
|
stringData:
|
|
# oauth2-proxy requires exactly 16, 24 or 32 bytes. This is the value whose
|
|
# rotation the experiment is about.
|
|
COOKIE_SECRET_A: "lab-cookie-secret-aaaaaaaaaaaaaa"
|
|
COOKIE_SECRET_B: "lab-cookie-secret-bbbbbbbbbbbbbb"
|
|
CLIENT_SECRET: proxy-lab-secret
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: oauth2-proxy
|
|
namespace: keycloak-lab
|
|
spec:
|
|
# Two replicas is the point: Q1 asks how they share the secret.
|
|
replicas: 2
|
|
selector:
|
|
matchLabels: { app: oauth2-proxy }
|
|
template:
|
|
metadata:
|
|
labels: { app: oauth2-proxy }
|
|
spec:
|
|
# See B-1: Kubernetes injects <SVCNAME>_PORT as a tcp:// URL and it
|
|
# collides with ordinary configuration names.
|
|
enableServiceLinks: false
|
|
topologySpreadConstraints:
|
|
- maxSkew: 1
|
|
topologyKey: kubernetes.io/hostname
|
|
whenUnsatisfiable: ScheduleAnyway
|
|
labelSelector:
|
|
matchLabels: { app: oauth2-proxy }
|
|
containers:
|
|
- name: oauth2-proxy
|
|
image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1
|
|
args:
|
|
- --provider=oidc
|
|
- --oidc-issuer-url=https://auth.hyeonworks.com/realms/keycloak-patterns
|
|
- --client-id=oauth2-proxy
|
|
- --redirect-url=https://app2.hyeonworks.com/oauth2/callback
|
|
- --email-domain=*
|
|
- --http-address=0.0.0.0:4180
|
|
# The upstream is the same echo app the B-4 header experiment used,
|
|
# so what the proxy forwards can be read straight off the response.
|
|
- --upstream=http://echo.header-lab.svc:8081
|
|
# ★ 이 옵션을 켜면 세션(=쿠키)에 access token 이 들어간다.
|
|
# 그러면 Set-Cookie 가 커져 프록시 앞단에서 502 가 났다.
|
|
# B-4 에서 본 헤더 크기 절벽이 이번에는 응답 쪽에서 나타난 것이다.
|
|
# - --pass-authorization-header=true
|
|
- --set-xauthrequest=true
|
|
- --reverse-proxy=true
|
|
- --cookie-secure=true
|
|
# One hour, matching the value Q1 records for the current setup.
|
|
- --cookie-expire=1h
|
|
- --skip-provider-button=true
|
|
# ★ 쿠키에 세션 전체를 담으면 Set-Cookie 가 커지고, 그 응답이
|
|
# 앞단 nginx 의 proxy_buffer 를 넘겨 502 가 났다(측정됨).
|
|
# Redis 로 옮기면 쿠키에는 티켓만 남는다 — 그리고 그 순간
|
|
# "replica 가 secret 을 공유해야 한다"는 문제의 성격도 바뀐다.
|
|
- --session-store-type=redis
|
|
- --redis-connection-url=redis://redis.keycloak-lab.svc:6379
|
|
env:
|
|
- name: OAUTH2_PROXY_CLIENT_SECRET
|
|
valueFrom:
|
|
secretKeyRef: { name: oauth2-proxy-secrets, key: CLIENT_SECRET }
|
|
# Which of the two secrets is in use is switched here. Both replicas
|
|
# read the same key, which is exactly the sharing Q1 asks about.
|
|
- name: OAUTH2_PROXY_COOKIE_SECRET
|
|
valueFrom:
|
|
secretKeyRef: { name: oauth2-proxy-secrets, key: COOKIE_SECRET_A }
|
|
ports:
|
|
- containerPort: 4180
|
|
name: http
|
|
readinessProbe:
|
|
httpGet: { path: /ping, port: http }
|
|
initialDelaySeconds: 5
|
|
resources:
|
|
requests: { memory: 32Mi, cpu: 20m }
|
|
limits: { memory: 128Mi }
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: oauth2-proxy
|
|
namespace: keycloak-lab
|
|
spec:
|
|
selector: { app: oauth2-proxy }
|
|
ports:
|
|
- port: 4180
|
|
targetPort: http
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: oauth2-proxy
|
|
namespace: keycloak-lab
|
|
spec:
|
|
ingressClassName: traefik
|
|
rules:
|
|
- host: app2.hyeonworks.com
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: oauth2-proxy
|
|
port:
|
|
number: 4180
|