The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
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
|