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

8.4 KiB
Raw Blame History

tags, create time
tags create time
MQ
安全
加密
审计
合规
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 天),配合自动轮转
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 解密消息
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 的最大连接数
# 为 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 防止跨租户网络访问
物理隔离 独立集群 最强隔离,成本最高

多租户安全架构

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 连接配置

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 最低版本。

关联笔记