--- 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-部署]]