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>
5.8 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | evidenceCapturedOn | body | assets | evidence | source | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | analysis-finding-a10-f007 | 패턴 구독의 R2 승인만 호출자가 아니라 배포에 대해 이루어진다 | caching-and-redis | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:analysis-finding-a10-f007 | 2026-09-01 | case-analysis-finding-a10-f007.body.md |
|
|
|
패턴 구독의 R2 승인만 호출자가 아니라 배포에 대해 이루어진다
같은 위험 등급의 연산 대부분은 호출자가 허가를 들고 오도록 서명이 요구한다. 패턴 구독만 서명에 허가 인자가 없고, SDK 가 자기 자신에게 발급한 허가를 쓴 뒤 버린다. 실제 효과는 배포가 그 정책을 켰는지 확인하는 것이다.
관계
- 같은 위험 등급에 두 승인 모델이 있으면 차이를 문서가 적어야 한다 이 사례가 그 규칙의 형태다.
- permit 정책 이름이 세 곳에 문자열로 존재하고 교차 검사가 없다 같은 리프의 허가 체계 사례다.
- 식별자 검증의 다섯 검사 중 둘은 도달할 수 없다 같은 리프의 다른 검증 사례다.
문제
이 리프는 위험한 연산을 등급으로 나누고, 상위 등급 연산에 명시적 승인을 요구한다.
그 승인이 어디서 오는지 연산별로 대조했다.
결론
패턴 구독만 다르다.
집합 연산 셋은 서명이 고급 연산 허가를 요구한다. 호출자가 들고 온다.
키 훑기와 해시 항목 조회도 마찬가지다.
비트 필드 실행은 예산이 필수이고 허가는 가드가 목록의 필수 정책으로 요구한다.
패턴 구독은 서명에 허가 인자가 없다.
대상을 계산하는 쪽이 부르는 것은 SDK 가 자기 자신에게 발급하는 경로다.
발급 구현은 정책 이름이 배포의 활성 정책 목록에 없으면 던진다. 그러므로 실제 효과는 이 배포가 패턴 구독을 켰는지 확인하는 것이다.
그리고 반환된 허가는 버려진다.
즉 다른 상위 등급 연산은 호출 지점이 승인을 증명하는데, 패턴 구독은 배포가 켜 두었는지만 본다.
자바독이 허가가 필요한 이유는 적는다. 그 확산 범위를 서버가 정한다는 것이다.
그런데 그 허가가 호출자가 아니라 SDK 가 스스로 발급한 것이라는 약해진 보증은 적지 않는다.
고급 연산 허가의 계약이 상위 등급 연산이 명시적으로 승인되었음을 증명한다는 것과 견주면 차이가 있다.
판정은 P3 다.
배포 수준 게이트는 실재하고 이름공간 봉쇄도 있으므로 열린 구멍은 아니다.
기록하는 이유는 같은 위험 등급에 두 가지 다른 승인 모델이 적용되고 그 차이가 문서화되어 있지 않기 때문이다.
수정은 둘 중 하나다. 패턴 구독 서명에 고급 연산 허가를 추가하거나, 자바독에 배포 수준 승인임을 명시하는 것이다.
검증 환경
확인 방식 : 연산별 서명과 허가 발급 경로 대조 소스 수정 : x
재현 조건
원문은 final/evidence/raw/163 계열에 있다.
- 상위 등급 연산 목록을 만든다.
- 각 연산의 서명에 허가 인자가 있는지 확인한다.
- 패턴 구독이 부르는 발급 경로를 확인한다.
- 그 발급 구현이 무엇을 검사하는지 읽는다.
- 반환된 허가가 어디에 쓰이는지 확인한다.
본문
R2 연산마다 승인 모델이 다르다.
| R2 연산 | 호출자가 permit을 들고 오는가 |
|---|---|
sets.difference/intersection/union |
예 — 서명이 AdvancedOperationPermit을 요구 |
keys.scan · hashes.entries |
예 |
bitFields.execute |
budget 필수, permit은 guard가 catalog의 required-policy로 요구 |
pubSub.patternSubscribe |
아니오 — 서명에 permit 인자가 없다 |
AdvancedOperationPermit 참조 위치
:::evidence key="analysis-finding-a10-f007" alt="코드베이스에서 AdvancedOperationPermit 를 검색한 출력 31줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="AdvancedOperationPermit 코드베이스 검색 — 31줄 · exit 0" zoom="true" :::
SDK가 자기 자신에게 발급하고 그 permit을 버린다
patternTargets가 부르는 context.sdkPermit(PATTERN_SUBSCRIBE)는 SDK가 자기 자신에게 발급하는 경로다. ConfiguredRedisPolicyAuthority.issueAdvanced는 정책 이름이 배포의 enabledPolicies에 없으면 던지므로 실제 효과는 "이 배포가 pattern-subscribe를 켰는가"를 확인하는 것이고, 반환된 permit은 버려진다.
javadoc이 적지 않는 것
permit이 필요한 이유("its fan-out is decided by the server")는 적지만, 그 permit이 호출자가 아니라 SDK가 스스로 발급한 것이라는 약해진 보증은 적지 않는다. AdvancedOperationPermit의 계약이 "proving that an R2 operation was explicitly approved"인 것과 견주면 차이가 있다.
열린 구멍은 아니다
배포 수준 게이트는 실재하고 네임스페이스 봉쇄도 있다. 기록하는 이유는 같은 위험 등급에 두 가지 다른 승인 모델이 적용되고 그 차이가 문서화돼 있지 않기 때문이다. 수정은 patternSubscribe 서명에 AdvancedOperationPermit을 추가하거나, javadoc에 "배포 수준 승인"임을 명시하는 것이다. P3.
확인하지 못한 것
정책을 끈 배포에서 패턴 구독이 실제로 거부되는지 실행하지 않았다. 발급 구현상 그 결과가 나온다.