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개가 남음.
93 lines
3.8 KiB
Markdown
93 lines
3.8 KiB
Markdown
# 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의 두 번째 사본이 되지 않는다.
|