Files
cs-note/hhs/MQ/11-安全与多租户/43-MQ-加密与审计.md
T
2026-05-24 20:51:06 +08:00

273 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- MQ
- 安全
- 加密
- 审计
- 合规
create time: 2026-05-24 19:52
---
# MQ 加密与审计
## 概述
认证授权解决了"谁在访问"的问题,加密和审计则解决"数据是否安全"和"操作是否可追溯"。本文覆盖传输加密、消息级加密、密钥管理、审计日志、配额管理和多租户隔离,构建 MQ 安全的完整防护体系。
## 正文
### 传输加密:TLS/SSL
传输加密是最基础的安全措施,防止网络嗅探和中间人攻击。MQ 场景下有两类通信需要加密:
1. **Client-Broker 通信**:Producer/Consumer 与 Broker 之间的数据传输
2. **Broker-Broker 通信**:集群内部副本同步、Controller 通信
TLS 配置要点:
- 使用 TLS 1.2+ 版本,禁用弱加密套件
- 生产环境必须双向认证(mTLS),至少服务端验证
- 证书有效期不宜过长(建议 90 天),配合自动轮转
```mermaid
graph LR
subgraph "加密层次"
TLS["传输加密 TLS - 加密传输通道"]
MsgEnc["消息级加密 - 加密消息体"]
AtRest["静态加密 - 加密磁盘存储"]
end
Producer["Producer"] -->|"TLS 加密通道"| Broker["Broker"]
Broker -->|"磁盘加密"| Disk["Storage"]
Broker -->|"TLS 加密通道"| Consumer["Consumer"]
Producer -.->|"应用层加密消息体"| MsgEnc
```
### 消息级加密(Envelope Encryption)
TLS 只保护传输通道,消息在 Broker 端以明文存储。如果 Broker 被入侵或存储介质被盗,消息就暴露了。消息级加密在应用层加密消息体,Broker 只存储密文。
Envelope Encryption 工作流程:
1. Producer 用 **数据密钥(DEK)** 加密消息体
2. DEK 用 **密钥加密密钥(KEK)** 加密后随消息一起发送
3. Broker 存储密文和加密后的 DEK
4. Consumer 获取消息后,先用 KEK 解密 DEK,再用 DEK 解密消息
```go
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"io"
"github.com/IBM/sarama"
)
// EnvelopeMessage 包含加密后的数据和加密后的 DEK
type EnvelopeMessage struct {
EncryptedData []byte `json:"encrypted_data"`
EncryptedDEK []byte `json:"encrypted_dek"`
}
// encryptMessage 使用 AES-GCM 加密消息体
func encryptMessage(plaintext []byte, kek []byte) (*EnvelopeMessage, error) {
// 生成随机 DEK
dek := make([]byte, 32)
rand.Read(dek)
// 用 DEK 加密消息
block, _ := aes.NewCipher(dek)
gcm, _ := cipher.NewGCM(block)
nonce := make([]byte, gcm.NonceSize())
io.ReadFull(rand.Reader, nonce)
encryptedData := gcm.Seal(nonce, nonce, plaintext, nil)
// 用 KEK 加密 DEK
kekBlock, _ := aes.NewCipher(kek)
kekGCM, _ := cipher.NewGCM(kekBlock)
kekNonce := make([]byte, kekGCM.NonceSize())
io.ReadFull(rand.Reader, kekNonce)
encryptedDEK := kekGCM.Seal(kekNonce, kekNonce, dek, nil)
return &EnvelopeMessage{
EncryptedData: encryptedData,
EncryptedDEK: encryptedDEK,
}, nil
}
func main() {
kek := []byte("0123456789abcdef0123456789abcdef") // KEK 从 KMS 获取
plaintext := []byte(`{"user_id": "u123", "action": "purchase"}`)
envelope, _ := encryptMessage(plaintext, kek)
data, _ := json.Marshal(envelope)
msg := &sarama.ProducerMessage{
Topic: "sensitive-events",
Value: sarama.ByteEncoder(data),
}
_ = msg // 发送到 Kafka
}
```
> [!question]
> 消息级加密会让 Broker 端的消息过滤失效,如何解决这个矛盾?
这是一个经典的取舍问题。几种应对方案:
- **部分加密**:只加密敏感字段,保留用于过滤的元数据在 Header 中明文传输
- **Token 化**:用 Token 替代敏感数据,Broker 按 Token 过滤
- **Consumer 端过滤**:接受 Broker 无法过滤的现实,在 Consumer 端做过滤(增加带宽消耗)
### 密钥管理
密钥管理是加密体系的核心。密钥泄露等于加密形同虚设。
**KMS(Key Management Service)**:云厂商提供的密钥管理服务(AWS KMS、Azure Key Vault、HashiCorp Vault),核心能力:
- 密钥生成和存储(HSM 硬件保护)
- 密钥轮转(自动更换 KEK,旧密钥保留用于解密历史数据)
- 访问审计(谁在什么时候访问了哪个密钥)
**密钥轮转策略**:
- KEK 定期轮转(如每 90 天)
- 轮转后旧 KEK 不立即删除,保留用于解密历史消息
- DEK 不需要轮转(每条消息用不同的 DEK)
### 审计日志
审计日志记录所有管理操作,用于事后追溯和合规审查。需要记录的事件:
- **Topic 管理**:创建、删除、配置变更
- **ACL 变更**:权限授予、撤销
- **用户管理**:用户创建、密码变更、证书签发
- **集群操作**:Broker 上下线、配置变更、滚动升级
审计日志要求:
- 不可篡改(写入独立存储,与 Broker 日志分离)
- 包含操作者身份、时间戳、操作详情、来源 IP
- 保留期限符合合规要求(通常 1-3 年)
### 配额管理
防止单个 Producer/Consumer 独占资源,影响其他租户:
- **生产者带宽配额**:限制每秒发送字节数(`producer_byte_rate`)
- **消费者带宽配额**:限制每秒拉取字节数(`consumer_byte_rate`)
- **请求百分比配额**:限制 CPU 时间占比(`request_percentage`)
- **连接数限制**:限制单个客户端 IP 的最大连接数
```bash
# 为 user-order-service 设置生产者带宽配额:10MB/s
kafka-configs.sh --bootstrap-server localhost:9093 \
--alter --add-config 'producer_byte_rate=10485760' \
--entity-type clients --entity-name order-service
```
### 多租户隔离
当多个团队或业务共用一个 MQ 集群时,隔离至关重要:
| 隔离维度 | 方案 | 效果 |
|----------|------|------|
| 命名空间 | Topic 前缀(如 `team-a.orders`) | 逻辑隔离,防止命名冲突 |
| 资源配额 | 带宽/连接数限制 | 防止资源争抢 |
| ACL 权限 | 按租户授予 Topic 权限 | 访问隔离 |
| 网络隔离 | 网络策略/专用 Listener | 防止跨租户网络访问 |
| 物理隔离 | 独立集群 | 最强隔离,成本最高 |
### 多租户安全架构
```mermaid
graph TB
subgraph TenantA["Tenant A"]
AppA["Service A"]
end
subgraph TenantB["Tenant B"]
AppB["Service B"]
end
subgraph MQCluster["MQ Cluster"]
ACL["ACL Engine"]
Quota["Quota Manager"]
Audit["Audit Logger"]
BrokerA["Broker 0"]
BrokerB["Broker 1"]
BrokerC["Broker 2"]
end
subgraph Storage["Key Storage"]
KMS["KMS / Vault"]
end
AppA -->|"TLS + SASL"| ACL
AppB -->|"TLS + SASL"| ACL
ACL -->|"check permission"| Quota
Quota -->|"enforce limits"| BrokerA
Quota --> BrokerB
Quota --> BrokerC
ACL -->|"log"| Audit
AppA -.->|"fetch DEK"| KMS
AppB -.->|"fetch DEK"| KMS
```
### 合规要求
在金融、医疗、出海场景下,MQ 还需满足合规要求:
- **数据驻留(Data Residency)**:特定数据必须存储在指定地域(如欧盟用户数据不出欧盟)
- **消息保留策略**:按数据分类设定不同的保留时长,过期自动删除
- **GDPR**:支持数据主体的"被遗忘权"——收到删除请求后,必须能从 MQ 中彻底删除相关消息(这在不可变日志中很难实现,通常通过加密密钥销毁来实现"逻辑删除")
### Go 代码:TLS 连接配置
```go
package main
import (
"crypto/tls"
"crypto/x509"
"os"
"github.com/IBM/sarama"
)
func main() {
// 加载 CA 证书
caCert, _ := os.ReadFile("ca.pem")
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// 加载客户端证书(mTLS 场景)
cert, _ := tls.LoadX509KeyPair("client.pem", "client-key.pem")
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
MinVersion: tls.VersionTLS12,
}
config := sarama.NewConfig()
config.Net.TLS.Enable = true
config.Net.TLS.Config = tlsConfig
producer, err := sarama.NewSyncProducer([]string{"broker1:9093"}, config)
if err != nil {
panic(err)
}
defer producer.Close()
// 通过 TLS 加密通道发送消息
msg := &sarama.ProducerMessage{
Topic: "secure-topic",
Value: sarama.StringEncoder("encrypted in transit"),
}
producer.SendMessage(msg)
}
```
这段代码展示了 Kafka TLS 连接的完整配置:加载 CA 证书验证服务端身份,加载客户端证书实现双向认证,强制 TLS 1.2 最低版本。
## 关联笔记
- [[42-MQ-认证与授权]]
- [[40-MQ-性能调优]]
- [[39-MQ-容器化与-K8s-部署]]