The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
129 lines
4.9 KiB
Markdown
129 lines
4.9 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: analysis-finding-a19-f009
|
|
title: 접근 검사가 두 갈래로 존재하고, 조립된 쪽이 진단이 약한 쪽이다 (§8.3)
|
|
topic: messaging-and-outbox
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:analysis-finding-a19-f009
|
|
evidenceCapturedOn: 2026-09-01
|
|
body: case-analysis-finding-a19-f009.body.md
|
|
assets:
|
|
- key: analysis-finding-a19-f009
|
|
file: ../../../final/evidence/rendered/analysis-finding-a19-f009.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/analysis-finding-a19-f009.txt
|
|
source:
|
|
- 원본 분석 절은 analysis/19-messaging-platform.md#L511 이다.
|
|
---
|
|
|
|
# 접근 검사가 두 갈래로 존재하고, 조립된 쪽이 진단이 약한 쪽이다 (§8.3)
|
|
|
|
같은 권한 검사가 두 형태로 있다. 조립된 쪽은 거부를 설정 분류로 돌려주고, 조립되지 않은 쪽은 전용 인가 예외를 던진다. 검사 자체는 조립된 쪽에서 수행되므로 보안 구멍은 아니다.
|
|
|
|
## 관계
|
|
|
|
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
|
|
이 사례가 그 규칙의 형태다.
|
|
- **브로커 권한 매니페스트의 자기 점검이 존재하지 않는다**
|
|
같은 가족의 다른 권한 사례다.
|
|
- **권한 거부와 설정 실수가 같은 버킷에 들어간다**
|
|
기록하는 이유다.
|
|
|
|
## 문제
|
|
|
|
같은 권한 검사가 두 형태로 있다.
|
|
|
|
어느 쪽이 조립되었고 두 쪽의 차이가 무엇인지 확인했다.
|
|
|
|
## 결론
|
|
|
|
조립된 쪽은 발행자가 접근 정책을 직접 부르는 형태다.
|
|
|
|
발행이 허용되지 않으면 발행 금지 코드와 메시지를 담은 거부 결과를 돌려준다. 실패 분류가 설정이고 재시도 불가다.
|
|
|
|
조립되지 않은 쪽은 전용 검증기다. 주 참조도 테스트 참조도 0 이다.
|
|
|
|
발행이 허용되지 않으면 전용 인가 예외를 던진다. 코드와 함께 자격증명이 그 목적지에 발행할 수 없다는 메시지를 담는다.
|
|
|
|
그 클래스의 자바독이 존재 이유를 적는다.
|
|
|
|
브로커의 권한 검사보다 먼저 돌고, 일으키는 실패가 논리 목적지와 역할을 이름으로 부른다는 것이다.
|
|
|
|
브로커 권한 거부는 애플리케이션 문맥이 없는 연결 수준 오류로 도착하며, 그래서 어느 모듈이 어디로 발행하려 했는가가 로그 한 줄이 아니라 조사가 된다는 것이다.
|
|
|
|
두 경로의 차이는 분류다.
|
|
|
|
조립된 쪽은 인가 거부를 설정으로 분류한다. 조립되지 않은 쪽은 전용 인가 예외를 던진다.
|
|
|
|
핵심 계약의 예외 스물여섯 종에 그 인가 예외가 명시적으로 있는데, 실제 발행 경로는 그 타입을 쓰지 않는다.
|
|
|
|
권한 거부가 설정으로 집계되면 설정 실수와 권한 침해 시도가 같은 갈래에 들어간다.
|
|
|
|
실제 검사 자체는 조립된 쪽에서 수행되므로 보안 구멍은 아니다.
|
|
|
|
분류와 진단의 문제이고, 중복 장치 중 조립되지 않은 쪽이 더 정확한 분류를 갖고 있다.
|
|
|
|
판정은 P3 다.
|
|
|
|
## 검증 환경
|
|
|
|
확인 방식 : 두 경로의 구현과 참조 계수 대조
|
|
소스 수정 : x
|
|
|
|
## 재현 조건
|
|
|
|
원문은 final/evidence/raw/252 계열에 있다.
|
|
|
|
1. 발행자에서 권한 검사를 부르는 지점을 읽는다.
|
|
2. 거부 결과의 분류와 재시도 여부를 확인한다.
|
|
3. 전용 검증기의 구현과 자바독을 읽는다.
|
|
4. 그 검증기의 참조를 센다.
|
|
5. 핵심 계약의 예외 목록에 인가 예외가 있는지 확인한다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
같은 권한 검사가 두 형태로 있다.
|
|
|
|
## 조립된 쪽
|
|
|
|
`DefaultMessagePublisher`가 `DestinationAccessPolicy`를 직접 호출한다.
|
|
|
|
```java
|
|
if (!access.mayPublish(destination.name())) {
|
|
return rejected("PUBLISH_FORBIDDEN",
|
|
"this application may not publish to '" + destination.name().value() + '\'', startedAt);
|
|
}
|
|
```
|
|
|
|
`FailureCategory.CONFIGURATION` · `retryable=false`인 `PublishResult`를 돌려준다.
|
|
|
|
## DefaultMessagePublisher 참조 위치
|
|
|
|
:::evidence key="analysis-finding-a19-f009" alt="코드베이스에서 DefaultMessagePublisher 를 검색한 출력 6줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="DefaultMessagePublisher 코드베이스 검색 — 6줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
## 조립되지 않은 쪽
|
|
|
|
`DestinationAccessValidator`(main 참조 0건, 테스트 0건)가 전용 예외를 던진다.
|
|
|
|
```java
|
|
public void requirePublish(DestinationName destination) {
|
|
if (!policy.mayPublish(destination)) {
|
|
throw new MessageAuthorizationException("DESTINATION_PUBLISH_DENIED",
|
|
"the producer credential may not publish to " + destination.value());
|
|
}
|
|
}
|
|
```
|
|
|
|
조립된 쪽이 진단이 약한 쪽이다. P3.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
권한 거부를 발생시켜 집계 갈래를 확인하지 않았다. 분류 상수상 그 결과가 나온다.
|
|
|
|
<!-- body:end -->
|