fix(setup): 실험대에서 35편을 끝까지 밟고 어긋난 명령·결과 31건을 고친다

test-server 를 비우고 다시 세운 뒤 Setup 기록 35편(virtualization 9 ·
keycloak-session-store 26)을 문서에 적힌 명령 그대로 쳤다. 어긋난 자리를
기록과 SSOT 양쪽에 실측과 함께 넣었다.

막히던 것
- 04 의 인증서 경로가 live/hyeonworks.com 이라 nginx 가 [emerg] 로 안 떴다.
  실제 계보는 live/auth.hyeonworks.com 이고 「문제가 생기면」은 진단이 거꾸로였다
- 인증서가 와일드카드가 아니다. SAN 이 auth·app1·app2 셋뿐이라 그 밖의 이름은
  TLS 에서 끊기고 curl 이 exit 60 · %{http_code} 000 을 낸다. SSOT 안에서
  두 문단이 서로 어긋나 있었다
- A-7 14번 ①이 kc-lab-1 에서 여섯 줄 다 실패하는데 마지막 date 만 「차단」을 찍는다

검사가 실패할 수 없던 자리
- B-1 의 세션 키 고르기는 앞 단계가 $KEY 를 채워 둬서 루프가 한 건도 못 맞혀도
  통과한다. KEY= 로 비우고 키마다 1/0 을 찍게 바꿨다
- k3s-agent 유닛의 sed -i 는 패턴에 $HOME 이 들어 있어 아무 줄도 안 바꾼 채 성공한다

certbot
- renew --dry-run 의 종료 코드는 성공도 0, 실패도 0, 다른 사유의 실패는 1 이다.
  본문의 renew failure(s) 로만 판정할 수 있다
