# Messaging 보안 ## Credential 분리 producer / consumer / admin은 **서로 다른 credential**이다. `MessageSecurityValidator`가 startup에서 강제한다. ```text 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이 나오지 않는다. `CredentialIds`는 `bearer `, `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 양쪽에서 거부한다. ```text 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의 두 번째 사본이 되지 않는다.