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>
58 lines
3.6 KiB
Plaintext
58 lines
3.6 KiB
Plaintext
# 주제: 같은 리프 안에서 "정규 형식" 인코딩이 두 가지이며, 그중 하나는
|
|
# 바로 옆 파일이 명시적으로 금지한 방식이다
|
|
# revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
|
|
# (A) 길이 접두 방식 — ApprovalGrant.canonicalForm() (ApprovalGrant.java:61-87)
|
|
# javadoc:
|
|
# "Every field is length-prefixed rather than delimited. A delimiter can be smuggled into a
|
|
# ticket or an identity to make two different grants render identically; a length prefix
|
|
# cannot."
|
|
# 구현:
|
|
# private static void appendField(StringBuilder canonical, String value) {
|
|
# canonical.append(value.length()).append(':').append(value);
|
|
# }
|
|
# 대상 필드 11개: ticket, approvedBy, requestedBy, operation, source, target, topologyVersion,
|
|
# maxImpact, planDigest, approvedAt, validUntil
|
|
|
|
# (B) 구분자 결합 방식 — 계획 다이제스트 2곳
|
|
# ReplayPlan.digest() (ReplayPlan.java:51-64)
|
|
# PlanDigest.ofCanonical(String.join("|",
|
|
# "REPLAY", replayId, destination, isolatedConsumerGroup, from, to, estimatedMessages,
|
|
# topologyVersion, targetsLiveConsumerGroup))
|
|
# RedrivePlan.digest() (RedrivePlan.java:50-62)
|
|
# PlanDigest.ofCanonical(String.join("|",
|
|
# "REDRIVE", redriveId, source, target, batchSize, candidates,
|
|
# alreadyRedrivenCandidates, topologyVersion))
|
|
|
|
# ---- 각 필드가 '|' 를 담을 수 있는가 ----
|
|
# command: cat messaging-core-api/.../destination/DestinationName.java
|
|
# private static final Pattern VALID = Pattern.compile("[a-z0-9][a-z0-9.-]{0,159}");
|
|
# -> destination/source/target 는 '|' 불가.
|
|
# replayId/redriveId : UUID.toString() -> 불가
|
|
# 불리언 : "true"/"false" -> 불가
|
|
# from/to : Instant.toString() -> 불가 ("" 가능)
|
|
# 정수/long : 10진수 -> 불가
|
|
# topologyVersion : String, 검증은 isBlank() 뿐 -> **가능**
|
|
# command: git grep -n "topologyVersion" -- src/messaging/messaging-admin-api/src/main
|
|
# ReplayPlan.java:37-38 if (topologyVersion == null || topologyVersion.isBlank()) throw …
|
|
# RedrivePlan.java:37-38 동일
|
|
# 생산처: BrokerTopologyInspector.topologyVersion() (messaging-admin-runtime, 애플리케이션이 구현하는 SPI)
|
|
# -> 형식 제약 없음.
|
|
|
|
# ---- 현재 충돌 가능성 판정 ----
|
|
# 자유 필드가 topologyVersion 하나뿐이고, 그 앞의 모든 필드가 '|' 를 담을 수 없으므로
|
|
# 왼쪽에서 '|' 를 세면 앞 필드들의 경계가 확정되고, 마지막 필드(고정 형식)도 오른쪽에서 확정된다.
|
|
# 따라서 현재 필드 구성에서는 인코딩이 단사(injective)이며 **충돌을 만들 수 없다**.
|
|
#
|
|
# 판정: 지금은 악용 가능한 결함이 아니다. 기록하는 이유는 두 가지다.
|
|
# 1) 같은 리프의 ApprovalGrant 가 정확히 이 위험("구분자 밀반입")을 이유로 길이 접두를 쓰고
|
|
# 그 이유를 javadoc 에 남겼는데, 승인이 서명으로 묶이는 대상인 계획 다이제스트 두 곳은
|
|
# 그 규칙을 따르지 않는다.
|
|
# 2) 단사성이 "자유 형식 필드가 하나뿐" 이라는 우연한 조건에 의존한다.
|
|
# 자유 형식 필드가 하나 더 추가되거나 topologyVersion 의 위치가 바뀌면
|
|
# 조용히 깨지며, 깨졌을 때의 결과는 "한 승인이 다른 계획을 인가" 로
|
|
# PlanDigest 가 존재하는 이유 자체가 무력화되는 것이다.
|
|
|
|
# 참고: PlanDigest 자체는 형식을 강하게 검증한다 (PlanDigest.java:23-28)
|
|
# if (!value.matches("[0-9a-f]{64}")) throw new IllegalArgumentException(…)
|