Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/messaging-and-outbox/case/case-a19-f008-acl.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
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>
2026-09-04 22:51:59 +09:00

134 lines
12 KiB
Markdown

---
kind: CASE
slug: a19-f008-acl
title: 브로커 ACL 매니페스트의 자기 점검이 존재하지 않는다
topic: messaging-and-outbox
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: case:a19-f008-acl
evidenceCapturedOn: 2026-09-04
body: case-a19-f008-acl.body.md
assets:
- key: a19-f008-acl
file: ../../../final/evidence/rendered/a19-f008-acl.svg
evidence:
- ../../../final/evidence/raw/a19-f008-acl.txt
source:
- 원본 분석 절은 analysis/19-messaging-platform.md#L495 이다.
---
# 브로커 ACL 매니페스트의 자기 점검이 존재하지 않는다
`messaging-security``BrokerAclManifest` 자바독은 기동 대조와 파괴적 권한 거부를 적어 두었다. 두 검사 모두 이 record 안에 메서드로 있고 시험도 단언한다. 그 메서드를 부르는 main 코드가 `BrokerAclManifest.java` 밖에 없다.
## 관계
- **ACL 매니페스트 전체가 쓰이지 않는다**
저쪽은 이 타입의 소비자가 없다는 미해결 질문이고, 여기서는 그중 자바독이 적어 둔 두 검사가 어느 main 경로에서도 불리지 않는 것을 확인했다.
- **클래스 javadoc 이 강제되지 않는 규칙을 강제된다고 말한다**
저쪽은 규칙이 코드로 없고 여기는 코드로 있는데 부르는 자리가 없다. 자바독만 읽는 사람에게 보이는 결과는 같다.
- **타입이 문서화한 불변식은 타입이 강제한다**
이 record 는 그 규칙을 지킨 형태다. 거부가 조건부라 생성자가 아니라 가드 메서드로 두었고, 남은 문제는 그 가드를 부르는 자리다.
## 문제
브로커 권한 매니페스트가 출하 리프에 record 로 있다.
자바독이 적어 둔 두 검사가 어디까지 존재하고 어디서 불리는지 확인했다.
## 결론
두 검사 모두 이 record 안에 있다. 부르는 main 코드가 없다.
파괴적 권한 거부는 requireApplicationRuntime()(:103)이 한다. 선언된 파괴 권한이 비어 있지 않으면 APPLICATION_HOLDS_DESTRUCTIVE_GRANT 로 던진다. 기동 대조에 필요한 뺄셈은 undeclared(:122) 와 missing(:135) 에 양방향으로 있다.
CredentialRuntimeRegistryTest 한 파일이 이 타입을 17 줄에서 쓰는데, 그 시험은 파괴적 grant 를 넣은 매니페스트가 requireApplicationRuntime() 에서 예외를 내는 것과 undeclared() 가 선언에 없는 grant 를 돌려주는 것을 단언한다. 두 메서드를 부르는 자리가 그 시험뿐이다.
StartupProfileValidation 으로 감싸인 대상 셋에도 이 타입은 들어 있지 않다. 감싸인 것은 KafkaBrokerProfile 과 RabbitBrokerProfile 과 BrokerSecurityProfile 셋이다. 마지막 것은 이 매니페스트와 같은 패키지인데 매니페스트만 빠져 있다. 브로커에 권한을 질의하는 이름 여덟도 0 파일인데, Kafka AdminClient 자체가 main 에 없어서 그 0 이 말해 주는 범위는 좁다.
간결 생성자가 파괴 여부를 보지 않는 것은 결함이 아니다. 자바독이 거부한다고 적은 것은 애플리케이션 런타임이 파괴적 권한을 선언한 경우이고, 운영 평면의 principal 은 그것을 정당하게 선언한다. 조건을 아는 쪽이 부르는 가드가 맞고 그 가드가 이미 있다.
원본 분석의 등급은 P2 이고 이 기록은 새로 매기지 않는다. 자바독이 단정한 것이 코드로 없다는 P2 의 지렛대는 이 리비전에서 성립하지 않는다. 낮출 근거도 있는데, 검사 대상이 런타임에 만들어지지 않고 파괴 연산 인터페이스에도 main 구현이 없다는 것이다.
## 검증 환경
확인 방식 : 자바독 원문 확인, 대응 메서드 넷의 본문과 그것을 단언하는 시험 확인, 자기 파일 안팎을 나눈 호출자 계수, 이 타입의 생성·주입 지점 계수, Operation 열거값의 파괴 표시 확인, 기동 검증으로 감싸인 프로파일 전수, 브로커 ACL 질의 API 이름 여덟 검색과 AdminClient 의 소스 세트 분포, 파괴 연산 인터페이스의 구현 계수
소스 수정 : x
## 재현 조건
1. BrokerAclManifest 의 클래스 자바독에서 적어 둔 두 검사를 읽는다.
2. 그 두 검사에 대응하는 메서드가 있는지 파일 끝까지 읽는다.
3. 그 메서드들을 부르는 자리를 자기 파일 안과 밖으로 나눠 세고 소스 세트도 가른다.
4. 그 동작을 단언하는 시험의 이름과 단언 줄을 읽는다.
5. 이 타입의 생성 지점과 주입 지점을 main 에서 찾는다.
6. 간결 생성자가 검사하는 것을 나열하고, 거부가 무조건인지 조건부인지 자바독에서 확인한다.
7. Operation 열거값의 파괴 표시를 읽고 자바독이 적어 둔 이름과 대조한다.
8. StartupProfileValidation 으로 감싸인 대상을 전부 찾고 각각 무엇을 감싸는지 읽는다.
9. 브로커 권한 질의 API 이름을 검색하고, 그 이름들이 속한 클라이언트가 main 에 있는지도 함께 센다.
10. 파괴 연산을 담은 인터페이스의 main 구현을 센다.
## 본문
<!-- body:start -->
`BrokerAclManifest``messaging-security` 의 141줄짜리 record 다. 클래스 자바독에는 플랫폼이 기동 시점에 자신을 이 매니페스트와 대조한다고 적혀 있고, 선언한 것보다 많은 권한을 들고 있는 런타임은 finding 이라고 적혀 있다. 파괴적 권한을 선언한 애플리케이션 런타임은 그대로 거부된다고도 적혀 있다.
## 자바독이 적어 둔 두 검사
:::evidence key="a19-f008-acl" alt="저장소 루트에서 돌린 정적 검색 출력 125줄. BrokerAclManifest 의 줄 수와 자바독의 두 단정이 원문 그대로 먼저 나오고, 이어서 그 두 단정에 대응하는 메서드 넷의 본문이 실린다 — destructiveGrants, 파괴적 권한이 있으면 APPLICATION_HOLDS_DESTRUCTIVE_GRANT 로 던지는 requireApplicationRuntime, 그리고 관측된 권한과 선언을 양방향으로 비교하는 undeclared 와 missing 이다. 간결 생성자는 널 검사와 공백 검사와 리스트 복사 셋만 한다. 그 메서드들을 부르는 자리 전부가 나오는데 BrokerAclManifest.java 밖의 main 코드는 0 건이고, 이 타입을 만들거나 받는 main 코드도 0 건이며, 자기 파일 밖에서 이름이 나오는 파일은 시험 하나다. 그 시험이 파괴적 grant 에 예외가 나는 것과 선언에 없는 grant 를 골라내는 것을 각각 단언한다. 이어서 Operation 열거값 일곱과 파괴 표시 셋, 기동 검증으로 감싸인 프로파일 셋, DestructiveMessagingAdmin 을 구현하는 main 클래스 0, 브로커 권한 질의 API 이름 여덟이 모두 0 파일이고 Kafka AdminClient 자체가 test 에만 있다는 것이 보인다." caption="자바독의 두 검사 · 대응 메서드 넷의 본문 · 파일 밖 main 호출자 0 과 시험의 단언 · 기동 검증 대상 셋 · 파괴 연산 구현 0 · ACL 질의 0 과 그 0 의 의미 — 125줄 · exit 0" zoom="true"
:::
두 번째 단정에 대응하는 것은 `requireApplicationRuntime()`(`:103`)이다. `destructiveGrants()` 로 선언된 파괴적 권한을 모아 비어 있지 않으면 `MessagingConfigurationException``APPLICATION_HOLDS_DESTRUCTIVE_GRANT` 코드로 던진다. 자바독 `:99` 에는 애플리케이션 런타임에 파괴적 권한을 주는 매니페스트를 거부한다고 적혀 있다.
첫 번째 단정에 대응하는 것은 `undeclared(Set<Grant>)`(`:122`)와 `missing(Set<Grant>)`(`:135`)다. 관측된 권한과 선언을 양방향으로 뺀다. `:116`\~`:117` 에는 초과가 finding 이고 부족이 아니라고 적혀 있다 — 빠진 권한은 첫 사용에서 시끄럽게 실패하지만 선언되지 않은 여분은 남용될 때까지 눈에 띄지 않기 때문이다.
시험도 그 둘을 단언한다. `CredentialRuntimeRegistryTest:167``anApplicationRuntimeMayNotHoldADestructiveGrant``:181` 에서 `requireApplicationRuntime` 에 예외가 나는 것을, `:187``aGrantTheBrokerHoldsButNobodyDeclaredIsTheFinding``:200` 에서 `undeclared` 가 선언에 없는 grant 를 돌려주는 것을 확인한다.
## 부르는 프로덕션 코드가 없다
`BrokerAclManifest.java` 밖에서 이 네 메서드를 부르는 main 코드는 0 건이다. 이 타입을 생성하거나 파라미터로 받는 main 코드도 0 건이고, 자기 파일 밖에서 이름이 나오는 파일은 그 시험 하나다.
기동 시점 검증에도 이 타입이 없다. `StartupProfileValidation` 이 감싸는 것은 `KafkaMessagingAutoConfiguration:59` 의 Kafka 브로커 프로파일, `RabbitMessagingAutoConfiguration:55` 의 Rabbit 브로커 프로파일, `MessagingCoreAutoConfiguration:221``compiled.security()` 셋이다. 세 번째는 같은 `messaging.security` 계열인데 매니페스트는 그 목록에 없다.
브로커에 권한을 질의하는 코드도 없다. `describeAcls`·`AclBinding`·`Authorizer` 를 포함한 이름 여덟이 저장소 전체에서 0 파일이다. 다만 그 0 의 의미는 좁다 — Kafka `AdminClient` 자체가 main 에 없고 test 5 파일에만 있으므로, 애초에 맞을 수 있는 코드가 main 에 없었다.
## 간결 생성자에 넣을 검사는 아니다
`:81`\~`:87` 의 간결 생성자는 `grants` 널 검사와 `principal` 공백 검사와 리스트 복사만 한다. 파괴 여부를 보지 않는다.
여기에 검사를 넣는 것이 수정 방향은 아니다. 자바독이 거부한다고 적은 것은 파괴적 권한 일반이 아니라 **애플리케이션 런타임**이 그것을 선언한 경우다. 운영 평면의 principal 은 `DELETE``PURGE` 를 정당하게 선언한다. 생성자에서 막으면 그 매니페스트를 이 타입으로 표현할 수 없다. 조건을 아는 쪽이 부르는 명시적 가드가 맞는 형태이고, 그 형태가 이미 `:103` 에 있다.
## 원문과 갈리는 자리
원문은 두 단정이 모두 실행되는 코드가 아니라고 적었고, 파괴적 권한을 선언한 매니페스트를 막는 코드가 record 자신에도 없다고 했다. 이 자리가 원문이 틀린 자리다. `requireApplicationRuntime()` 이 그 거부를 구현하고 시험이 그것을 고정한다. `undeclared``missing` 도 기동 대조에 필요한 비교를 갖고 있다. 없는 것은 구현이 아니라 그것을 부르는 자리다.
원문은 간결 생성자가 `principal``pattern` 공백을 검사한다고 적었다. `pattern` 공백 검사는 중첩된 `Grant` 의 생성자에 있다.
원문은 기동 검증으로 감싼 것을 브로커 프로파일 둘로 적었다. 셋이고, 세 번째가 이 매니페스트와 같은 패키지의 보안 프로파일이다.
원문이 인용한 자바독은 파괴적 권한으로 `DELETE_TOPIC``PURGE` 를 적는다. `Operation``DELETE_TOPIC` 이라는 값은 없고 `DELETE` 가 있으며, 파괴로 표시된 값은 `ALTER`·`DELETE`·`PURGE` 셋이다.
원문이 적은 "테스트 1건" 은 파일 수로는 맞다. 그 한 파일 안에서 이 타입이 나오는 줄은 17 이다.
## 등급에 대해
원본 분석의 등급은 P2 다. 이 리비전에서 그 등급을 떠받치던 근거 하나는 성립하지 않는다. 자바독이 단정한 것이 코드로 존재하지 않는다는 것이 P2 의 지렛대였는데, 코드는 있고 부르는 자리만 없다.
낮출 근거도 함께 있다. 프로덕션에서 이 매니페스트를 만드는 코드가 0 이라 검사할 대상이 런타임에 없고, 파괴 연산을 담은 `DestructiveMessagingAdmin` 은 main 구현이 0 이라 매니페스트가 거짓이어도 애플리케이션이 파괴 연산을 부를 수 없다.
이 기록은 등급을 새로 매기지 않는다. 상류가 P2 로 둔 근거와 여기서 확인한 반대 근거를 함께 남긴다.
## 확인하지 못한 것
그런 매니페스트를 실제로 만들어 아무 곳에서도 걸리지 않는 것을 실행으로 보이지는 않았다. `requireApplicationRuntime()` 을 부르는 main 코드가 없다는 데까지다.
브로커에 붙어 권한을 조회하지 않았다. 그런 조회를 하는 코드가 없다는 것까지 확인했다.
채택자가 `DestructiveMessagingAdmin` 을 구현해 넣는 배포는 보지 않았다. 이 저장소 main 에 구현이 0 이라는 것까지 확인했다.
채택자가 `DestructiveMessagingAdmin` 을 구현해 넣는 배포는 보지 않았다. 이 저장소 main 에 구현이 0 이라는 것까지 확인했다.
<!-- body:end -->