AWS ALB + Mosquitto mTLS 安全集成方案(Header 透传模式)
1. 方案概述
本方案旨在解决"设备端 mTLS 认证"与"内网 Mosquitto 匿名访问"之间的安全断层问题。通过 AWS ALB 终结设备侧双向 TLS,并将客户端证书身份信息以自定义 Header 形式注入后端 TCP 流量,配合 Mosquitto 认证插件实现基于身份的动态访问控制,彻底消除内网匿名访问风险,同时保留 ALB 统一证书管理的便利性。
核心架构流程
[IoT 设备] --(mTLS:8883)--> [AWS ALB] --(TCP:1883 + X-Client-Cert-CN)--> [Mosquitto Broker]
|
[Auth Plugin] <--(读取Header)--> [ACL/DB]
2. 前置条件
- AWS ALB 已配置 TLS Listener(端口 8883),并启用 Mutual Authentication (mTLS)
- ALB 关联的 ACM Private CA 或自签 CA 信任库已正确配置
- Mosquitto 版本 ≥ 2.0(支持动态安全模块与插件 API)
- 内网安全组/NACL 仅允许 ALB 所在子网的 ENI 访问 Mosquitto 1883 端口
- 已准备用于测试的有效/无效客户端证书
3. 详细实施步骤
3.1 ALB 配置:启用证书信息透传
在 ALB 的 TLS Listener 上配置自定义 Header,将客户端证书的 Common Name (CN) 传递给后端:
- 进入 EC2 Console → Load Balancers → 选择目标 ALB
- Listeners → 编辑 8883 端口监听器
- 在 "Mutual authentication" 部分确认已启用且模式为 "Verify client certificate"
- 添加自定义 Header:
- Header Name:
X-Client-Cert-CN - Header Value:
${client.cert.subject.cn}
- Header Name:
- (推荐)添加指纹 Header 用于防伪造与审计:
- Header Name:
X-Client-Cert-Fingerprint - Header Value:
${client.cert.fingerprint.sha256}
- Header Name:
⚠️ 关键注意:ALB 仅在 mTLS 握手成功时才会注入这些 Header。若设备未提供有效证书,连接将在 ALB 层被终止,不会到达 Mosquitto。此特性是方案安全性的基石。
3.2 Mosquitto 部署认证插件
推荐使用 mosquitto-go-auth 插件,它原生支持从 TCP 连接中提取 ALB 注入的 Header,无需修改 Mosquitto 源码。
安装插件(Docker 方式推荐)
docker pull iegomez/mosquitto-go-auth:latest
配置 mosquitto.conf
# 禁用匿名访问(必须!)
allow_anonymous false
# 加载认证插件
plugin /usr/lib/mosquitto/auth-plugin.so
# 插件配置:使用 HTTP 后端验证
auth_opt_backends http
auth_opt_http_host 127.0.0.1 # 认证服务地址
auth_opt_http_port 8080 # 认证服务端口
auth_opt_http_get_params clientid=%c,username=%u,cn=%h:X-Client-Cert-CN,fingerprint=%h:X-Client-Cert-Fingerprint
auth_opt_http_superuser_uri /api/mqtt/superuser
auth_opt_http_aclcheck_uri /api/mqtt/acl
auth_opt_http_timeout_ms 3000 # 超时设置,避免阻塞连接
3.3 开发轻量级认证服务
需部署一个 HTTP 微服务(Python/Go/Node.js 均可),接收插件请求并返回认证结果。以下为 Python Flask 示例:
from flask import Flask, request, jsonify
import re
app = Flask(__name__)
# CN 白名单正则,防止 Header 注入攻击
CN_PATTERN = re.compile(r'^[a-zA-Z0-9\-\.]+$')
@app.route('/api/mqtt/acl', methods=['GET'])
def check_acl():
cn = request.args.get('cn', '')
topic = request.args.get('topic', '')
acc = int(request.args.get('acc', 0)) # 1=SUB, 2=PUB
# 1. 校验 CN 格式合法性
if not CN_PATTERN.match(cn):
return jsonify({"status": "deny"}), 403
# 2. 业务逻辑:根据 CN 判断是否有权访问该主题
# 示例:device-{id} 只能操作 devices/{id}/telemetry
device_id = cn.replace('device-', '')
allowed_pub_topic = f"devices/{device_id}/telemetry"
allowed_sub_topic = f"devices/{device_id}/command"
if acc == 2 and topic == allowed_pub_topic:
return jsonify({"status": "allow"}), 200
elif acc == 1 and topic == allowed_sub_topic:
return jsonify({"status": "allow"}), 200
else:
return jsonify({"status": "deny"}), 403
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8080)
3.4 ACL 策略设计建议
| 证书 CN 模式 | 允许发布 | 允许订阅 | 说明 |
|---|---|---|---|
device-{id} |
devices/{id}/telemetry |
devices/{id}/command |
设备只能操作自己的主题 |
gateway-{region} |
gateways/{region}/+/uplink |
gateways/{region}/+/downlink |
网关可代理子设备 |
admin-* |
# |
# |
管理账号(仅限运维跳板机) |
4. 安全加固要点
- Header 防伪 :Mosquitto 必须绑定到
127.0.0.1或通过 VPC Security Group 严格限制来源 IP 仅为 ALB ENI,防止绕过 ALB 直接伪造 Header - CN 白名单校验:认证服务中对 CN 做正则校验,避免特殊字符导致注入或路径遍历
- 日志审计 :Mosquitto 开启
log_type security,记录每次认证的 CN、IP、时间戳;认证服务同步写入结构化日志 - 证书吊销检查:ALB 的 mTLS 已内置 CRL/OCSP 检查,无需后端重复验证,但需确保 CRL 更新及时
- 超时与限流:在 ALB 上设置 Idle Timeout ≤ 60s;认证服务加 Redis 缓存(TTL 30s)减少查询压力,避免高频连接拖垮认证服务
5. 测试验证清单
- 无证书设备连接 → ALB 拒绝,Mosquitto 无日志
- 无效/过期证书设备连接 → ALB 拒绝,Mosquitto 无日志
- 有效证书但 CN 不在 ACL 范围 → Mosquitto 返回 CONNACK 0x05 (Not Authorized)
- 有效证书且 CN 匹配 → 正常收发指定主题消息
- 尝试越权订阅其他设备主题 → 被 ACL 拦截,日志记录 deny
- 直接从内网 IP 连接 1883(无 Header)→ 被插件拒绝
- 认证服务宕机 → Mosquitto 拒绝所有新连接(fail-closed)
6. 后续演进建议
- 短期(1-2天):按本方案落地,完成基础身份化改造
- 中期:将认证服务接入公司统一 IAM/OAuth2 体系,实现证书与用户/设备账号绑定,支持动态权限下发
- 长期:评估迁移至 EMQX Enterprise,其原生支持 ALB Header 认证 + 可视化 ACL 管理 + 规则引擎数据转发,免维护自定义插件,更适合大规模生产环境