- --dry-run 은 staging 서버를 쓰는데 renewal/*.conf 의 account= 는 운영 계정을
  가리킨다. 실패한 dry-run 이 staging 계정을 하나 더 만들어 다음 실행이 계속 멎는다
- 훅을 755 로 놓고 시뮬레이션이 성공해도 Running deploy-hook command 는 안 나온다.
  certbot 2.1.0 에는 --run-deploy-hooks 도 없다
- 강제 갱신은 실제로 쳤고 서빙까지 닿았다. serial 06F3E0EF…1373 → 065547…3DF1,
  notAfter Dec 3 → Dec 16, nginx worker 2629 4712 → 4745 4754

독자가 칠 수 있는 형태로
- 안 되는 형태가 번호 붙은 단계에 앉아 있던 8곳을 뒤집고, 되는 형태를 ①로 올렸다
- 랩 안에서 공개 이름을 치는 curl 65줄에 --resolve 를 붙였다. 붙인 형태를 실제로
  쳐서 문서가 적은 값과 같은지 확인했다
- 힙독·sed -i·echo >>·&&·|| 를 편집기 + 파일 리스팅 + 분할 형태로 바꿨다
- 닫는 코드펜스가 빠져 뒤 200여 줄의 블록 종류가 뒤집혀 있던 곳을 포함해 3곳을 고쳤다

관문: check_body PASS · check_prose error 0 · check_evidence 두 프로젝트 문제 없음 ·
verify-tech-log-tree error 0 · verify-project-layout error 0 · 코드펜스 전수 0건

남은 것: B-0 주입은 keycloak-pattern 저장소의 소스를 고치고 이미지를 다시 구워야
해서 안 했다(unknown).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-17 19:38:12 +09:00
co-authored by Claude Opus 5
parent 4a457afde9
commit 32e39e20aa
150 changed files with 6066 additions and 269 deletions
File diff suppressed because it is too large Load Diff
@@ -165,3 +165,103 @@ SSOT 의 「2026-09-17 재현 — 어디까지 밟았고 무엇이 막았나」
비밀은 어느 파일에도 값으로 들어 있지 않다. 비밀번호는 길이(19·22)만 적었고, 화면에
찍혀 나온 토큰은 <REDACTED n자> 로 가렸다.
--- 2026-09-17 추가 (인증서 대기 중에 잰 것) ---
ext-01..02 밖에서 http 로 닿는지, X-Forwarded-For 가 오는지
ext-03..05, ext-09,11 traefik-forwarded-headers.yaml 적용 전/후/되돌린 뒤 앱이 받은 헤더
ext-06..07 인증서 상태와 발급 전제 (플러그인 O, cloudflare.ini X)
ext-08 http 만으로 어디까지 되는지 (well-known 200 · token 401)
ext-10, ext-12 출발지를 덮는 것이 무엇인지 — 호스트 sudo 가 막아 못 가림
ext-13 BFF 로그인 체인이 https://auth 로 가서 막히는 것
b6-01..09 B-6 키 회전을 문서대로 밟은 기록. 키 조작은 재현, 401/200 판정은 못 냄
ext-14 파드에서 공개 이름이 100.83.212.4 로 풀리고 80·443 이 닫혀 있는 것
ext-15 CoreDNS 를 *.override 로 고치려다 CrashLoopBackOff 난 기록
ext-16 *.server 로 고쳐 넣어 CoreDNS 가 뜬 기록
ext-17 고친 뒤 파드에서 엣지 192.168.122.10:80 이 열리는 것
d4-01 D-4 5·6·7 절. 7 절이 test-server 에서 빈손이고 nginx 는 엣지에 있다
d4a-01 D-4a 5~8 절. 훅 작성·설치·실행과 워커 PID 가 갈리는 것
b7-01, b7-02 CoreDNS 를 고친 뒤 oauth2-proxy 가 두드리는 주소가
100.83.212.4 에서 192.168.122.10 으로 바뀐 것과, 빌린 Grafana Ingress 복구
misc-01 오래 미검증이던 명령 넷 — cookie secret 길이 32·32, R 함수가 빈 출력,
certbot renew --dry-run 이 인증서 0장에서 내는 줄
b2-01 --scan | xargs del 이 tr -d '\r' 없이도 도는 것과, od -c 로 본 줄 끝
b2-02 b2 ②③④ 의 세션 수 — DB 5 와 관리 API 4 가 다른 것
b0-01..03 TLS 뒤 b0 상태 확인 — BFF 가 B-1 상태이고 /actuator/beans 가 밖에서 200
b1-01..05 B-1 을 끝까지 — 로그인, Redis 키 둘, head -1 이 엉뚱한 키를 집는 것
b6-10..18 B-6 을 끝까지 — 대조군 200, 추가는 무중단, 제거 뒤 401/200 이 번갈아 나오는 것
b7-03..08 B-7 을 끝까지 — 클라이언트가 없어 Client not found, 만든 뒤 회전까지
b7a-01 회전 뒤 남은 고아 세션과 TTL 이 갱신 없이 줄어드는 것
d4a-07-dryrun-after-removing-duplicate-staging-account.txt
kc-lab-edge 에서 `sudo certbot renew --dry-run`. staging ACME 계정이 둘이라
"Please choose an account" 로 멎던 것을, 나중에 생긴 계정 하나를 지운 뒤 다시 잰 것.
certbot exit=0 · "Congratulations, all simulated renewals succeeded".
실패했을 때도 0 이 나온 적이 있어 종료 코드로는 성공과 실패가 안 갈린다는 근거.
--- 2026-09-17 저녁 추가 (A-6 관찰·복구를 마저 밟은 기록) ---
a6-05-observe-prep.txt A-6 준비 1~5 — 배치와 파드 IP, a6-probe 파드, PW 길이 19,
구간별 시간 한 번, 두 노드 20회씩 기준선(k1 44ms · k0 40ms),
`grep "^agroal_"` 가 레이블과 값까지 열일곱 줄로 나오는 것
a6-06-observe.txt A-6 주입 재현과 관찰 1~5 — flannel.1 세 줄, 카운터 0 pkt → 18 pkt,
주입 뒤 k1 1865ms · k0 40ms, 동시 20건 1.87→22.25 초 계단 스무 줄,
sort / sort -g 가 같고 sort -g -k2 만 듣는 것, 부하 직후 풀 지표
(blocking_time_max 20000.0 · max_used_count 18.0),
readiness 프로브 context deadline exceeded, 낙관적 락 로그 0
a6-07-revert-checklist.txt A-6 복구와 확인표 여덟 줄 — flannel.1 noqueue, enp1s0 은
기본값 fq_codel, 회복 뒤 k1 42ms · k0 39ms, 엔드포인트 주소 둘,
awaiting/active 0, 탐침 NotFound, 밖에서 auth.hyeonworks.com 이
100.83.212.4 로 풀려 443 이 닫혀 000 이고 --resolve 로 짚으면 200
--- 2026-09-17 저녁 추가 (A-7 을 1번부터 복구까지 다시 밟은 기록) ---
a7-06-prep-and-switch.txt A-7 1~11 — 전환 전 args ["start"], DB 143행, 탐침, 교차 refresh 200,
기능 목록이 두 줄, DELETE 144, patch 가 "patched" 를 내는 것,
전환 뒤 로그인 5회 200 과 DB (0 rows), 캐시 지표 96줄 원문
a7-07-observe.txt A-7 12~15 — 교차 refresh 200, 롤링 재시작 뒤 400 Session not active,
14 ① 블록을 kc-lab-1 에서 문서 그대로 쳤을 때 kubectl 이 막히고
iptables 가 Bad argument 를 내며 ssh 가 Host key verification failed 이고
그래도 "차단" 시각이 찍히는 것, 규칙이 0개인 것, lab host 에서 다시 친
형태와 cluster_size 2→1(+75초)·해제 뒤 1→2(+60초), coord=t 둘,
대조군 200 · 시험군 400, DB 정지 뒤 500 / 200
a7-08-revert-checklist.txt A-7 복구와 확인표 아홉 줄 — args ["start"], git diff 빈 출력,
로그인 뒤 세션 행 1, raw PREROUTING 비어 있음, cluster_size 양쪽 2,
탐침 NotFound, 밖은 000 이고 --resolve 로 짚으면 200
--- 2026-09-17 저녁 추가 (B-5 를 1번부터 복구까지 다시 밟은 기록) ---
b5-04-baseline.txt B-5 준비 1~5 — 파드 배치, --scan 이 빈 출력(키 0), save 가
3600 1 300 100 60 10000 이고 appendonly yes, spec.volumes 에
kube-api-access-* 가 함께 있는 것, PVC 둘 Bound, 공개 이름으로는
세 경로가 000 이고 --resolve 로 짚으면 200/302/200, health 그룹 셋
b5-05-redis-down.txt B-5 주입 1 과 관찰 — Redis 0/0, BFF 로그가 pollConnect 가 아니라
ConnectionWatchdog "Cannot reconnect ... Connection refused",
bff 둘 1/1 RESTARTS 0, 엔드포인트 true,true, 세 경로 200/000/000,
/actuator/health 는 --max-time 10 이면 000 이고 60.046452 초 뒤 503
(redis: QueryTimeoutException), token-boundary 는 90초까지 무응답
b5-06-persistence.txt B-5 주입 2 와 지속성 시험 — 볼륨을 떼도 spec.volumes 가 빈 줄이
아니라 kube-api-access-* 하나, appendonlydir 생성, 파드 삭제 뒤
dbsize 0 인데 appendonly 는 yes(매니페스트 args 가 이미 yes),
PVC 를 되돌린 뒤 dbsize 4 와 written-on-pvc, 네 지표 시계열 0개
b5-07-revert-checklist.txt B-5 복구와 확인표 여덟 줄 — apply 후 redis 1/1, PVC Bound,
appendonly yes, bff 둘 1/1, 엔드포인트 둘, b5:* 삭제 1건이고 남은 키는
bff:session:sessions 셋, 밖은 000 이고 --resolve 로 짚으면 200
d4a-09-dryrun-with-deploy-hook.txt
/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh 를 755 로 놓은 뒤의
`sudo certbot renew --dry-run`. 시뮬레이션은 성공(exit=0)했는데
"Running deploy-hook command" 줄은 없다 — certbot 2.1.0 의 dry-run 은 deploy 훅을 건너뛴다.
d4a-10-dryrun-dns-propagation-flake.txt
같은 명령이 바로 앞뒤로는 성공했는데 이 판만 실패한 것. 사유는 DNS 전파로,
"During secondary validation: DNS problem: NXDOMAIN looking up TXT for
_acme-challenge.auth.hyeonworks.com" · propagation-seconds 기본값 30. certbot exit=1.
d4a-11-deploy-hook-not-called-in-dry-run.txt
위 판정의 근거 넷 — 훅 파일의 권한·크기, 성공한 출력에서 "deploy-hook" 을 센 값 0,
certbot 2.1.0, `--help all` 에 --run-deploy-hooks 가 없다는 것(센 값 0).
c2-05-probe-reachability-relive.txt
C-2 §5 의 임시 탐침 파드(curlimages/curl:8.11.1)를 2026-09-17 재구성 랩에서 다시 띄운 것.
app1.hyeonworks.com 이 192.168.122.10(엣지 게스트)으로 풀리고 HTTPS 200.
원래 실행의 100.83.212.4(tailnet 헤어핀)와 주소가 다르다 — 지금은 CoreDNS 의
coredns-custom 항목이 그 일을 한다. networkpolicy 는 여전히 없다.
d4-01-before-force-renewal.txt / d4-02-force-renewal.txt / d4-03-after-force-renewal.txt
D-4·D-4a 의 강제 갱신을 2026-09-17 에 kc-lab-edge 에서 실제로 친 것.
01 = 치기 전 archive 목록·nginx worker PID·서빙 인증서의 serial/날짜
02 = `sudo certbot renew --force-renewal` 전문. 배포 훅이 돌았고 certbot exit=0
03 = 친 뒤의 같은 세 가지 + 세 이름의 HTTPS 응답.
serial 06F3E0EF…1373 → 065547991777…3DF1, notAfter Dec 3 → Dec 16,
worker 2629 4712 → 4745 4754. 갱신이 서빙까지 닿았다는 근거.
@@ -0,0 +1,88 @@
=== a6 준비 1 ① 배치와 IP ===
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
bff-5d97897676-nmlqs 1/1 Running 0 150m 10.42.0.22 kc-lab-1 <none> <none>
bff-5d97897676-zvfhp 1/1 Running 0 150m 10.42.1.51 kc-lab-2 <none> <none>
keycloak-0 1/1 Running 0 121m 10.42.1.64 kc-lab-2 <none> <none>
keycloak-1 1/1 Running 0 121m 10.42.0.29 kc-lab-1 <none> <none>
oauth2-proxy-d9b587458-7x9jt 1/1 Running 0 55m 10.42.1.78 kc-lab-2 <none> <none>
oauth2-proxy-d9b587458-njxdw 1/1 Running 0 55m 10.42.1.79 kc-lab-2 <none> <none>
postgres-7b474b88c8-48lrr 1/1 Running 0 160m 10.42.1.46 kc-lab-2 <none> <none>
redis-f94bc6dbf-s674z 1/1 Running 0 123m 10.42.1.63 kc-lab-2 <none> <none>
=== a6 준비 1 ② 세 값을 셸 변수에 ===
K0=10.42.1.64 K1=10.42.0.29 PG=10.42.1.46
=== a6 준비 2 ① 탐침 파드를 띄운다 ===
pod/a6-probe created
exit=0
=== a6 준비 2 ② 뜰 때까지 기다린다 ===
pod/a6-probe condition met
exit=0
=== a6 준비 2 ③ 값이 아니라 길이만 확인한다 ===
K0=10.42.1.64 K1=10.42.0.29 PW=19자
=== a6 준비 3 ① 구간별 시간을 함께 찍는다 ===
connect 0.000344 ttfb 0.110990 total 0.111040
=== a6 준비 4 ① keycloak-1 에 20회 ===
=== a6 준비 4 ② 원본을 먼저 본다 (k1) ===
0.044959
0.046268
0.046746
0.041681
0.040931
0.048671
0.040027
0.052310
0.040426
0.039144
0.042341
0.044985
0.042190
0.044052
0.043110
0.044736
0.039418
0.058032
0.040404
0.038562
=== a6 준비 4 ③ 그다음 줄여서 본다 (k1) ===
20회 평균 44 ms
=== a6 준비 4 대조군 — $K1 을 $K0 로, 파일 이름도 /tmp/base-k0 으로 ===
=== 원본 (k0) ===
0.046606
0.069418
0.044654
0.039537
0.037198
0.038300
0.037252
0.039732
0.036486
0.037554
0.036501
0.035420
0.036633
0.036898
0.052301
0.035256
0.036421
0.034673
0.035721
0.040525
=== 평균 (k0) ===
20회 평균 40 ms
=== a6 준비 5 ① 지표 이름 목록 ===
agroal_invalid_count_total{datasource="default"} 0.0
agroal_flush_count_total{datasource="default"} 0.0
agroal_leak_detection_count_total{datasource="default"} 0.0
agroal_reap_count_total{datasource="default"} 3.0
agroal_blocking_time_max_milliseconds{datasource="default"} 112.0
agroal_max_used_count{datasource="default"} 3.0
agroal_acquire_count_total{datasource="default"} 1908.0
agroal_active_count{datasource="default"} 0.0
agroal_awaiting_count{datasource="default"} 0.0
agroal_blocking_time_average_milliseconds{datasource="default"} 0.0
agroal_creation_time_total_milliseconds{datasource="default"} 160.0
agroal_creation_time_average_milliseconds{datasource="default"} 32.0
agroal_destroy_count_total{datasource="default"} 3.0
agroal_available_count{datasource="default"} 2.0
agroal_creation_count_total{datasource="default"} 5.0
agroal_creation_time_max_milliseconds{datasource="default"} 105.0
agroal_blocking_time_total_milliseconds{datasource="default"} 285.0
@@ -0,0 +1,225 @@
K0=10.42.1.64 K1=10.42.0.29 PG=10.42.1.46
=== 주입 전에 flannel.1 에 무엇이 걸려 있나 ===
qdisc noqueue 0: root refcnt 2
=== 주입 ① 밴드 3개짜리 분류기 ===
exit=0
=== 주입 ② 3번 밴드에 200ms ===
exit=0
=== 주입 ③ 출발지가 postgres 인 패킷을 3번 밴드로 ===
exit=0
=== 주입 ④ 주입 시각 ===
17:56:37 주입
=== 주입 검증 ① 넣은 직후의 카운터 ===
qdisc prio 1: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
qdisc netem 30: parent 1:3 limit 1000 delay 200ms
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
=== 주입 검증 ② 요청을 한 번 보낸다 ===
1.855960
=== 주입 검증 ③ 다시 센다 ===
qdisc netem 30: parent 1:3 limit 1000 delay 200ms
Sent 1556 bytes 18 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
=== 관찰 1 ① 구간별 시간 (keycloak-1) ===
connect 0.000552 ttfb 1.864861 total 1.864966
=== 관찰 1 ② 20회 반복 — keycloak-1 ===
1.860758
1.870170
1.872980
1.876086
1.857782
1.859351
1.858796
1.860100
1.864847
1.874546
1.856623
1.870452
1.879456
1.867865
1.861147
1.861699
1.867978
1.862271
1.864539
1.857025
20회 평균 1865 ms 최대 1879 ms
=== 관찰 1 ③ 20회 반복 — keycloak-0 (대조군) ===
0.039154
0.043190
0.052382
0.037677
0.036750
0.037014
0.038348
0.035596
0.036738
0.038740
0.036093
0.040349
0.038105
0.040798
0.035273
0.042598
0.045344
0.038030
0.046025
0.034717
20회 평균 40 ms 최대 52 ms
=== 관찰 2 ① 동시 20건 ===
17:57:39 부하 시작
17:58:02 부하 끝
=== 관찰 3 ① 부하 직후에 읽는다 ===
agroal_blocking_time_max_milliseconds{datasource="default"} 20000.0
agroal_max_used_count{datasource="default"} 18.0
agroal_acquire_count_total{datasource="default"} 2020.0
agroal_active_count{datasource="default"} 0.0
agroal_awaiting_count{datasource="default"} 0.0
agroal_blocking_time_average_milliseconds{datasource="default"} 114.0
agroal_available_count{datasource="default"} 18.0
agroal_blocking_time_total_milliseconds{datasource="default"} 230680.0
=== 관찰 2 ② 다 모였는지부터 센다 ===
20 /tmp/load.txt
=== 관찰 2 ③ 원본을 본다 ===
200 1.889034
200 1.871238
200 1.967936
200 3.399275
200 4.813854
200 6.247018
200 7.652515
200 9.066673
200 10.495914
200 11.919137
200 13.326469
200 14.736787
200 16.159992
200 17.588220
200 21.854297
200 22.214107
200 22.207834
200 22.216690
200 22.245645
200 22.242271
=== 관찰 2 ④ 상태 코드와 시간을 나눠 본다 ===
20 200
1.871238
1.889034
1.967936
3.399275
4.813854
6.247018
7.652515
9.066673
10.495914
11.919137
13.326469
14.736787
16.159992
17.588220
21.854297
22.207834
22.214107
22.216690
22.242271
22.245645
=== 관찰 2 ⑤ 정렬 방식을 갈라 본다 — sort (사전순) ===
200 1.871238
200 1.889034
200 1.967936
200 10.495914
200 11.919137
200 13.326469
200 14.736787
200 16.159992
200 17.588220
200 21.854297
200 22.207834
200 22.214107
200 22.216690
200 22.242271
200 22.245645
200 3.399275
200 4.813854
200 6.247018
200 7.652515
200 9.066673
=== 관찰 2 ⑤ sort -g ===
200 1.871238
200 1.889034
200 1.967936
200 10.495914
200 11.919137
200 13.326469
200 14.736787
200 16.159992
200 17.588220
200 21.854297
200 22.207834
200 22.214107
200 22.216690
200 22.242271
200 22.245645
200 3.399275
200 4.813854
200 6.247018
200 7.652515
200 9.066673
=== 관찰 2 ⑥ sort -g -k2 ===
200 1.871238
200 1.889034
200 1.967936
200 3.399275
200 4.813854
200 6.247018
200 7.652515
200 9.066673
200 10.495914
200 11.919137
200 13.326469
200 14.736787
200 16.159992
200 17.588220
200 21.854297
200 22.207834
200 22.214107
200 22.216690
200 22.242271
200 22.245645
=== 관찰 4 ① 최근 이벤트 ===
57m Normal ScalingReplicaSet deployment/oauth2-proxy Scaled up replica set oauth2-proxy-d9b587458 from 0 to 1
57m Normal Pulled pod/oauth2-proxy-d9b587458-7x9jt Container image "quay.io/oauth2-proxy/oauth2-proxy:v7.7.1" already present on machine and can be accessed by the pod
57m Normal SuccessfulCreate replicaset/oauth2-proxy-d9b587458 Created pod: oauth2-proxy-d9b587458-7x9jt
57m Normal Created pod/oauth2-proxy-d9b587458-7x9jt Container created
57m Normal Started pod/oauth2-proxy-d9b587458-7x9jt Container started
57m Normal Created pod/oauth2-proxy-d9b587458-njxdw Container created
57m Normal Killing pod/oauth2-proxy-6c56c4c94f-gq484 Stopping container oauth2-proxy
57m Normal SuccessfulDelete replicaset/oauth2-proxy-6c56c4c94f Deleted pod: oauth2-proxy-6c56c4c94f-gq484
57m Normal Pulled pod/oauth2-proxy-d9b587458-njxdw Container image "quay.io/oauth2-proxy/oauth2-proxy:v7.7.1" already present on machine and can be accessed by the pod
57m Normal ScalingReplicaSet deployment/oauth2-proxy Scaled up replica set oauth2-proxy-d9b587458 from 1 to 2
57m Normal Started pod/oauth2-proxy-d9b587458-njxdw Container started
57m Normal SuccessfulCreate replicaset/oauth2-proxy-d9b587458 Created pod: oauth2-proxy-d9b587458-njxdw
57m Normal ScalingReplicaSet deployment/oauth2-proxy Scaled down replica set oauth2-proxy-6c56c4c94f from 2 to 1
57m Normal SuccessfulDelete replicaset/oauth2-proxy-6c56c4c94f Deleted pod: oauth2-proxy-6c56c4c94f-szr6j
57m Normal ScalingReplicaSet deployment/oauth2-proxy Scaled down replica set oauth2-proxy-6c56c4c94f from 1 to 0
57m Normal Killing pod/oauth2-proxy-6c56c4c94f-szr6j Stopping container oauth2-proxy
115s Normal Created pod/a6-probe Container created
115s Normal Pulled pod/a6-probe Container image "curlimages/curl:8.11.1" already present on machine and can be accessed by the pod
114s Normal Started pod/a6-probe Container started
19s Warning Unhealthy pod/keycloak-1 Readiness probe failed: Get "http://10.42.0.29:9000/health/ready": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
=== 관찰 4 ② 파드 상태 ===
NAME READY STATUS RESTARTS AGE
a6-probe 1/1 Running 0 115s
bff-5d97897676-nmlqs 1/1 Running 0 152m
bff-5d97897676-zvfhp 1/1 Running 0 152m
keycloak-0 1/1 Running 0 123m
keycloak-1 1/1 Running 0 123m
oauth2-proxy-d9b587458-7x9jt 1/1 Running 0 57m
oauth2-proxy-d9b587458-njxdw 1/1 Running 0 57m
postgres-7b474b88c8-48lrr 1/1 Running 0 162m
redis-f94bc6dbf-s674z 1/1 Running 0 125m
=== 관찰 5 ① 미검증 — 충돌 로그를 센다 ===
0
exit=1
@@ -0,0 +1,53 @@
=== 복구 1 ① 해제 시각 ===
17:58:20 해제
=== 복구 1 ② root 를 지운다 ===
exit=0
=== 복구 1 ③ 무엇이 남았는지 본다 ===
qdisc noqueue 0: root refcnt 2
=== 복구 2 회복 확인 — 20회 반복 측정을 그대로 다시 (keycloak-1) ===
keycloak-1 20회 평균 42 ms
=== 복구 2 회복 확인 — keycloak-0 ===
keycloak-0 20회 평균 39 ms
=== 확인표 qdisc (flannel.1) ===
qdisc noqueue 0: root refcnt 2
=== 확인표 (물리 쪽도) enp1s0 ===
qdisc fq_codel 0: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
=== 확인표 파드 ===
NAME READY STATUS RESTARTS AGE
a6-probe 1/1 Running 0 2m9s
bff-5d97897676-nmlqs 1/1 Running 0 152m
bff-5d97897676-zvfhp 1/1 Running 0 152m
keycloak-0 1/1 Running 0 123m
keycloak-1 1/1 Running 0 123m
oauth2-proxy-d9b587458-7x9jt 1/1 Running 0 57m
oauth2-proxy-d9b587458-njxdw 1/1 Running 0 57m
postgres-7b474b88c8-48lrr 1/1 Running 0 162m
redis-f94bc6dbf-s674z 1/1 Running 0 125m
=== 확인표 Service ===
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
keycloak-lxk8h IPv4 8080 10.42.0.29,10.42.1.64 4h22m
=== 확인표 풀 ===
agroal_active_count{datasource="default"} 0.0
agroal_awaiting_count{datasource="default"} 0.0
=== 탐침 파드를 지운다 ===
pod "a6-probe" deleted from keycloak-lab namespace
=== 확인표 탐침 파드 ===
Error from server (NotFound): pods "a6-probe" not found
exit=1
=== 확인표 밖 ===
000
=== 확인표 밖 — 같은 줄을 -s 없이 다시 ===
curl: (7) Failed to connect to auth.hyeonworks.com:443 after 3 ms: Could not connect to server
000
exit=7
=== 이름이 무엇으로 풀리는가 ===
100.83.212.4 auth.hyeonworks.com
=== 엣지 쪽에서 같은 줄 ===
000
exit=7
=== 엣지 주소로 직접 (이름만 헤더로) ===
200
exit=0
=== http 로 ===
301
exit=0
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
=== a7 12 로그인하고 반대 노드에서 갱신한다 ===
200
=== a7 13 ① 토큰을 담고 access token 의 클레임을 편다 ===
{"exp":1789635852,"iat":1789635792,"jti":"onltro:81f8f0e3-06aa-890c-3b2b-bc1080d617bd","iss":"https://auth.hyeonworks.com/realms/master","typ":"Bearer","azp":"admin-cli","sid":"7HNHaJ1RxIKwxI-CUn86vUSe","scope":"profile email
=== a7 13 ② 시각을 남기고 롤링 재시작을 건다 ===
18:04:47 재시작
statefulset.apps/keycloak restarted
Waiting for partitioned roll out to finish: 0 out of 2 new pods have been updated...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for partitioned roll out to finish: 1 out of 2 new pods have been updated...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
partitioned roll out complete: 2 new pods have been updated...
=== a7 13 ③ 롤아웃 뒤 IP 를 다시 잡는다 ===
10.42.1.84 10.42.0.38
=== a7 13 ④ 재시작 전 토큰으로 갱신을 시도한다 ===
{"error":"invalid_grant","error_description":"Session not active"}
400
=== (참고) 재시작 뒤 sessions 캐시 ===
"pod":"keycloak-1"}
"151"]}
"pod":"keycloak-0"}
"0"]}]}}
######## a7 14 ① 을 kc-lab-1 에서 문서 그대로 친 결과
time="2026-09-17T09:04:49Z" level=warning msg="Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode or --write-kubeconfig-group to modify kube config permissions"
error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied
time="2026-09-17T09:04:49Z" level=warning msg="Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode or --write-kubeconfig-group to modify kube config permissions"
error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied
Bad argument `7800'
Try `iptables -h' or 'iptables --help' for more information.
Bad argument `57800'
Try `iptables -h' or 'iptables --help' for more information.
Host key verification failed.
Host key verification failed.
09:04:49 차단
remote_exit=0
######## 그 뒤 두 노드의 raw PREROUTING — 규칙이 하나도 안 들어갔다
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
--- kc-lab-2 ---
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
K0=10.42.1.84 K1=10.42.0.38
=== a7 14 ① 양쪽 노드에 raw DROP 을 넣는다 (lab host 에서 두 노드 모두 ssh 한 줄로) ===
18:06:40 차단
=== a7 14 ② 두 노드의 규칙과 카운터를 본다 ===
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP 6 -- * * 0.0.0.0/0 10.42.0.38 tcp dpt:57800
0 0 DROP 6 -- * * 0.0.0.0/0 10.42.0.38 tcp dpt:7800
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP 6 -- * * 0.0.0.0/0 10.42.1.84 tcp dpt:57800
2 116 DROP 6 -- * * 0.0.0.0/0 10.42.1.84 tcp dpt:7800
=== a7 14 ③ 두 노드가 각각 아는 멤버 수를 본다 ===
+25초
"pod":"keycloak-1"}
"2"]}
"pod":"keycloak-0"}
"2"]}]}}
+50초
"pod":"keycloak-1"}
"2"]}
"pod":"keycloak-0"}
"2"]}]}}
+75초
"pod":"keycloak-1"}
"1"]}
"pod":"keycloak-0"}
"1"]}]}}
+100초
"pod":"keycloak-1"}
"1"]}
"pod":"keycloak-0"}
"1"]}]}}
+125초
"pod":"keycloak-1"}
"1"]}
"pod":"keycloak-0"}
"1"]}]}}
=== a7 14 ② 다시 — 카운터가 올라갔나 ===
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
20 1184 DROP 6 -- * * 0.0.0.0/0 10.42.0.38 tcp dpt:57800
2 120 DROP 6 -- * * 0.0.0.0/0 10.42.0.38 tcp dpt:7800
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP 6 -- * * 0.0.0.0/0 10.42.1.84 tcp dpt:57800
22 4002 DROP 6 -- * * 0.0.0.0/0 10.42.1.84 tcp dpt:7800
=== a7 14 ④ 디스커버리 테이블의 코디네이터를 센다 ===
name | ip | coord
------------------+-----------------+-------
keycloak-0-41852 | 10.42.1.84:7800 | t
keycloak-1-65118 | 10.42.0.38:7800 | t
(2 rows)
[exited with code 0]
K0=10.42.1.84 K1=10.42.0.38
=== a7 14 ⑤ 대조군 — 로그인한 노드에서 갱신한다 ===
same-node 200
=== a7 14 ⑥ 시험군 — 새로 로그인해서 반대 노드에서 갱신한다 ===
{"error":"invalid_grant","error_description":"Session not active"}
cross-node 400
=== a7 14 차단을 푼다 (두 노드) ===
18:09:09 해제
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
=== 양쪽 cluster_size 가 2 로 돌아오는가 ===
+20초
"pod":"keycloak-1"}
"1"]}
"pod":"keycloak-0"}
"1"]}]}}
+40초
"pod":"keycloak-1"}
"1"]}
"pod":"keycloak-0"}
"1"]}]}}
+60초
"pod":"keycloak-1"}
"2"]}
"pod":"keycloak-0"}
"2"]}]}}
둘 다 2
[exited with code 0]
K0=10.42.1.84 K1=10.42.0.38
=== a7 15 ① 로그인만 해서 /tmp/rt 를 채운다 ===
{"exp":1789636190,"iat":1789636130,"jti":"onltro:66c69a56-72aa-998d-3a65-669945a48ac9","iss":"https://auth.hyeonworks.com/realms/master","typ":"Bearer","azp":"admin-cli","sid":"x8EWkde54QSEnlY-lDKizez3","scope":"profile email
=== a7 15 ② 시각을 남기고 DB 를 0 replica 로 내린다 ===
18:10:24 정지
deployment.apps/postgres scaled
pod/postgres-7b474b88c8-48lrr condition met
=== a7 15 ③ 캐시를 가진 노드에서 refresh ===
500
=== a7 15 ④ 새 로그인 ===
200
=== a7 15 ⑤ DB 를 다시 올린다 ===
deployment.apps/postgres scaled
Waiting for deployment "postgres" rollout to finish: 0 out of 1 new replicas have been updated...
Waiting for deployment "postgres" rollout to finish: 0 of 1 updated replicas are available...
deployment "postgres" successfully rolled out
@@ -0,0 +1,64 @@
=== a7 복구 ① 두 노드의 raw 규칙을 확인한다 ===
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
=== a7 복구 ② DB 파드를 본다 ===
NAME READY STATUS RESTARTS AGE
postgres-7b474b88c8-kvt96 1/1 Running 0 30s
=== a7 복구 ③ args 를 기본값으로 되돌린다 ===
statefulset.apps/keycloak patched
18:10:55 원복
Waiting for partitioned roll out to finish: 0 out of 2 new pods have been updated...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for partitioned roll out to finish: 1 out of 2 new pods have been updated...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
partitioned roll out complete: 2 new pods have been updated...
=== a7 복구 ④ 매니페스트의 변경을 확인한다 (patch 로 바꿨으므로 파일은 안 건드렸다) ===
git diff exit=0 (출력 없음이면 변경 없음)
=== a7 복구 ⑤ 새 IP 로 탐침을 다시 띄운다 ===
pod "a7-probe" deleted from keycloak-lab namespace
K0=10.42.1.86 K1=10.42.0.39
pod/a7-probe created
pod/a7-probe condition met
=== a7 복구 ⑥ 로그인을 한 번 한다 (상태 코드만) ===
200
=== a7 복구 ⑦ 로그인 뒤 온라인 세션 행을 센다 ===
1
=== 확인표 args ===
["start"]
=== 확인표 파드 ===
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
a7-probe 1/1 Running 0 2s 10.42.1.87 kc-lab-2 <none> <none>
bff-5d97897676-nmlqs 1/1 Running 0 166m 10.42.0.22 kc-lab-1 <none> <none>
bff-5d97897676-zvfhp 1/1 Running 0 166m 10.42.1.51 kc-lab-2 <none> <none>
keycloak-0 1/1 Running 0 72s 10.42.1.86 kc-lab-2 <none> <none>
keycloak-1 1/1 Running 0 105s 10.42.0.39 kc-lab-1 <none> <none>
oauth2-proxy-d9b587458-7x9jt 1/1 Running 0 72m 10.42.1.78 kc-lab-2 <none> <none>
oauth2-proxy-d9b587458-njxdw 1/1 Running 0 71m 10.42.1.79 kc-lab-2 <none> <none>
postgres-7b474b88c8-kvt96 1/1 Running 0 2m18s 10.42.1.85 kc-lab-2 <none> <none>
redis-f94bc6dbf-s674z 1/1 Running 0 139m 10.42.1.63 kc-lab-2 <none> <none>
=== 확인표 DB ===
NAME READY STATUS RESTARTS AGE
postgres-7b474b88c8-kvt96 1/1 Running 0 2m18s
=== 확인표 iptables ===
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
=== 확인표 클러스터 ===
"pod":"keycloak-1"}
"2"]}
"pod":"keycloak-0"}
"2"]}]}}
=== a7 복구 ⑧ 탐침 파드를 지운다 ===
pod "a7-probe" deleted from keycloak-lab namespace
=== 확인표 탐침 파드 ===
Error from server (NotFound): pods "a7-probe" not found
=== 확인표 밖 ===
000
200
@@ -0,0 +1,3 @@
=== 지금 bff 가 어느 편의 상태인가 ===
2 replicas, image=keycloak-pattern-bff:lab
--- SessionRepository 계열 빈이 있나 (b0 2절과 같은 질문)
@@ -0,0 +1,10 @@
=== actuator/beans 가 응답하나 ===
sh: curl: not found
command terminated with exit code 127
--- 빈 수
sh: curl: not found
command terminated with exit code 127
0
--- session 이 이름에 든 빈
sh: curl: not found
command terminated with exit code 127
@@ -0,0 +1,22 @@
=== b0 1단계 ①②③ (문서 형태) ===
bff-5d97897676-nmlqs
155395 /tmp/beans.json
{"contexts":{"keycloak-bff":{"beans":{"applicationTaskExecutor":{"aliases":["bootstrapExecutor"],"scope":"singleton","type":"org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor","resource
=== 밖에서 /actuator/beans 는 정말 Bad Gateway 인가 (TLS 가 선 지금) ===
code=200 size=155395
{"contexts":{"keycloak-bff":{"beans":{"applicationTaskExecutor":{"aliases":["bootstrapExecutor"],"scope":"singleton","ty
=== b0 2단계 저장소 계열 빈 ===
236
"org.springframework.boot.autoconfigure.session.RedisSessionConfiguration"
"org.springframework.boot.autoconfigure.session.RedisSessionProperties"
"org.springframework.boot.autoconfigure.session.SessionRepositoryFilterConfiguration"
"org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration"
"org.springframework.session.data.redis.RedisSessionRepository"
"org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"
"org.springframework.session.web.http.SessionRepositoryFilter"
"sessionRepository"
"sessionRepositoryFilterRegistration"
"springBootSessionRepositoryCustomizer"
"springSessionRepositoryFilter"
@@ -0,0 +1,11 @@
=== ① 로그인 페이지까지 따라간다 ===
도착: https://app1.hyeonworks.com/...
상태: 0 개의 form 단서
form action: ...
=== ② 자격을 제출한다 ===
결과: 000
=== ③ 토큰 경계를 읽는다 (b0 5절 · b1 관찰) ===
=== ④ 쿠키 항아리에 무엇이 남았나 (값은 길이만) ===
@@ -0,0 +1,20 @@
=== ① /oauth2/authorization/keycloak 로 시작 ===
ë„ì°©: https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/auth?response
ë°”ì´íЏ: 7021
form action: https://auth.hyeonworks.com/realms/keycloak-patterns/login-actions/authenticate?session_code=ySbuIX9Mo31v8XcKl
=== â‘¡ ìžê²© 제출 ===
ê²°ê³¼: 200 https://app1.hyeonworks.com/
본문 앞: <!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AP3 Â
=== â‘¢ í† í° ê²½ê³„ ===
{"pattern":"AP3-backend-for-frontend","principal":"labuser","accessTokenStoredOnServer":true,"refreshTokenStoredOnServer":true,"browserTokenCount":0,"csrfProtectionEnabled":true}
=== â‘£ 쿠키 (ê°’ì€ ê¸¸ì´ë§Œ) ===
auth.hyeonworks.com KC_AUTH_SESSION_HASH 66ìž
auth.hyeonworks.com KEYCLOAK_SESSION 64ìž
@@ -0,0 +1,20 @@
=== 쿠키 전부 (HttpOnly 포함 · 값은 길이만) ===
auth.hyeonworks.com KC_AUTH_SESSION_HASH 66자
auth.hyeonworks.com AUTH_SESSION_ID 165자
app1.hyeonworks.com AP3_SESSION 48자
auth.hyeonworks.com KEYCLOAK_IDENTITY 594자
auth.hyeonworks.com KEYCLOAK_SESSION 64자
=== b1 관찰 2 · Redis 를 직접 연다 ===
dbsize=2
bff:session:sessions:b66634a2-1381-4071-8ceb-0a006fbe1327 type=hash ttl=1783
bff:session:sessions:c5f7b0c7-cd17-4749-bf3e-6d42c814bb1c type=hash ttl=1790
=== b1 관찰 3 · 필드와 바이트 ===
키: bff:session:sessions:b66634a2-1381-4071-8ceb-0a006fbe1327
--- 필드 이름 (값은 안 본다)
sessionAttr:SPRING_SECURITY_SAVED_REQUEST
maxInactiveInterval
creationTime
lastAccessedTime
--- 전체 바이트: 2224
@@ -0,0 +1,28 @@
=== b1 관찰 2 ① 문서 그대로 ===
2
bff:session:sessions:b66634a2-1381-4071-8ceb-0a006fbe1327
bff:session:sessions:c5f7b0c7-cd17-4749-bf3e-6d42c814bb1c
=== b1 관찰 2 ② 문서 그대로 (head -1) ===
bff:session:sessions:b66634a2-1381-4071-8ceb-0a006fbe1327
--- 그 키의 타입과 필드
hash
sessionAttr:SPRING_SECURITY_SAVED_REQUEST
maxInactiveInterval
creationTime
lastAccessedTime
--- TTL: 1771
=== 키가 둘인데 각각 무엇을 담고 있나 ===
bff:session:sessions:b66634a2-1381-4071-8ceb-0a006fbe1327
sessionAttr:SPRING_SECURITY_SAVED_REQUEST
maxInactiveInterval
creationTime
lastAccessedTime
bff:session:sessions:c5f7b0c7-cd17-4749-bf3e-6d42c814bb1c
lastAccessedTime
maxInactiveInterval
sessionAttr:org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository.AUTHORIZATION_REQUEST
creationTime
sessionAttr:SPRING_SECURITY_LAST_EXCEPTION
sessionAttr:SPRING_SECURITY_CONTEXT
@@ -0,0 +1,6 @@
=== SPRING_SECURITY_CONTEXT 를 가진 키를 골라서 관찰 3 ② ===
인증된 세션 키: bff:session:sessions:c5f7b0c7-cd17-4749-bf3e-6d42c814bb1c
TTL: 1767
--- --no-raw hgetall 앞 두 줄
1) "lastAccessedTime"
2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\xa0\xaeW\xad\xa3"
@@ -0,0 +1,20 @@
=== 시험용 키 둘을 넣는다 ===
OK
OK
bff:session:sessions:e22840df-61f2-4126-99a9-7f1dd7fddcc1
bff:session:probe2
bff:session:sessions:8a132645-4322-4b73-b97a-e3604ad1e766
bff:session:sessions:503e5c35-4e15-4d59-a034-5b9decc0ec54
bff:session:probe1
=== b2 ② 를 문서 그대로 (tr -d '
' 없이) ===
5
=== 정말 지워졌나 ===
(위가 비면 지워진 것)
=== 스캔 출력에 CR 이 실제로 붙어 오나 ===
0000000 b f f : s e s s i o n : p r o b
0000020 e 3 \n
0000023
@@ -0,0 +1,21 @@
=== b2 ② 토큰 행 수 ===
count
-------
0
(1 row)
=== b2 ③ Keycloak 세션을 DB 쪽에서 센다 ===
offline_flag | count
--------------+-------
0 | 5
(1 row)
=== b2 ④ 관리 API 로 세는 형태 ===
[ {
"offline" : "0",
"clientId" : "bff-confidential",
"active" : "4",
"id" : "7ae3362a-2bed-4902-b6af-d7307ea4ad0b"
} ]
@@ -0,0 +1,13 @@
=== B-2 ② 로그인 뒤 토큰 행 수 ===
count
-------
1
(1 row)
=== 빈 목록에 JDBC 계열이 있나 ===
"authorizedClientRepository"
"authorizedClientService"
"org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager"
"org.springframework.security.oauth2.client.JdbcOAuth2AuthorizedClientService"
"org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository"
@@ -0,0 +1,18 @@
=== B-2 7단계 ① 행 수·해시·발급 시각 ===
client_registration_id | principal_name | access_token_issued_at | at_md5
------------------------+----------------+----------------------------+----------------------------------
keycloak | labuser | 2026-09-17 08:01:46.958592 | 722769ac041a42164d08060e5446d02b
(1 row)
=== B-2 7단계 ② 두 토큰의 바이트 수 ===
client_registration_id | principal_name | access_token_type | at_len | rt_len
------------------------+----------------+-------------------+--------+--------
keycloak | labuser | Bearer | 1437 | 744
(1 row)
=== B-2 관찰 2 ① rt 바이트 수 ===
744
=== B-2 관찰 2 ② 앞 36자만 (자격증명이라 36자에서 끊는다) ===
eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldU
@@ -0,0 +1,12 @@
=== 대조군 (브라우저 A) ===
행 수 1
at_md5 722769ac041a42164d08060e5446d02b
=== 주입: 새 브라우저(B)로 같은 사용자 로그인 ===
로그인 폼을 거쳐 200
{"pattern":"AP3-backend-for-frontend","principal":"labuser","accessTokenStoredOnServer":tr
=== 관찰 1: 행이 늘었나 덮어써졌나 ===
행 수 1
at_md5 5c38b11f27f490ea1d9ee07e73aef2b2
발급 시각 2026-09-17 08:05:52.156416
@@ -0,0 +1,43 @@
=== b5 1 파드 배치를 본다 ===
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
bff-5d97897676-nmlqs 1/1 Running 0 168m 10.42.0.22 kc-lab-1 <none> <none>
bff-5d97897676-zvfhp 1/1 Running 0 168m 10.42.1.51 kc-lab-2 <none> <none>
keycloak-0 1/1 Running 0 3m 10.42.1.86 kc-lab-2 <none> <none>
keycloak-1 1/1 Running 0 3m33s 10.42.0.39 kc-lab-1 <none> <none>
oauth2-proxy-d9b587458-7x9jt 1/1 Running 0 73m 10.42.1.78 kc-lab-2 <none> <none>
oauth2-proxy-d9b587458-njxdw 1/1 Running 0 73m 10.42.1.79 kc-lab-2 <none> <none>
postgres-7b474b88c8-kvt96 1/1 Running 0 4m6s 10.42.1.85 kc-lab-2 <none> <none>
redis-f94bc6dbf-s674z 1/1 Running 0 141m 10.42.1.63 kc-lab-2 <none> <none>
=== b5 2 Redis 의 내용과 영속화 설정을 본다 ===
PONG
save
3600 1 300 100 60 10000
appendonly
yes
=== b5 3 ① 파드에 볼륨이 붙어 있는가 ===
[{"name":"data","persistentVolumeClaim":{"claimName":"redis-data"}},{"name":"kube-api-access-g4scs","projected":{"defaultMode":420,"sources":[{"serviceAccountToken":{"expirationSeconds":3607,"path":"token"}},{"configMap":{"items":[{"key":"ca.crt","path":"ca.crt"}],"name":"kube-root-ca.crt"}},{"downwardAPI":{"items":[{"fieldRef":{"apiVersion":"v1","fieldPath":"metadata.namespace"},"path":"namespace"}]}}]}}]
=== b5 3 ② 어디에 붙었는지와 PVC 상태를 본다 ===
[{"mountPath":"/data","name":"data"},{"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount","name":"kube-api-access-g4scs","readOnly":true}]
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
postgres-data Bound pvc-fe269834-3dd1-4b1f-82f6-a53413310a30 5Gi RWO local-path <unset> 4h38m
redis-data Bound pvc-76a6565c-ca60-4a93-b08f-51400c5680ce 1Gi RWO local-path <unset> 168m
=== b5 4 세 경로의 상태 코드를 뽑는다 ===
/ 000
/bff/token-boundary 000
/actuator/health 000
=== (진단) 이름이 무엇으로 풀리나 ===
100.83.212.4 app1.hyeonworks.com
=== (진단) 엣지 주소를 짚어 같은 셋 ===
/ 200
/bff/token-boundary 302
/actuator/health 200
=== b5 5 ① health 그룹 셋의 본문을 받는다 ===
=== (진단) 같은 셋을 엣지 주소로 ===
{"status":"UP","groups":["liveness","readiness"],"components":{"db":{"status":"UP","details":{"database":"PostgreSQL","validationQuery":"isValid()"}},"diskSpace":{"status":"UP","details":{"total":20922114048,"free":13518041088,"threshold":10485760,"path":"/app/.","exists":true}},"livenessState":{"status":"UP"},"ping":{"status":"UP"},"readinessState":{"status":"UP"},"redis":{"status":"UP","details":{"version":"7.4.11"}},"ssl":{"status":"UP","details":{"validChains":[],"invalidChains":[]}}}}
{"status":"UP"}
{"status":"UP"}
=== b5 5 ② kubelet 이 보는 경로를 확인한다 ===
/actuator/health/readiness
@@ -0,0 +1,56 @@
=== b5 주입 1 시각을 남기고 Redis 를 0대로 내린다 ===
18:14:47 정지
deployment.apps/redis scaled
pod/redis-f94bc6dbf-s674z condition met
=== b5 주입 검증 ① Redis 가 0대인가 ===
No resources found in keycloak-lab namespace.
NAME READY UP-TO-DATE AVAILABLE AGE
redis 0/0 0 0 168m
=== b5 주입 검증 ② BFF 로그에서 연결 시도를 찾는다 ===
2026-09-17T09:09:46.531Z WARN 1 --- [keycloak-bff] [l-1:housekeeper] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@13f41550 (This connection has been closed.). Possibly consider using a shorter maxLifetime value.
2026-09-17T09:09:54.173Z WARN 1 --- [keycloak-bff] [l-1:housekeeper] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@75a79757 (This connection has been closed.). Possibly consider using a shorter maxLifetime value.
2026-09-17T09:09:55.362Z WARN 1 --- [keycloak-bff] [l-1:housekeeper] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@43aef356 (This connection has been closed.). Possibly consider using a shorter maxLifetime value.
2026-09-17T09:09:57.318Z WARN 1 --- [keycloak-bff] [l-1:housekeeper] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@7be4bd3b (This connection has been closed.). Possibly consider using a shorter maxLifetime value.
2026-09-17T09:09:57.955Z WARN 1 --- [keycloak-bff] [l-1:housekeeper] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@32b0803f (This connection has been closed.). Possibly consider using a shorter maxLifetime value.
2026-09-17T09:10:19.741Z WARN 1 --- [keycloak-bff] [l-1:housekeeper] com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@5b2e9f1c (This connection has been closed.). Possibly consider using a shorter maxLifetime value.
2026-09-17T09:13:13.527Z INFO 1 --- [keycloak-bff] [xecutorLoop-1-2] i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was redis.keycloak-lab.svc/10.43.44.209:6379
2026-09-17T09:13:13.528Z INFO 1 --- [keycloak-bff] [xecutorLoop-1-1] i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was redis.keycloak-lab.svc/10.43.44.209:6379
2026-09-17T09:13:13.529Z WARN 1 --- [keycloak-bff] [ioEventLoop-4-2] i.l.core.protocol.ConnectionWatchdog : Cannot reconnect to [redis.keycloak-lab.svc/<unresolved>:6379]: Connection refused: redis.keycloak-lab.svc/10.43.44.209:6379
2026-09-17T09:13:13.530Z WARN 1 --- [keycloak-bff] [ioEventLoop-4-1] i.l.core.protocol.ConnectionWatchdog : Cannot reconnect to [redis.keycloak-lab.svc/<unresolved>:6379]: Connection refused: redis.keycloak-lab.svc/10.43.44.209:6379
=== b5 주입 검증 ③ 엉뚱한 것을 죽이지 않았는지 본다 ===
NAME READY STATUS RESTARTS AGE
bff-5d97897676-nmlqs 1/1 Running 0 168m
bff-5d97897676-zvfhp 1/1 Running 0 168m
keycloak-0 1/1 Running 0 3m17s
keycloak-1 1/1 Running 0 3m50s
oauth2-proxy-d9b587458-7x9jt 1/1 Running 0 74m
oauth2-proxy-d9b587458-njxdw 1/1 Running 0 74m
postgres-7b474b88c8-kvt96 1/1 Running 0 4m23s
=== b5 관찰 세 경로를 다시 친다 (문서 그대로) ===
/ 000
/bff/token-boundary 000
/actuator/health 000
=== (진단) 엣지 주소를 짚어 같은 셋 ===
/ 200
/bff/token-boundary 000
/actuator/health 000
=== (진단) token-boundary 응답 본문 ===
[본문 끝]
=== b5 관찰 health 그룹 셋을 코드와 본문으로 본다 (엣지 주소를 짚어서) ===
health 000
health/readiness 200
health/liveness 200
{"status":"UP"}
=== b5 관찰 엔드포인트가 아직 ready 인지 본다 ===
NAME ADDR READY
bff-ptf2d [10.42.1.51],[10.42.0.22] true,true
=== (진단) /actuator/health 를 더 길게 기다려 본다 ===
health 503 total 60.046452
exit=0
{"status":"DOWN","groups":["liveness","readiness"],"components":{"db":{"status":"UP","details":{"database":"PostgreSQL","validationQuery":"isValid()"}},"diskSpace":{"status":"UP","details":{"total":20922114048,"free":13516791808,"threshold":10485760,"path":"/app/.","exists":true}},"livenessState":{"status":"UP"},"ping":{"status":"UP"},"readinessState":{"status":"UP"},"redis":{"status":"DOWN","details":{"error":"org.springframework.dao.QueryTimeoutException: Redis command timed out"}},"ssl":{"status":"UP","details":{"validChains":[],"invalidChains":[]}}}}
=== (진단) /bff/token-boundary 도 길게 ===
token-boundary 000 total 90.001609
exit=28
[exited with code 0]
@@ -0,0 +1,74 @@
=== b5 관찰 ① Redis 를 다시 올린다 ===
18:18:32 복구
deployment.apps/redis scaled
Waiting for deployment "redis" rollout to finish: 0 out of 1 new replicas have been updated...
Waiting for deployment "redis" rollout to finish: 0 of 1 updated replicas are available...
deployment "redis" successfully rolled out
=== b5 관찰 ② 회복했는지와 재시작 횟수를 본다 (엣지 주소를 짚어서) ===
/actuator/health 200
/bff/token-boundary 302
NAME READY STATUS RESTARTS AGE
bff-5d97897676-nmlqs 1/1 Running 0 173m
bff-5d97897676-zvfhp 1/1 Running 0 173m
=== b5 주입 2 ① 볼륨 참조를 떼고 롤아웃을 기다린다 ===
deployment.apps/redis patched
Waiting for deployment "redis" rollout to finish: 0 out of 1 new replicas have been updated...
Waiting for deployment "redis" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "redis" rollout to finish: 1 old replicas are pending termination...
deployment "redis" successfully rolled out
=== b5 주입 검증 ④ 볼륨이 정말 떨어졌는가 ===
NAME READY STATUS RESTARTS AGE
redis-547fb5fcc7-wcqg8 1/1 Running 0 11s
redis-f94bc6dbf-l9q6x 0/1 Completed 0 47s
[{"name":"kube-api-access-d7bbm","projected":{"defaultMode":420,"sources":[{"serviceAccountToken":{"expirationSeconds":3607,"path":"token"}},{"configMap":{"items":[{"key":"ca.crt","path":"ca.crt"}],"name":"kube-root-ca.crt"}},{"downwardAPI":{"items":[{"fieldRef":{"apiVersion":"v1","fieldPath":"metadata.namespace"},"path":"namespace"}]}}]}}]
=== (진단) volumeMounts 쪽도 ===
[{"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount","name":"kube-api-access-d7bbm","readOnly":true}]
=== b5 주입 2 ② AOF 를 켜고 키를 심는다 ===
OK
appendonly
yes
OK
1
total 12
drwxr-xr-x 3 redis redis 4096 Sep 17 09:17 .
drwxr-xr-x 1 root root 4096 Sep 17 09:17 ..
drwx------ 2 redis redis 4096 Sep 17 09:17 appendonlydir
=== b5 관찰 ③ 볼륨 없이 파드를 지우고 남은 것을 센다 ===
pod "redis-547fb5fcc7-wcqg8" deleted from keycloak-lab namespace
Waiting for deployment "redis" rollout to finish: 0 of 1 updated replicas are available...
deployment "redis" successfully rolled out
NAME READY STATUS RESTARTS AGE
redis-547fb5fcc7-4xv8m 1/1 Running 0 12s
0
appendonly
yes
=== b5 관찰 ④ 볼륨을 되돌리고 같은 시험을 다시 한다 ===
secret/bff-secrets configured
persistentvolumeclaim/redis-data unchanged
deployment.apps/redis configured
service/redis unchanged
deployment.apps/bff unchanged
service/bff unchanged
ingress.networking.k8s.io/bff unchanged
Waiting for deployment "redis" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "redis" rollout to finish: 1 old replicas are pending termination...
deployment "redis" successfully rolled out
appendonly
yes
OK
pod "redis-547fb5fcc7-4xv8m" deleted from keycloak-lab namespace
pod "redis-f94bc6dbf-lqrgp" deleted from keycloak-lab namespace
Waiting for deployment "redis" rollout to finish: 0 of 1 updated replicas are available...
deployment "redis" successfully rolled out
NAME READY STATUS RESTARTS AGE
redis-f94bc6dbf-b7zdk 1/1 Running 0 12s
4
written-on-pvc
=== b5 PVC 의 스토리지 클래스를 본다 ===
local-path
=== b5 B층 구성 요소의 지표가 있는가 ===
redis_up 시계열 0개
redis_connected_clients 시계열 0개
pg_up 시계열 0개
pg_stat_database_numbackends 시계열 0개
@@ -0,0 +1,39 @@
=== b5 복구 1 매니페스트를 다시 적용한다 ===
secret/bff-secrets configured
persistentvolumeclaim/redis-data unchanged
deployment.apps/redis unchanged
service/redis unchanged
deployment.apps/bff unchanged
service/bff unchanged
ingress.networking.k8s.io/bff unchanged
deployment "redis" successfully rolled out
=== b5 복구 2 실험 키만 지우고 남은 키를 본다 ===
1
bff:session:sessions:[REDACTED]
bff:session:sessions:[REDACTED]
bff:session:sessions:[REDACTED]
=== b5 복구 3 확인표 — Redis ===
NAME READY UP-TO-DATE AVAILABLE AGE
redis 1/1 1 1 174m
=== 확인표 볼륨 ===
[{"name":"data","persistentVolumeClaim":{"claimName":"redis-data"}},{"name":"kube-api-access-6mxzf","projected":{"defaultMode":420,"sources":[{"serviceAccountToken":{"expirationSeconds":3607,"path":"token"}},{"configMap":{"items":[{"key":"ca.crt","path":"ca.crt"}],"name":"kube-root-ca.crt"}},{"downwardAPI":{"items":[{"fieldRef":{"apiVersion":"v1","fieldPath":"metadata.namespace"},"path":"namespace"}]}}]}}]
=== 확인표 PVC ===
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
postgres-data Bound pvc-fe269834-3dd1-4b1f-82f6-a53413310a30 5Gi RWO local-path <unset> 4h44m
redis-data Bound pvc-76a6565c-ca60-4a93-b08f-51400c5680ce 1Gi RWO local-path <unset> 174m
=== 확인표 영속화 ===
appendonly
yes
=== 확인표 BFF ===
NAME READY STATUS RESTARTS AGE
bff-5d97897676-nmlqs 1/1 Running 0 174m
bff-5d97897676-zvfhp 1/1 Running 0 174m
=== 확인표 엔드포인트 ===
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
bff-ptf2d IPv4 8083 10.42.1.51,10.42.0.22 174m
=== 확인표 밖 (문서 그대로) ===
000
=== 확인표 밖 (엣지 주소를 짚어서) ===
200
=== (마무리) git 은 안 건드렸다 ===
git status exit=0
@@ -0,0 +1,25 @@
=== b6 ① kcadm 로그인 ===
Logging into http://localhost:8080 as user admin of realm master
exit=0
=== b6 ② 비밀번호 길이만 ===
19
=== b6 2단계 공급자 목록 ===
"id" : "5be506c2-8472-442b-8258-9c6692f45a13", "name" : "Trusted Hosts", "providerId" : "trusted-hosts"
"id" : "e1686b9f-15a2-4979-a302-8fc18a4d6b22", "name" : "Full Scope Disabled", "providerId" : "scope"
"id" : "6a0b8379-b2b1-4d2f-bc3b-fa1673723730", "name" : "Allowed Client Scopes", "providerId" : "allowed-client-templates"
"id" : "7dc6fe06-06c3-4d25-b93a-1fe245e4837c", "name" : "Consent Required", "providerId" : "consent-required"
"id" : "06777327-7c87-4ff4-92da-3335462ccb3b", "name" : "Allowed Client Scopes", "providerId" : "allowed-client-templates"
"id" : "3c9062a0-fc6d-4fef-870e-7bfc10270279", "name" : "Allowed Protocol Mapper Types", "providerId" : "allowed-protocol-mappers"
"id" : "a03ee0cb-765b-4eea-9d6c-e76bcee3adf4", "name" : "Allowed Registration Web Origins", "providerId" : "registration-web-origins"
"id" : "d47b58ee-7c34-4be0-a3f3-30f49b4577c1", "name" : "rsa-enc-generated", "providerId" : "rsa-enc-generated"
"id" : "807e653f-deaf-41cd-bbaa-5b19eaf74a2a", "name" : "hmac-generated-hs512", "providerId" : "hmac-generated"
"id" : "404e38a3-d3c3-4996-9228-200a912de08b", "name" : "Allowed Registration Web Origins", "providerId" : "registration-web-origins"
"id" : "27216d59-950c-4c8c-b0a8-e1d8108e0c0f", "name" : "aes-generated", "providerId" : "aes-generated"
"id" : "142c7871-4556-4faa-b9af-83d2376da5c6", "name" : "Max Clients Limit", "providerId" : "max-clients"
"id" : "a09ec151-6c8c-4e55-84c0-f02781221b83", "name" : "rsa-generated", "providerId" : "rsa-generated"
"id" : "212bde50-f539-4742-85bc-97f4a31a604a", "name" : "Allowed Protocol Mapper Types", "providerId" : "allowed-protocol-mappers"
=== b6 2단계 -q type=... 형태 ===
"name" : "rsa-enc-generated" "name" : "hmac-generated-hs512" "name" : "aes-generated" "name" : "rsa-generated"
@@ -0,0 +1,18 @@
=== b6 3단계를 문서 그대로: [lab host] 에서 공개 URL 로 ===
https 000 (Failed to connect to auth.hyeonworks.com:443 after 22 ms: Could not connect to server)
http 000 (Failed to connect to auth.hyeonworks.com:80 after 4 ms: Could not connect to server)
=== 같은 것을 dev 에서 http 로 ===
{"keys":[{"kid":"HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg","kty":"RSA","alg":"RS256","use":"sig","x5c":["MIICsTCCAZkC
=== 3단계 ② kid 만 ===
{"keys":[{"kid":"HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg"
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
=== 4단계 ① 키 단위로 잘라 RS256 만 센다 ===
1
=== 4단계 ② kcadm get keys ===
"kid" : "abfdb1a2-539c-4be6-b651-30e8a8e5c917", "status" : "ACTIVE", "algorithm" : "AES",
"kid" : "24d796a2-ca3c-477c-bf9f-c19f81e0e64c", "status" : "ACTIVE", "algorithm" : "HS512",
"kid" : "HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg", "status" : "ACTIVE", "algorithm" : "RS256",
"kid" : "5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI", "status" : "ACTIVE", "algorithm" : "RSA-OAEP",
@@ -0,0 +1,8 @@
=== 왜 못 닿나 ===
100.83.212.4 auth.hyeonworks.com
--- 호스트가 80/443 을 듣나
(리스너 없음)
=== 고쳐 치는 형태: --resolve 로 엣지를 직접 가리킨다 ===
{"keys":[{"kid":"HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg"
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
@@ -0,0 +1,7 @@
=== b6 5단계: 문서 그대로 [lab host] 에서 (https 공개 URL) ===
0자
=== 같은 것을 --resolve 로 고쳐서 ===
길이 0자
응답(토큰 없음): {"error":"invalid_grant","error_description":"Invalid user credentials"}
헤더:
@@ -0,0 +1,8 @@
=== 비밀번호를 B-6 이 박아 둔 값으로 맞춘다 ===
exit=0
=== b6 5·6단계 다시 ===
5단계 길이: 1406자
6단계 헤더: {"alg":"RS256","typ" : "JWT","kid" : "HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg"}
6단계 kid : HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg
(토큰은 랩 호스트의 /tmp/old.jwt 에만 둔다)
@@ -0,0 +1,30 @@
=== 5. 옛 토큰 ===
1406자 · kid HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg
=== 7. 지금 통하는가 ===
old 401
=== 8. priority 200 공급자 추가 ===
Created new component with id 'a8b5aecc-ffb6-4b17-8d5b-3eef5ecb9b72'
16:19:04 추가
=== 9. JWKS 에 옛 키가 남아 있는가 ===
{"keys":[{"kid":"iq4fSn_kovyqBsgaemqsVcsRJlWIYO5HTyiTO5WI9T4"
{"kid":"HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg"
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
RS256 키 수: 2
=== 10. 새 토큰의 kid ===
1406자 · kid iq4fSn_kovyqBsgaemqsVcsRJlWIYO5HTyiTO5WI9T4
=== 11. 둘 다 통하는가 ===
old 401
new 401
exp 1789629509 · now 1789629545
=== 12. 지울 대상 ===
지울 id: a09ec151-6c8c-4e55-84c0-f02781221b83
=== 13. 옛 공급자 제거 ===
16:19:07 제거
=== 14. JWKS 에서 사라졌는가 ===
{"keys":[{"kid":"iq4fSn_kovyqBsgaemqsVcsRJlWIYO5HTyiTO5WI9T4"
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
RS256 키 수: 1
=== 15. 두 토큰을 다시 ===
old 401
new 401
exp 1789629509 · now 1789629547
@@ -0,0 +1,47 @@
=== echo 가 왜 401 을 내나 ===
at java.base/sun.nio.ch.Net.connect0(Native Method) ~[na:na]
at java.base/sun.nio.ch.Net.connect(Unknown Source) ~[na:na]
at java.base/sun.nio.ch.Net.connect(Unknown Source) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.connect(Unknown Source) ~[na:na]
at java.base/java.net.SocksSocketImpl.connect(Unknown Source) ~[na:na]
at java.base/java.net.Socket.connect(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.SSLSocketImpl.connect(Unknown Source) ~[na:na]
at java.base/sun.security.ssl.BaseSSLSocketImpl.connect(Unknown Source) ~[na:na]
at java.base/sun.net.NetworkClient.doConnect(Unknown Source) ~[na:na]
at java.base/sun.net.www.http.HttpClient.openServer(Unknown Source) ~[na:na]
at java.base/sun.net.www.http.HttpClient.openServer(Unknown Source) ~[na:na]
at java.base/sun.net.www.protocol.https.HttpsClient.<init>(Unknown Source) ~[na:na]
at java.base/sun.net.www.protocol.https.HttpsClient.New(Unknown Source) ~[na:na]
at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(Unknown Source) ~[na:na]
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(Unknown Source) ~[na:na]
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(Unknown Source) ~[na:na]
at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(Unknown Source) ~[na:na]
at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source) ~[na:na]
at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(Unknown Source) ~[na:na]
at org.springframework.http.client.SimpleClientHttpRequest.executeInternal(SimpleClientHttpRequest.java:79) ~[spring-web-6.2.19.jar!/:6.2.19]
at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:88) ~[spring-web-6.2.19.jar!/:6.2.19]
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:81) ~[spring-web-6.2.19.jar!/:6.2.19]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:900) ~[spring-web-6.2.19.jar!/:6.2.19]
... 101 common frames omitted
=== echo 의 issuer 설정 ===
[{"name":"SERVER_PORT","value":"8081"},{"name":"SERVER_FORWARD_HEADERS_STRATEGY","value":"native"},{"name":"JAVA_TOOL_OPTIONS","value":"-XX:MaxRAMPercentage=70"},{"name":"SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI","value":"https://auth.hyeonworks.com/realms/keycloak-patterns"},{"name":"SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI","value":"https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs"}]
=== 401 의 WWW-Authenticate ===
HTTP/1.1 401 Unauthorized
Server: nginx/1.22.1
Date: Thu, 17 Sep 2026 07:17:43 GMT
Content-Length: 0
Connection: keep-alive
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Expires: 0
Pragma: no-cache
=== realm 의 accessTokenLifespan ===
{
"accessTokenLifespan" : 60,
"ssoSessionIdleTimeout" : 1800
}=== 시계 차이 ===
host 1789629559
kc-0 1789629465
@@ -0,0 +1,18 @@
=== 기계마다의 시각 (같은 순간에 친다) ===
lab host 1789629566 07:19:26
kc-lab-1 1789629472 07:17:52
kc-lab-2 1789629473 07:17:53
kc-lab-edge 1789629473 07:17:53
kc-0 pod 1789629473
--- 시간 동기 상태
== kc-lab-1
NTP=yes
NTPSynchronized=yes
TimeUSec=Thu 2026-09-17 07:17:53 UTC
== kc-lab-2
NTP=yes
NTPSynchronized=yes
TimeUSec=Thu 2026-09-17 07:17:54 UTC
== lab host
NTP=no
NTPSynchronized=no
@@ -0,0 +1,15 @@
=== b6 원상복구 확인표 ===
--- 서명 키
{"keys":[{"kid":"iq4fSn_kovyqBsgaemqsVcsRJlWIYO5HTyiTO5WI9T4"
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
--- 파드
echo-cd47dccc6-nq62j 1/1 Running 0 41m
echo-cd47dccc6-q46lk 1/1 Running 0 41m
keycloak-0 1/1 Running 0 24m
keycloak-1 1/1 Running 0 25m
--- 공급자 목록
"name" : "rsa-enc-generated"
"name" : "rsa-rotated"
--- 새 토큰 + /api/me
토큰 1406자
/api/me 401
@@ -0,0 +1,10 @@
=== echo 를 다시 띄워 JWKS 를 받게 한다 ===
deployment "echo" successfully rolled out
=== b6 7단계 대조군: 토큰이 지금 통하는가 ===
토큰 0자
old 000
=== echo 로그에 JWKS 오류가 남아 있나 ===
0
0
@@ -0,0 +1,7 @@
=== b6 5·7단계 (dev 에서) ===
5단계 토큰 1406자
6단계 kid iq4fSn_kovyqBsgaemqsVcsRJlWIYO5HTyiTO5WI9T4
7단계 대조군:
old 200
(본문과 WWW-Authenticate)
HTTP/2 200
@@ -0,0 +1,18 @@
=== 출발 상태: 지금 있는 RSA 공급자 ===
}, { "id" : "a8b5aecc-ffb6-4b17-8d5b-3eef5ecb9b72", "name" : "rsa-rotated",
=== 5. 옛 토큰 ===
1406자 · kid iq4fSn_kovyqBsgaemqsVcsRJlWIYO5HTyiTO5WI9T4
=== 7. 대조군 ===
old 200
=== 8. 더 높은 우선순위 공급자를 더한다 (기존이 200 이라 300 을 준다) ===
Created new component with id '958e80bd-3b9f-4b05-9d9a-011b8e4ef54a'
=== 9. JWKS 에 옛 키가 남아 있는가 ===
{"keys":[{"kid":"mL3CkHOfWatmZa6b3c_aFUWrY35EI1WmjxTc1ydpqGc"
{"kid":"iq4fSn_kovyqBsgaemqsVcsRJlWIYO5HTyiTO5WI9T4"
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
RS256 키 수: 2
=== 10. 새 토큰의 kid ===
kid mL3CkHOfWatmZa6b3c_aFUWrY35EI1WmjxTc1ydpqGc
=== 11. 둘 다 통하는가 ===
old 200
new 200
@@ -0,0 +1,14 @@
=== 12. 지울 대상 (rsa-rotated = 옛 키) ===
a8b5aecc-ffb6-4b17-8d5b-3eef5ecb9b72
=== 다시 옛 토큰을 받는다 (수명 안에 끝내려고) ===
옛 kid mL3CkHOfWatmZa6b3c_aFUWrY35EI1WmjxTc1ydpqGc
새 kid mL3CkHOfWatmZa6b3c_aFUWrY35EI1WmjxTc1ydpqGc
※ 둘 다 지금은 새 키로 서명된다 — 옛 키 토큰은 위 8~11 에서 받은 것이 정본이다
=== 13. 옛 공급자를 지운다 ===
07:52:46 제거
=== 14. JWKS 에서 사라졌는가 ===
{"keys":[{"kid":"mL3CkHOfWatmZa6b3c_aFUWrY35EI1WmjxTc1ydpqGc"
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
RS256 키 수: 1
=== 15. 두 토큰을 다시 ===
새키 토큰 200
@@ -0,0 +1,19 @@
=== 5. 옛 키로 서명된 토큰 (이 값을 끝까지 안 건드린다) ===
1406자 · kid mL3CkHOfWatmZa6b3c_aFUWrY35EI1WmjxTc1ydpqGc
=== 7. 대조군 ===
old 200
=== 8. priority 400 공급자를 더한다 ===
Created new component with id '7aa3832c-787a-4e9b-9eda-2ec72c4c0b97'
=== 9·10 ===
RS256 키 수: 2
새 토큰 kid AS65-ZKZDmxLFCfxk2agmmHQpM2jdT4xjP-To6r5SRM
=== 11. 겹치는 구간 ===
old 200
new 200
=== 13. 옛 공급자(priority 300) 제거 ===
지울 id: 958e80bd-3b9f-4b05-9d9a-011b8e4ef54a
=== 14·15 ===
RS256 키 수: 1
old 200
new 200
경과 6초 (토큰 수명 60초)
@@ -0,0 +1,14 @@
=== 지금 JWKS 에 있는 kid ===
{"keys":[{"kid":"AS65-ZKZDmxLFCfxk2agmmHQpM2jdT4xjP-To6r5SRM"
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
=== 사라진 키로 서명된 토큰을 다시 만들 수는 없다 — 지금 받는 토큰은 살아 있는 키다 ===
지금 토큰 kid AS65-ZKZDmxLFCfxk2agmmHQpM2jdT4xjP-To6r5SRM
=== 16. echo 를 다시 띄워 캐시를 비운다 ===
deployment "echo" successfully rolled out
재시작 34초
=== 재시작 뒤 현재 토큰 ===
now 200
fresh 200
@@ -0,0 +1,16 @@
5. 옛 토큰 kid AS65-ZKZDmxLFCfxk2agmmHQpM2jdT4xjP-To6r5SRM
[+0초]
7. 대조군 old 200
8. Created new component with id '066d3957-9b0e-411f-a36c-87ed17a18815'
10. 새 토큰 kid yHLkV7okKrLif-ndb01imOfaHze94lrllFgITCtoQmc
11. old 200 new 200
[+2초]
13. 옛 공급자 제거 (7aa3832c-787a-4e9b-9eda-2ec72c4c0b97)
[+5초]
14. RS256 키 수: 1
15. old 200 new 200
[+6초]
16. echo 재시작
[+39초]
16 뒤. old 401 new 504
[+70초]
@@ -0,0 +1,12 @@
=== echo 가 준비됐는지 먼저 ===
echo-85b6ffd88f-grm5g 1/1 Running 0 75s
echo-85b6ffd88f-nnvpt 1/1 Running 0 59s
옛 토큰 kid yHLkV7okKrLif-ndb01imOfaHze94lrllFgITCtoQmc
Created new component with id '9af4a602-1e5c-47d7-8c52-6fd7667a5261'
제거 완료 [+5초] · JWKS RS256 1개
--- 제거 뒤 옛 토큰을 10초마다
[+6초] old=401
[+16초] old=200
[+26초] old=401
[+36초] old=200
[+47초] old=401
@@ -0,0 +1,9 @@
=== 옛 키로 서명된 토큰을 하나 만들고 즉시 회전+제거, 그다음 파드별로 친다 ===
옛 kid aJCNYKhd4LWm6fTcmllP3dbq_incT5Jww6CzNj92WmU
Created new component with id '6689ab97-c4de-4b23-8ad7-c66ae4759113'
제거 완료
--- 파드마다 직접 (클러스터 안에서, 파드 IP 로)
echo-85b6ffd88f-grm5g 10.42.1.76 up
echo-85b6ffd88f-nnvpt 10.42.0.35 up
--- 같은 토큰으로 8번 연속 (라운드로빈)
401 200 401 200 401 200 401 200
@@ -0,0 +1,20 @@
=== B-7 1단계: Grafana Ingress 를 백업한다 ===
30 /home/donghyeon/grafana-ingress-backup.yaml
=== B-7 2단계 ①② ===
ingress.networking.k8s.io "grafana" deleted from observability namespace
secret/oauth2-proxy-secrets configured
deployment.apps/oauth2-proxy created
service/oauth2-proxy created
ingress.networking.k8s.io/oauth2-proxy created
Waiting for deployment "oauth2-proxy" rollout to finish: 0 of 2 updated replicas are available...
error: timed out waiting for the condition
=== 파드 상태와 실제 오류 ===
oauth2-proxy-6c56c4c94f-vncmx 0/1 CrashLoopBackOff 4 (6s ago) 91s
oauth2-proxy-6c56c4c94f-zxg52 0/1 CrashLoopBackOff 3 (48s ago) 91s
---
[2026/09/17 07:37:18] [provider.go:55] Performing OIDC Discovery...
[2026/09/17 07:37:18] [main.go:59] ERROR: Failed to initialise OAuth2 Proxy: error initialising provider: could not create provider data: error building OIDC ProviderVerifier: could not get verifier builder: error while discovery OIDC configuration: failed to discover OIDC configuration: error performing request: Get "https://auth.hyeonworks.com/realms/keycloak-patterns/.well-known/openid-configuration": dial tcp 192.168.122.10:443: connect: connection refused
[2026/09/17 07:36:36] [provider.go:55] Performing OIDC Discovery...
[2026/09/17 07:36:36] [main.go:59] ERROR: Failed to initialise OAuth2 Proxy: error initialising provider: could not create provider data: error building OIDC ProviderVerifier: could not get verifier builder: error while discovery OIDC configuration: failed to discover OIDC configuration: error performing request: Get "https://auth.hyeonworks.com/realms/keycloak-patterns/.well-known/openid-configuration": dial tcp 192.168.122.10:443: connect: connection refused
@@ -0,0 +1,15 @@
=== B-7 되돌리기 ② 빌린 Ingress 를 걷고 Grafana 것을 올린다 ===
ingress.networking.k8s.io "oauth2-proxy" deleted from keycloak-lab namespace
ingress.networking.k8s.io/grafana created
deployment.apps "oauth2-proxy" deleted from keycloak-lab namespace
Error from server (NotFound): deployments.apps "svc" not found
Error from server (NotFound): deployments.apps "oauth2-proxy" not found
=== 돌아왔는지 ===
header-lab echo traefik app1.hyeonworks.com 192.168.122.11,192.168.122.12 80 61m
keycloak-lab bff traefik app1.hyeonworks.com 192.168.122.11,192.168.122.12 80 73m
keycloak-lab keycloak traefik auth.hyeonworks.com 192.168.122.11,192.168.122.12 80 3h3m
observability grafana traefik app2.hyeonworks.com 192.168.122.11,192.168.122.12 80 1s
---
0
oauth2-proxy 파드 0
@@ -0,0 +1,21 @@
=== 쌓인 RSA 공급자 정리 (가장 높은 것 하나만 남긴다) ===
"id" : "6689ab97-c4de-4b23-8ad7-c66ae4759113", "name" : "rsa-rot-6",
"id" : "d47b58ee-7c34-4be0-a3f3-30f49b4577c1", "name" : "rsa-enc-generated",
=== B-7 1단계: Grafana Ingress 백업 ===
백업 30줄
=== B-7 2단계: Grafana 를 내리고 oauth2-proxy 배포 ===
ingress.networking.k8s.io "grafana" deleted from observability namespace
secret/oauth2-proxy-secrets configured
deployment.apps/oauth2-proxy created
service/oauth2-proxy created
ingress.networking.k8s.io/oauth2-proxy created
Waiting for deployment "oauth2-proxy" rollout to finish: 0 out of 2 new replicas have been updated...
Waiting for deployment "oauth2-proxy" rollout to finish: 0 of 2 updated replicas are available...
Waiting for deployment "oauth2-proxy" rollout to finish: 1 of 2 updated replicas are available...
deployment "oauth2-proxy" successfully rolled out
=== 3단계: replica 와 배치 ===
oauth2-proxy-6c56c4c94f-gq484 1/1 Running 0 12s 10.42.1.77 kc-lab-2 <none> <none>
oauth2-proxy-6c56c4c94f-szr6j 1/1 Running 0 12s 10.42.0.36 kc-lab-1 <none> <none>
@@ -0,0 +1,18 @@
=== B-7 4단계: 진입점 두 곳이 갈라지나 ===
https://app1.hyeonworks.com/ 200
https://app2.hyeonworks.com/ 302
=== B-7 7단계: app2 로 로그인한다 ===
도착: https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/auth?app
form:
로그인 결과: 000
head: cannot open '/tmp/p2b.html' for reading: No such file or directory
본문 앞:
=== 쿠키 (값은 길이만) ===
app2.hyeonworks.com _oauth2_proxy_csrf 176자
=== B-7 8단계: Redis 에 세션이 들어갔나 ===
bff:session:sessions:b66634a2-1381-4071-8ceb-0a006fbe1327
bff:session:sessions:c5f7b0c7-cd17-4749-bf3e-6d42c814bb1c
dbsize: 2
@@ -0,0 +1,13 @@
=== realm 에 등록된 클라이언트 ===
account
account-console
admin-cli
bff-confidential
broker
realm-management
security-admin-console
=== oauth2-proxy 가 쓰는 client_id ===
=== 이 클라이언트를 만드는 setup 문서가 있나 ===
docs/keycloak-session-store/tech-log-studio/trust-handed-over-at-the-edge/setup/setup-reproduce-c2-backchannel-logout.md
@@ -0,0 +1,11 @@
=== 어느 문서에도 없는 한 단계: oauth2-proxy 클라이언트를 만든다 ===
클라이언트 비밀 길이: 16자
Created new client with id 'f7bed9b9-8fb5-4b3d-9253-c574c0b09739'
=== 만들어졌나 ===
[ {
"clientId" : "oauth2-proxy",
"enabled" : true,
"redirectUris" : [ "https://app2.hyeonworks.com/oauth2/callback" ],
"publicClient" : false
} ]
@@ -0,0 +1,41 @@
=== B-7 7단계: app2 로 로그인 ===
로그인 폼: https://auth.hyeonworks.com/realms/keycloak-patterns/login-actions/authenticate?session_co
로그인 결과: 401 · 본문 0바이트
=== 업스트림이 받은 헤더 (echo 가 그대로 돌려준다) ===
{
"headers" : {
"host" : [ "app2.hyeonworks.com" ],
"user-agent" : [ "curl/8.5.0" ],
"accept" : [ "*/*" ],
"accept-encoding" : [ "gzip" ],
"cookie" : [ "_oauth2_proxy=djIuWDI5aGRYUm9NbDl3Y205NGVTMDFaR05rTURjNVpUQmlaVE5sWWpoaE9UVXdZelptWm1FMVpHTTVZamhpTVEuWDhMSFFnVnYtenRXV0hzTFMzVVN0QQ==|1789631924|yGT15uSP74BddTXAVgSvVGaz65hygKtaju9CDM3lHlc=" ],
"x-forwarded-email" : [ "labuser@example.invalid" ],
"x-forwarded-host" : [ "app2.hyeonworks.com" ],
"x-forwarded-port" : [ "443" ],
"x-forwarded-preferred-username" : [ "labuser" ],
"x-forwarded-proto" : [ "https" ],
"x-forwarded-server" : [ "traefik-5d6fcf895-xmqp6" ],
"x-forwarded-user" : [ "07d3be2a-9f22-4779-a211-e530c8b7ff3b" ],
"x-real-ip" : [ "192.168.122.1" ]
},
"remoteAddr" : "192.168.122.1",
"localAddr" : "10.42.0.35",
"scheme" : "https",
"secure" : true,
"serverName" : "app2.hyeonworks.com",
"serverPort" : 443,
"requestUrl" : "https://app2.hyeonworks.com/api/echo"
}
=== 쿠키 (값은 길이만) ===
auth.hyeonworks.com KC_AUTH_SESSION_HASH 64자
auth.hyeonworks.com AUTH_SESSION_ID 165자
auth.hyeonworks.com KEYCLOAK_IDENTITY 594자
auth.hyeonworks.com KEYCLOAK_SESSION 64자
app2.hyeonworks.com _oauth2_proxy 176자
=== B-7 8단계: Redis ===
_oauth2_proxy-5dcd079e0be3eb8a950c6ffa5dc9b8b1
bff:session:sessions:b66634a2-1381-4071-8ceb-0a006fbe1327
bff:session:sessions:c5f7b0c7-cd17-4749-bf3e-6d42c814bb1c
dbsize=3
@@ -0,0 +1,21 @@
=== 주입 2 ① env 이름 순서 ===
OAUTH2_PROXY_CLIENT_SECRET OAUTH2_PROXY_COOKIE_SECRET
=== 회전 전 대조군: 지금 쿠키가 통하는가 ===
회전 전 200
=== 주입 2 ② A → B 로 바꾼다 ===
07:58:58 UTC 회전
deployment.apps/oauth2-proxy patched
deployment "oauth2-proxy" successfully rolled out
=== 주입 검증 1·2·3 ===
참조: COOKIE_SECRET_B
Redis 세션: _oauth2_proxy-5dcd079e0be3eb8a950c6ffa5dc9b8b1
파드:
oauth2-proxy-d9b587458-7x9jt 1/1 Running 0 25s
oauth2-proxy-d9b587458-njxdw 1/1 Running 0 12s
=== 관찰: 회전 전 쿠키가 아직 통하는가 ===
회전 뒤 302
(리다이렉트 목적지) https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/auth?approval_prompt=force&client_id=oauth2-proxy&redirect_uri=https%3A%2F%2Fapp2.hyeonworks.com%2Foauth2%2Fcallback&response_type=code&scope=openid+email+profile&state=5V_qNK4dsuw6h4jJTooqFYlaVJ5WarqCuDpsGlpHgAw%3A%2Fapi%2Fecho
@@ -0,0 +1,10 @@
=== B-7a 6단계: R 함수와 스캔 루프 (오래 미검증이던 블록) ===
_oauth2_proxy-5dcd079e0be3eb8a950c6ffa5dc9b8b1 type=string ttl=3551 len=3457
=== B-7a: TTL 이 줄어드는가 · 갱신되는가 (30초 간격 세 번) ===
08:01:07
_oauth2_proxy-5dcd079e0be3eb8a950c6ffa5dc9b8b1 ttl=3551
08:01:37
_oauth2_proxy-5dcd079e0be3eb8a950c6ffa5dc9b8b1 ttl=3521
08:02:07
_oauth2_proxy-5dcd079e0be3eb8a950c6ffa5dc9b8b1 ttl=3490
@@ -0,0 +1,29 @@
=== C-1 주입 1: app1 에 로그인한다 ===
app1 로그인 200
{"pattern":"AP3-backend-for-frontend","principal":"labuser","accessTokenStoredOnServer":true,"refreshTokenStoredOnServer
=== C-1 주입 3: 같은 창의 새 탭에서 app2 를 연다 (재인증이 뜨는가) ===
도착: https://app2.hyeonworks.com/api/echo
로그인 폼이 떴나: 0 (0 이면 재인증 없이 통과)
본문 앞: { "headers" : { "host" : [ "app2.hyeonworks.com" ], "user-agent" : [ "curl/8.5.0
=== C-1 주입검증 2: 어느 클라이언트가 붙었나 ===
[ {
"offline" : "0",
"clientId" : "bff-confidential",
"active" : "15",
"id" : "7ae3362a-2bed-4902-b6af-d7307ea4ad0b"
}, {
"offline" : "0",
"clientId" : "oauth2-proxy",
"active" : "2",
"id" : "f7bed9b9-8fb5-4b3d-9253-c574c0b09739"
} ]
=== C-1 주입검증 3: 저장소에 무엇이 늘었나 ===
_oauth2_proxy-9aa2915c715c48b09a8916015a6f3801
bff:session:sessions:0daf4915-a8a6-4b8e-bc97-5e58627eae23
bff:session:sessions:b66634a2-1381-4071-8ceb-0a006fbe1327
_oauth2_proxy-5dcd079e0be3eb8a950c6ffa5dc9b8b1
bff:session:sessions:c5f7b0c7-cd17-4749-bf3e-6d42c814bb1c
dbsize=5
@@ -0,0 +1,13 @@
=== C-1 주입검증 1: user session 과 client session 수 ===
YwQBgnWh4dmkiknXlBVdbL6f | keycloak-patterns | 1
1O7AtenktEMp_0MFgwFtev8o | keycloak-patterns | 1
c4GCKTLjyfC52_JjLJuUf2GR | keycloak-patterns | 1
Ur1NWoD6WiHMBX-oi1DvDrEe | keycloak-patterns | 1
q2LCg3GRdG8acMlSYRXdJ-XF | keycloak-patterns | 1
rjpU8hkBoDdgWG6duNzadrgn | keycloak-patterns | 1
tIbyDHzlcDUxD6Ke65juXHlE | keycloak-patterns | 1
zOVsxAt2PbCKF2fIOD8_YMNe | keycloak-patterns | 1
4i6Q7qb3Y_7Ib4mQzzsmTkzQ | keycloak-patterns | 1
V0mkutuu-0tNdmvHBfWucB9j | keycloak-patterns | 2
(16 rows)
@@ -0,0 +1,15 @@
Server: 10.43.0.10
Address: 10.43.0.10:53
Name: app1.hyeonworks.com
Address: 192.168.122.10
--- HTTPS ---
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.
app1 200
curl exit=0
pod "c2probe" deleted from keycloak-lab namespace
=== NetworkPolicy 가 있나 ===
No resources found in keycloak-lab namespace.
@@ -0,0 +1,11 @@
-rw-r--r-- 1 root root 4882 Sep 4 08:22 fullchain2.pem
-rw-r--r-- 1 root root 4882 Sep 4 12:29 fullchain3.pem
-rw------- 1 root root 241 Sep 3 01:47 privkey1.pem
-rw------- 1 root root 241 Sep 4 08:22 privkey2.pem
-rw------- 1 root root 241 Sep 4 12:29 privkey3.pem
--- nginx worker PID ---
2629 4712
--- 지금 서빙하는 인증서의 notBefore ---
notBefore=Sep 4 11:29:18 2026 GMT
notAfter=Dec 3 11:29:17 2026 GMT
serial=06F3E0EF4D1BB03DE58130EAAD1176101373
@@ -0,0 +1,34 @@
=== D-4 5단계: 타이머 ===
0 timers listed.
Pass --all to see loaded but inactive timers, too.
--- 엣지 쪽
Thu 2026-09-17 17:06:05 UTC 9h left - - certbot.timer certbot.service
=== D-4 6단계 ① ② ③ ===
[Unit]
Description=Certbot
Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html
Documentation=https://certbot.eff.org/docs
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-renew
PrivateTmp=true
---
[Unit]
Description=Run certbot twice daily
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=43200
Persistent=true
[Install]
WantedBy=timers.target
---
total 0
=== D-4 7단계를 문서 그대로: [test-server] 에서 ===
(줄 수: 3)
=== 같은 것을 엣지에서 ===
1065 1 11145 Thu Sep 17 04:27:39 2026 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
1106 1065 11135 Thu Sep 17 04:27:49 2026 nginx: worker process
@@ -0,0 +1,16 @@
certbot exit=0
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Renewing an existing certificate for auth.hyeonworks.com and 2 more domains
Hook 'deploy-hook' ran with error output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
2026/09/17 10:36:31 [notice] 4744#4744: signal process started
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded:
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -0,0 +1,15 @@
=== 디스크의 새 파일 ===
-rw------- 1 root root 241 Sep 4 08:22 privkey2.pem
-rw------- 1 root root 241 Sep 4 12:29 privkey3.pem
-rw------- 1 root root 241 Sep 17 10:36 privkey4.pem
=== 지금 서빙하는 인증서 ===
notBefore=Sep 17 09:37:58 2026 GMT
notAfter=Dec 16 09:37:57 2026 GMT
serial=065547991777D11A408CEA90D945DDA03DF1
=== nginx worker PID (갱신 전 2629 4712) ===
4745 4754
auth 302 verify=0
app1 200 verify=0
app2 302 verify=0
@@ -0,0 +1,26 @@
=== 1단계 출발점 워커 PID (문서대로 test-server) ===
(랩 호스트에서는 빈손이다)
--- 엣지에서
1065 1 11186 Thu Sep 17 04:27:39 2026 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
1106 1065 11177 Thu Sep 17 04:27:49 2026 nginx: worker process
=== 5단계 훅 파일을 만든다 ===
#!/bin/sh
nginx -t && nginx -s reload
=== 6단계 설치 ===
exit=0
=== 7단계 제자리에 있나 ===
total 4
-rwxr-xr-x 1 root root 38 Sep 17 07:34 reload-nginx.sh
=== 8단계 손으로 한 번 돌린다 ===
exit=0
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
2026/09/17 07:34:08 [notice] 2361#2361: signal process started
=== 8단계 뒤 워커 PID 재측정 ===
1065 1 11189 Thu Sep 17 04:27:39 2026 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
2362 1065 0 Thu Sep 17 07:34:08 2026 nginx: worker process
@@ -0,0 +1,19 @@
=== 엣지에 인증서가 생긴 뒤 dry-run 이 무엇을 하나 ===
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Failed to renew certificate auth.hyeonworks.com with error: You should register before running non-interactively, or provide --agree-tos and --email <email_address> flags.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
All simulated renewals failed. The following certificates could not be renewed:
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (failure)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 renew failure(s), 0 parse failure(s)
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
exit=0
=== 엣지에 자격증명 파일이 있나 ===
ls: cannot access '/etc/letsencrypt/cloudflare.ini': No such file or directory
1
@@ -0,0 +1,15 @@
certbot exit=1
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Failed to renew certificate auth.hyeonworks.com with error: Missing command line flag or config entry for this setting:
Please choose an account
Choices: ['test-server@2026-09-03T01:50:44Z (66d5)', 'kc-lab-edge@2026-09-17T08:16:31Z (5df4)']
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
All simulated renewals failed. The following certificates could not be renewed:
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (failure)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 renew failure(s), 0 parse failure(s)
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
@@ -0,0 +1,6 @@
=== 엣지의 ACME 계정이 왜 둘인가 ===
*
stat: cannot statx '/etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/*/': No such file or directory
=== renewal 설정이 계정을 지목하나 ===
8:account = 8d53f9312e2a4c9cdd13620122cd8272
@@ -0,0 +1,10 @@
=== 운영 디렉터리의 계정들 ===
stat: cannot statx '$d': No such file or directory
/etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/8d53f9312e2a4c9cdd13620122cd8272/
=== renewal 이 지목한 계정이 그중에 있나 ===
/etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/8d53f9312e2a4c9cdd13620122cd8272
=== 각 계정의 regr.json 이 누구인가 ===
/etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/8d53f9312e2a4c9cdd13620122cd8272/regr.json
acct/3702274776
@@ -0,0 +1,6 @@
=== staging 디렉터리의 계정들 ===
5df435a72a89118f1699212efd5fe2fe
66d5d86599378a0b07936733587017a6
=== 운영 디렉터리의 계정 수 ===
1
@@ -0,0 +1,13 @@
certbot exit=0
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for auth.hyeonworks.com and 2 more domains
Waiting 30 seconds for DNS changes to propagate
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -0,0 +1,13 @@
certbot exit=0
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for auth.hyeonworks.com and 2 more domains
Waiting 30 seconds for DNS changes to propagate
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -0,0 +1,23 @@
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for auth.hyeonworks.com and 2 more domains
Waiting 30 seconds for DNS changes to propagate
Certbot failed to authenticate some domains (authenticator: dns-cloudflare). The Certificate Authority reported these problems:
Domain: auth.hyeonworks.com
Type: dns
Detail: During secondary validation: DNS problem: NXDOMAIN looking up TXT for _acme-challenge.auth.hyeonworks.com - check that a DNS record exists for this domain
Hint: The Certificate Authority failed to verify the DNS TXT records created by --dns-cloudflare. Ensure the above domains are hosted by this DNS provider, or try increasing --dns-cloudflare-propagation-seconds (currently 30 seconds).
Failed to renew certificate auth.hyeonworks.com with error: Some challenges have failed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
All simulated renewals failed. The following certificates could not be renewed:
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (failure)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 renew failure(s), 0 parse failure(s)
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
@@ -0,0 +1,9 @@
=== 훅 파일 ===
total 4
-rwxr-xr-x 1 root root 38 Sep 17 09:23 reload-nginx.sh
=== 성공한 dry-run 출력에 deploy-hook 이 몇 번 ===
0
=== certbot 판 ===
certbot 2.1.0
=== --run-deploy-hooks 플래그가 있나 ===
0
@@ -0,0 +1,21 @@
=== 밖에서 도메인으로 — 앞서 Host 헤더로 대신했던 것을 진짜 경로로 다시 잰다 ===
--- B-4 주입 (문서의 명령에서 https 만 http 로) ---
대조군 : (비면 정상)
동명 헤더 둘 : "x-auth-request-roles" : [ "admin", "editor" ]
값 안의 쉼표 : "x-auth-request-roles" : [ "role-with,comma" ]
신원 위조 :
"x-auth-request-email" : [ "attacker@evil.invalid" ]
"x-auth-request-user" : [ "attacker" ]
--- x-forwarded-* 가 밖에서 오면 무엇이 되나 ---
"x-forwarded-host" : [ "app1.hyeonworks.com" ]
"x-forwarded-port" : [ "80" ]
"x-forwarded-proto" : [ "http" ]
"x-forwarded-server" : [ "traefik-59b7647586-t8fc7" ]
"x-real-ip" : [ "10.42.0.1" ]
--- C-2 후보 셋 (밖에서) ---
/logout/connect/back-channel/keycloak 302
/backchannel-logout 302
/oauth2/sign_out 302
--- app2 (Grafana) 도 닿나 ---
app2 302
@@ -0,0 +1,41 @@
=== 엣지 nginx 는 무엇을 설정하나 ===
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header X-Forwarded-Port 80;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
=== 그런데 앱에 도착하는 것 ===
"x-forwarded-host" : [ "app1.hyeonworks.com" ]
"x-forwarded-port" : [ "80" ]
"x-forwarded-proto" : [ "http" ]
"x-forwarded-server" : [ "traefik-59b7647586-t8fc7" ]
"x-real-ip" : [ "10.42.1.0" ]
=== 내 주소는 무엇인가 ===
inet 100.83.212.4/32 scope global tailscale0
=== Traefik 이 신뢰하는 출발지 설정이 있나 ===
(위가 비면 기본값)
=== 저장소에 그 설정 파일이 있다 — 적용돼 있나 ===
# Make Traefik trust the X-Forwarded-* headers that the host nginx sets.
#
# Without this, Traefik rewrites every forwarded header from its own connection,
# which is plain HTTP on port 80. The application then sees scheme=http even
# though the browser connected over TLS. See docs/two-hop-proxy-header-contract.md.
#
# k3s installs Traefik through its bundled HelmChart, so values are overridden
# with a HelmChartConfig rather than by editing the deployment. k3s reconciles
# the chart and recreates the Traefik pod.
#
# kubectl apply -f deploy/lab/k8s/traefik-forwarded-headers.yaml
# kubectl -n kube-system rollout status deploy/traefik --timeout=180s
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
@@ -0,0 +1,43 @@
=== 적용 전 — 앱이 보는 것 ===
"x-forwarded-host" : [ "app1.hyeonworks.com" ]
"x-forwarded-port" : [ "80" ]
"x-forwarded-proto" : [ "http" ]
"x-forwarded-server" : [ "traefik-59b7647586-t8fc7" ]
"x-real-ip" : [ "10.42.0.1" ]
(x-forwarded-for 가 없다. x-real-ip 는 flannel 게이트웨이다)
=== 저장소의 설정 전문 ===
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
forwardedHeaders:
# Requests arriving from these sources keep their existing
# X-Forwarded-* values instead of having them rewritten.
#
# 10.42.0.0/16 is the pod CIDR. It is required because the traefik
# Service uses externalTrafficPolicy: Cluster, so svclb SNATs the
# traffic and Traefik sees a pod-network address rather than the
# host nginx address.
#
# The node/host range is deliberately absent. Because svclb SNATs,
# the host nginx address never reaches Traefik — measured, not assumed.
# Trusting a range that cannot appear only widens the surface.
#
# Trusting the whole pod CIDR still means any pod in the cluster could
# forge these headers, which is why echo-network-policy.yaml restricts
# who may reach the application at all.
trustedIPs:
- 10.42.0.0/16
websecure:
=== 적용한다 (파일 주석이 적어 둔 두 줄) ===
helmchartconfig.helm.cattle.io/traefik created
Waiting for deployment "traefik" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "traefik" rollout to finish: 1 old replicas are pending termination...
deployment "traefik" successfully rolled out
@@ -0,0 +1,10 @@
=== 적용 뒤 — 앱이 보는 것 ===
"x-forwarded-host" : [ "app1.hyeonworks.com" ]
"x-forwarded-port" : [ "80" ]
"x-forwarded-proto" : [ "http" ]
"x-forwarded-server" : [ "traefik-5d6fcf895-n8p4k" ]
"x-real-ip" : [ "192.168.122.1" ]
=== 내 tailnet 주소와 견준다 ===
"remoteAddr" : "192.168.122.1"
호스트의 tailscale0 = 100.83.212.4 / 엣지 게스트 = 192.168.122.10
@@ -0,0 +1,12 @@
=== 가설: Traefik 이 보는 출발지가 trustedIPs 밖이다 ===
앱이 본 remoteAddr = 192.168.122.1 (호스트의 virbr0)
설정의 trustedIPs = 10.42.0.0/16 (파드 대역)
=== 게스트 대역을 더해서 시험한다 (저장소 파일은 안 고친다) ===
helmchartconfig.helm.cattle.io/traefik configured
deployment "traefik" successfully rolled out
=== 다시 잰다 ===
"x-forwarded-proto" : [ "http" ]
"x-real-ip" : [ "192.168.122.1" ]
"remoteAddr" : "192.168.122.1"
@@ -0,0 +1,18 @@
=== kc-lab-edge 의 인증서 ===
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
No certificates found.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
---
ls: cannot access '/etc/letsencrypt/live/': No such file or directory
=== 엣지 nginx 가 443 을 듣나 ===
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1106,fd=5),("nginx",pid=1065,fd=5))
---
keycloak-lab
=== 밖에서 ===
https://auth.hyeonworks.com/realms/master 000
https://app1.hyeonworks.com/ 000
http://auth.hyeonworks.com/realms/master 200
@@ -0,0 +1,8 @@
=== 04번 1단계: 플러그인이 깔렸나 ===
* dns-cloudflare
* standalone
* webroot
=== 04번 3단계: 자격증명 파일이 있나 (값은 안 본다) ===
ls: cannot access '/etc/letsencrypt/cloudflare.ini': No such file or directory
wc: /etc/letsencrypt/cloudflare.ini: No such file or directory
@@ -0,0 +1,19 @@
=== 엣지 nginx 가 X-Forwarded-For 를 세우나 ===
8: listen 80 default_server;
9: server_name _;
12: proxy_pass http://k3s_traefik;
14: proxy_set_header Host $host;
15: proxy_set_header X-Forwarded-Host $host;
16: proxy_set_header X-Forwarded-Proto http;
17: proxy_set_header X-Forwarded-Port 80;
18: proxy_set_header X-Forwarded-For $remote_addr;
19: proxy_set_header X-Real-IP $remote_addr;
=== app1 은 엣지를 거치나 — 엣지 access log 마지막 3줄 ===
192.168.122.1 - - [17/Sep/2026:07:10:45 +0000] "GET /api/echo HTTP/1.1" 200 617 "-" "curl/8.5.0"
192.168.122.1 - - [17/Sep/2026:07:11:33 +0000] "GET /realms/master HTTP/1.1" 200 602 "-" "curl/8.5.0"
192.168.122.1 - - [17/Sep/2026:07:12:01 +0000] "GET /api/echo HTTP/1.1" 200 617 "-" "curl/8.5.0"
=== http 로 토큰 엔드포인트가 도나 (이게 되면 b·c 편은 TLS 없이 돈다) ===
well-known 200
token 401
@@ -0,0 +1,21 @@
=== 앱이 실제로 받은 헤더 전부 (trustedIPs 에 게스트 대역을 더한 상태) ===
{
"headers" : {
"host" : [ "app1.hyeonworks.com" ],
"user-agent" : [ "curl/8.5.0" ],
"accept" : [ "*/*" ],
"x-forwarded-host" : [ "app1.hyeonworks.com" ],
"x-forwarded-port" : [ "80" ],
"x-forwarded-proto" : [ "http" ],
"x-forwarded-server" : [ "traefik-697889c85-jqcjn" ],
"x-real-ip" : [ "192.168.122.1" ],
"accept-encoding" : [ "gzip" ]
},
"remoteAddr" : "192.168.122.1",
"localAddr" : "10.42.0.27",
"scheme" : "http",
"secure" : false,
"serverName" : "app1.hyeonworks.com",
"serverPort" : 80,
"requestUrl" : "http://app1.hyeonworks.com/api/echo"
}
@@ -0,0 +1,6 @@
=== 저장소 판으로 되돌린다 ===
helmchartconfig.helm.cattle.io/traefik configured
deployment "traefik" successfully rolled out
=== 호스트에서 출발지를 바꾸는 규칙이 있나 ===
--- libvirt
@@ -0,0 +1,46 @@
=== 저장소 판만 적용한 상태에서 다시 잰다 ===
ports:
web:
forwardedHeaders:
# Requests arriving from these sources keep their existing
# X-Forwarded-* values instead of having them rewritten.
#
# 10.42.0.0/16 is the pod CIDR. It is required because the traefik
# Service uses externalTrafficPolicy: Cluster, so svclb SNATs the
# traffic and Traefik sees a pod-network address rather than the
# host nginx address.
#
# The node/host range is deliberately absent. Because svclb SNATs,
# the host nginx address never reaches Traefik — measured, not assumed.
# Trusting a range that cannot appear only widens the surface.
#
# Trusting the whole pod CIDR still means any pod in the cluster could
# forge these headers, which is why echo-network-policy.yaml restricts
# who may reach the application at all.
trustedIPs:
- 10.42.0.0/16
websecure:
forwardedHeaders:
trustedIPs:
- 10.42.0.0/16
--- 앱이 받은 것
{
"headers" : {
"host" : [ "app1.hyeonworks.com" ],
"user-agent" : [ "curl/8.5.0" ],
"accept" : [ "*/*" ],
"x-forwarded-host" : [ "app1.hyeonworks.com" ],
"x-forwarded-port" : [ "80" ],
"x-forwarded-proto" : [ "http" ],
"x-forwarded-server" : [ "traefik-5d6fcf895-xmqp6" ],
"x-real-ip" : [ "192.168.122.1" ],
"accept-encoding" : [ "gzip" ]
},
"remoteAddr" : "192.168.122.1",
"localAddr" : "10.42.0.27",
"scheme" : "http",
"secure" : false,
"serverName" : "app1.hyeonworks.com",
"serverPort" : 80,
"requestUrl" : "http://app1.hyeonworks.com/api/echo"
}
@@ -0,0 +1,24 @@
=== 이 curl 은 어디서 어디로 가나 ===
100.83.212.4 app1.hyeonworks.com
100.83.212.4 auth.hyeonworks.com
=== 호스트의 DNAT 파일 (sudo 없이 읽히나) ===
#!/usr/sbin/nft -f
table ip lab_edge
delete table ip lab_edge
table ip lab_edge {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iifname "tailscale0" tcp dport {80, 443} dnat to 192.168.122.10
}
chain forward {
type filter hook forward priority filter - 10; policy accept;
ip daddr 192.168.122.10 tcp dport {80, 443} ct state new accept
}
}
=== lab_edge 테이블 실물 ===
sudo: a password is required
exit=1
@@ -0,0 +1,7 @@
=== http 로 로그인 체인이 도나 ===
oauth2 진입 302 -> https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/auth?response_type=code&client_id=bff-confidential&scope=openid%20profile%20email&state=Vt28FHS-cuqr7NcdUaR6xjPOs2zreT0crThNPaGjDuQ%3D&redirect_uri=http://app1.hyeonworks.com/login/oauth2/code/keycloak&nonce=s14SFB1opH_jb0_IQKSx65X6FrtD4-8svAS8qfTQQwU&code_challenge=iv0wxqu3JsnSsT-f07hRh1UCwei4WHBZiDNGw-TATvA&code_challenge_method=S256
=== 그 리다이렉트를 따라가 본다 (3홉) ===
final 302 https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/auth?response_type=code&client_id=bff-confidential&scope=openid%20profile%20email&state=ATjUt4POkZvWTWfeYJHZxH4uYbJGdIcMDjw0yYmXTW8%3D&redirect_uri=http://app1.hyeonworks.com/login/oauth2/code/keycloak&nonce=R7GTKPxRn-hNvjvcsYTH_Zpw32wrlD7h376XQ1d5I2I&code_challenge=C9PP9r8Oze2mzwC6AusVE19CKx6-xHoQ1fxZwHO7PRM&code_challenge_method=S256
=== 등록된 redirectUris ===
@@ -0,0 +1,43 @@
=== 파드 안에서 공개 이름이 무엇으로 풀리나 ===
100.83.212.4 auth.hyeonworks.com
100.83.212.4 app1.hyeonworks.com
=== 파드에서 그 주소의 80·443 에 닿나 ===
100.83.212.4:80 닫힘
100.83.212.4:443 닫힘
192.168.122.10(엣지):80 열림
192.168.122.10(엣지):443 닫힘
=== CoreDNS 에 이 이름을 돌리는 설정이 있나 ===
# File managed by k3s. DO NOT EDIT.
# See: https://docs.k3s.io/advanced#coredns-custom-configuration-imports
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
hosts /etc/coredns/NodeHosts {
ttl 60
reload 15s
fallthrough
}
prometheus :9153
cache 30
loop
reload
loadbalance
import /etc/coredns/custom/*.override
forward . /etc/resolv.conf
}
import /etc/coredns/custom/*.server
--- NodeHosts
192.168.122.11 kc-lab-1
192.168.122.12 kc-lab-2
=== 노드의 /etc/hosts 에 있나 ===
(없음)
@@ -0,0 +1,11 @@
=== CoreDNS 에 엣지로 보내는 항목을 더한다 (k3s 의 custom override) ===
configmap/coredns-custom created
deployment.apps/coredns restarted
Waiting for deployment "coredns" rollout to finish: 0 of 1 updated replicas are available...
error: timed out waiting for the condition
=== 파드에서 다시 ===
80 닫힘
=== echo 를 다시 띄워 JWKS 를 받게 해 본다 (여전히 https 라 실패해야 정상) ===
deployment "echo" successfully rolled out
@@ -0,0 +1,14 @@
=== 실패 원인 ===
plugin/hosts: this plugin can only be used once per Server Block
k3s Corefile 이 이미 hosts /etc/coredns/NodeHosts 를 쓴다. override 는 같은 블록에 들어간다.
=== 별도 서버 블록(.server)으로 고쳐 넣는다 ===
configmap/coredns-custom configured
coredns-747f7ff557-c8f9q 1/1 Running 0 30s
--- 로그
.:53
hyeonworks.com.:53
[WARNING] No files matching import glob pattern: /etc/coredns/custom/*.override
[INFO] plugin/reload: Running configuration SHA512 = bbef792a18068d14383e2aae31d25d75edca67dfea52ee28e28117264f318981634c941f3626af4eed0f5f0d9866ddac82f13668a8f19b5a353fae28e695f24d
CoreDNS-1.14.6
linux/amd64, go1.26.5, 424d125
@@ -0,0 +1,9 @@
=== 파드에서 다시 푼다 ===
192.168.122.10 auth.hyeonworks.com
192.168.122.10 app1.hyeonworks.com
192.168.122.10:80 열림
192.168.122.10:443 닫힘
=== 클러스터 안에서 realm 을 받아 본다 (http) ===
sh: line 1: curl: command not found
command terminated with exit code 127
@@ -0,0 +1,14 @@
=== B-7 6단계 ② 두 cookie secret 의 길이만 (값은 안 본다) ===
32
32
=== B-7a 6단계 R 함수와 스캔 루프 ===
(위가 비면 세션 키가 없는 것이다) dbsize=3
=== D-4a 8단계 dry-run 으로 훅 호출만 본다 ===
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
No simulated renewals were attempted.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
exit=0