vault backup: 2026-05-24 20:51:06
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
---
|
||||
tags:
|
||||
- MQ
|
||||
- 安全
|
||||
- 认证
|
||||
- 授权
|
||||
- Kafka
|
||||
create time: 2026-05-24 19:52
|
||||
---
|
||||
|
||||
# MQ 认证与授权
|
||||
|
||||
## 概述
|
||||
|
||||
MQ 作为数据流转的核心枢纽,安全防护至关重要。本文梳理 MQ 面临的安全威胁,对比 SASL/PLAIN、SASL/SCRAM、mTLS、OAuth2 四种认证机制,讲解 ACL 授权模型,并以 Kafka 为例展示完整的安全配置实践。
|
||||
|
||||
## 正文
|
||||
|
||||
### MQ 安全威胁分析
|
||||
|
||||
一个没有安全防护的 MQ 集群就像一栋没有门锁的大楼:
|
||||
|
||||
- **未授权访问**:任何人都能连接 Broker,读取或写入任意 Topic
|
||||
- **消息篡改**:中间人攻击修改消息内容,下游消费者收到错误数据
|
||||
- **消息窃听**:网络嗅探获取敏感消息(如用户订单、支付信息)
|
||||
- **DoS 攻击**:恶意客户端大量发送消息耗尽 Broker 资源
|
||||
|
||||
> [!question]
|
||||
> 如果你的 MQ 集群部署在内网,是否还需要认证授权?内网就一定安全吗?
|
||||
|
||||
### 认证机制对比
|
||||
|
||||
#### SASL/PLAIN
|
||||
|
||||
最简单的用户名密码认证。密码以明文传输(除非配合 TLS),适合开发测试环境。
|
||||
|
||||
- 优点:配置简单,无需额外基础设施
|
||||
- 缺点:密码明文传输、不支持动态添加用户(需重启 Broker)
|
||||
|
||||
#### SASL/SCRAM
|
||||
|
||||
基于挑战-响应的认证协议,密码不在网络上传输。Kafka 支持 SCRAM-SHA-256 和 SCRAM-SHA-512。
|
||||
|
||||
- 优点:密码加密存储、支持动态添加用户、无需重启 Broker
|
||||
- 缺点:性能比 mTLS 略低
|
||||
|
||||
#### mTLS(双向 TLS)
|
||||
|
||||
客户端和服务端互相验证证书。这是安全性最高的方案。
|
||||
|
||||
- 优点:双向认证、无需密码管理、证书可自动轮转
|
||||
- 缺点:证书管理复杂(签发、分发、续期、吊销)
|
||||
|
||||
#### OAuth2/OIDC
|
||||
|
||||
基于令牌的认证,适合云原生和微服务场景。客户端通过 OAuth2 Server 获取 Token,携带 Token 连接 Broker。
|
||||
|
||||
- 优点:与企业身份系统集成、支持细粒度权限、令牌可过期
|
||||
- 缺点:依赖外部 OAuth2 Server、配置复杂
|
||||
|
||||
### 认证授权流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as "Client App"
|
||||
participant Broker as "Kafka Broker"
|
||||
participant Authn as "Authentication Module"
|
||||
participant Authz as "Authorization Module (ACL)"
|
||||
Client->>Broker: "Connect with credentials"
|
||||
Broker->>Authn: "Verify identity"
|
||||
Authn-->>Broker: "Identity: user-service-order"
|
||||
Client->>Broker: "Produce to order-topic"
|
||||
Broker->>Authz: "Check ACL: can user-service-order WRITE order-topic?"
|
||||
Authz-->>Broker: "ALLOW"
|
||||
Broker-->>Client: "Produce success"
|
||||
Client->>Broker: "Consume payment-topic"
|
||||
Broker->>Authz: "Check ACL: can user-service-order READ payment-topic?"
|
||||
Authz-->>Broker: "DENY"
|
||||
Broker-->>Client: "Authorization failed"
|
||||
```
|
||||
|
||||
### ACL 授权模型
|
||||
|
||||
ACL(Access Control List)提供 Topic 级别的细粒度权限控制。Kafka 的 ACL 由五元组定义:
|
||||
|
||||
- **Principal**:认证身份(如 `User:alice`)
|
||||
- **Resource**:资源类型和名称(如 `Topic:order-events`)
|
||||
- **Operation**:操作类型(Read/Write/Create/Delete/Describe)
|
||||
- **Permission**:Allow/Deny
|
||||
- **Host**:来源 IP(可选)
|
||||
|
||||
常见权限模式:
|
||||
- 最小权限原则:每个服务只授予需要的 Topic 权限
|
||||
- 生产者只有 Write 权限,消费者只有 Read 权限
|
||||
- 管理操作(Create/Delete)只授予运维账号
|
||||
|
||||
### Kafka 完整安全配置
|
||||
|
||||
下面展示 SASL/SCRAM + ACL 的完整配置流程。
|
||||
|
||||
Broker 端配置(`server.properties`):
|
||||
|
||||
```properties
|
||||
# 启用 SASL/SCRAM 认证
|
||||
listeners=SASL_SSL://:9093
|
||||
security.inter.broker.protocol=SASL_SSL
|
||||
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-256
|
||||
sasl.enabled.mechanisms=SCRAM-SHA-256
|
||||
|
||||
# 启用 ACL
|
||||
authorizer.class.name=kafka.security.authorizer.AclAuthorizer
|
||||
super.users=User:admin
|
||||
allow.everyone.if.no.acl.found=false
|
||||
```
|
||||
|
||||
```bash
|
||||
# 创建用户
|
||||
kafka-configs.sh --bootstrap-server localhost:9093 \
|
||||
--alter --add-config 'SCRAM-SHA-256=[iterations=8192,password=secret]' \
|
||||
--entity-type users --entity-name order-service
|
||||
|
||||
# 授权:允许 order-service 写 order-topic
|
||||
kafka-acls.sh --bootstrap-server localhost:9093 \
|
||||
--add --allow-principal User:order-service \
|
||||
--producer --topic order-topic
|
||||
|
||||
# 授权:允许 payment-service 读 order-topic
|
||||
kafka-acls.sh --bootstrap-server localhost:9093 \
|
||||
--add --allow-principal User:payment-service \
|
||||
--consumer --topic order-topic --group payment-group
|
||||
```
|
||||
|
||||
### Go 代码:SASL/SCRAM 认证配置
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/xdg-go/scram"
|
||||
)
|
||||
|
||||
// SCRAM 客户端实现
|
||||
type SCRAMClient struct {
|
||||
*scram.Client
|
||||
*scram.ClientConversation
|
||||
hashGenerator func() hash.Hash
|
||||
}
|
||||
|
||||
func (c *SCRAMClient) Begin(userName, password, authzID string) error {
|
||||
client, err := c.hashGenerator().NewClient(userName, password, authzID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Client = client
|
||||
c.ClientConversation = client.NewConversation()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SCRAMClient) Step(challenge string) (string, error) {
|
||||
return c.ClientConversation.Step(challenge)
|
||||
}
|
||||
|
||||
func (c *SCRAMClient) Done() bool {
|
||||
return c.ClientConversation.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
config := sarama.NewConfig()
|
||||
config.Net.SASL.Enable = true
|
||||
config.Net.SASL.Mechanism = sarama.SASLTypeSCRAMSHA256
|
||||
config.Net.SASL.User = "order-service"
|
||||
config.Net.SASL.Password = "secret"
|
||||
config.Net.SASL.SCRAMClientGeneratorFunc = func() sarama.SCRAMClient {
|
||||
return &SCRAMClient{hashGenerator: sha256.New}
|
||||
}
|
||||
config.Net.TLS.Enable = true // SCRAM 通常配合 TLS 使用
|
||||
|
||||
producer, err := sarama.NewSyncProducer([]string{"localhost:9093"}, config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer producer.Close()
|
||||
|
||||
// 发送消息,自动完成 SCRAM 认证握手
|
||||
msg := &sarama.ProducerMessage{
|
||||
Topic: "order-topic",
|
||||
Value: sarama.StringEncoder(`{"order_id": "12345"}`),
|
||||
}
|
||||
partition, offset, _ := producer.SendMessage(msg)
|
||||
_ = partition
|
||||
_ = offset
|
||||
}
|
||||
```
|
||||
|
||||
这段代码实现了 SASL/SCRAM-SHA-256 认证的完整流程:客户端发送用户名,Broker 返回挑战(salt + iteration),客户端计算哈希响应,Broker 验证通过后建立连接。
|
||||
|
||||
### mTLS 的简化方案
|
||||
|
||||
> [!question]
|
||||
> mTLS 安全性最高,但管理证书很麻烦。在微服务场景下如何简化证书管理?
|
||||
|
||||
几种主流方案:
|
||||
- **Service Mesh(Istio)**:Sidecar 代理自动处理证书签发和轮转,应用层完全无感
|
||||
- **cert-manager**:K8s 上的证书管理控制器,自动签发和续期
|
||||
- **SPIFFE/SPIRE**:标准化的服务身份框架,跨平台证书管理
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[43-MQ-加密与审计]]
|
||||
- [[40-MQ-性能调优]]
|
||||
- [[39-MQ-容器化与-K8s-部署]]
|
||||
@@ -0,0 +1,272 @@
|
||||
---
|
||||
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-部署]]
|
||||
Reference in New Issue
Block a user