--- 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: - 원본 분석 절은 final/document.md#a19#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 구현을 센다. ## 본문 `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)`(`:122`)와 `missing(Set)`(`: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 이라는 것까지 확인했다.