Files
clean-architecture-backend-…/docs/messaging/security.md
T
DongHyeonka d646c2f12f feat(messaging): 브로커 중립 메시징 플랫폼 24개 leaf 추가
messaging-superpowers-package 설계서/계획서 기반 구현.
registry를 19 → 43 leaf로 확장하고 src/messaging 아래 24개 leaf를 등록.

- core-api: M1 publish/consume + M2 batch·delayed·pause-resume
- policy/transport-spi: 재시도 결정, DLQ orchestration, admission control, lifecycle
- kafka·rabbit(Stable): contiguous commit, confirm/return 상관, 배치, 보안 설정
- pulsar·nats(Experimental): 기본 비활성, live 인증 없음을 코드로 기록
- outbox/inbox/claim-check: 트랜잭션 결합, lease, 무결성 검증
- admin: plan → approve → execute를 타입으로 강제
- 문서 9종, infra compose 7종, JMH 벤치마크 3종

검증: 아키텍처 게이트 3종 통과, 24개 leaf 전부 check 통과,
messaging 테스트 604개 통과/0 실패.

미완: 계획서가 요구한 실 브로커 IT 40개 중 7개만 작성.
Rabbit 13 / Outbox 6 / Inbox 4 / NATS·Pulsar·Share 5 / testkit 2 /
starter·admin 3, 그리고 TLS·ACL 2개가 남음.
2026-08-14 14:55:38 +09:00

3.8 KiB

Messaging 보안

Credential 분리

producer / consumer / admin은 서로 다른 credential이다. MessageSecurityValidator가 startup에서 강제한다.

producer credential == consumer credential  → 실패
admin credential == producer|consumer       → 실패
production 프로필에 admin credential 존재    → 실패

마지막 규칙이 "애플리케이션은 topic을 purge할 수 없다"를 구조적으로 만든다. runtime이 admin 자격 증명을 아예 보유하지 않으므로, 침해된 handler가 상승시킬 대상이 없다.

Production 필수 조건

  • TLS 활성
  • TLS hostname verification 활성
  • broker authentication 활성
  • topology auto-create 비활성

Kafka는 추가로 enable.idempotence=true, acks=all, max.in.flight.requests.per.connection <= 5, consumer auto-commit 금지.

RabbitMQ는 추가로 publisher confirm, publisher return, mandatory=true, durable work queue의 quorum queue, consumer auto-ack 금지.

Credential은 값이 아니라 참조다

BrokerCredentialProfile의 어떤 variant도 secret을 담지 않는다. 식별자만 보관하고 connect 시점에 CredentialProvider로 해석한다. heap dump나 설정 출력에서 사용 가능한 credential이 나오지 않는다.

CredentialIdsbearer , sk-, -----begin, eyJ 같은 접두사를 거부한다. 참조가 들어갈 자리에 secret 자체를 붙여넣는 가장 흔한 사고를 막는다.

Rotation

CredentialRotationPlan.isDue()는 만료 전에 참이 된다. broker가 연결을 거부하기 시작한 시점에는 이미 publish가 실패하고 consumer가 멈춰 있다.

rotation은 세대 교체다. DefaultMessagingRuntimeRegistry.install()이 새 세대를 원자적으로 게시하고, 이전 세대는 마지막 lease가 닫힐 때까지 열려 있다가 닫힌다. 진행 중인 publish는 시작한 연결에서 confirm을 받는다.

drain deadline이 이 대기를 제한한다. 없으면 lease 하나가 새면 폐기된 credential이 무기한 열려 있고, rotation이 보안상 무의미해진다.

Header

금지 header는 application·platform 양쪽에서 거부한다.

Authorization, Proxy-Authorization, Cookie, Set-Cookie,
access_token, refresh_token, api_key, password, client_secret

credential이 header에 들어가면 broker storage, DLQ dump, 운영 도구에 남는다. downstream redaction으로는 되돌릴 수 없다.

예약 header(msg.*, traceparent, tracestate, baggage)는 platform만 쓴다. application이 msg.id를 설정할 수 있으면 Inbox 중복 제거와 DLQ 상관관계가 의존하는 logical identity가 호출자 제어가 된다.

ACL

DestinationAccessValidator가 broker ACL 이전에 검사한다. broker ACL 거부는 애플리케이션 컨텍스트가 없는 연결 수준 오류로 도착하므로 "어느 모듈이 어디에 publish하려 했는가"가 조사 대상이 된다.

관측성 누출

MessagingRedactor는 denylist다.

  • secret: authorization, cookie, token, password, secret, credential
  • per-message identity: messageId, correlationId, causationId, partitionKey, key, offset, deliveryTag, sequence
  • payload: payload, body, data
  • 예외 상세: exceptionMessage, stackTrace

identity를 지우는 이유는 두 가지다. bounded metric을 message당 하나의 series로 만들고, support log를 재식별 표면으로 만들기 때문이다.

CardinalityGuard는 dimension당 값 개수를 상한한다. cardinality 사고는 점진적이지 않다. 테스트 10건에서는 멀쩡하고 운영에서 백엔드를 죽인다.

감사

MessagingAuditEvent는 replay, redrive, offset reset, purge, delete를 기록한다. subject(운영자 identity), approval ticket, 그리고 redactor를 통과한 details만 담는다. 누가 무엇을 했는지 증명하되 payload의 두 번째 사본이 되지 않는다.