site logo

Marico's space

AWS IoT 策略:每个设备的最小权限

服务器技术 2026-10-12 20:56:10 5

最近折腾AWS IoT Core的设备权限管理,踩了几个坑,这篇把问题说清楚。在AWS IoT里,证书(Certificate)负责验证设备身份,策略(Policy)负责控制设备能干什么。两者配合才能真正做到最小权限——不然证书一旦泄露,攻击者能控制你整个账户的所有设备。

之前在系列第三篇里写过一个把设备名写死在策略里的方案,单设备能用,但要管十个设备就得写十个策略,太蠢了。这篇深入看看AWS IoT策略的评估机制,以及怎么用一份策略给所有设备精准划权限。

策略是如何被评估的

每次设备尝试连接、发布或订阅时,AWS IoT都会检查挂在证书上的策略。默认规则很简单:没有明确允许的就是禁止的。如果某个操作被拒绝,AWS直接关闭连接,设备端看不到任何错误信息,只能干瞪眼。

四个核心操作:

操作 资源类型 评估时机
iot:Connect client/<client-id> 连接时
iot:Publish topic/<主题> 每条消息发布时
iot:Subscribe topicfilter/<过滤器> 订阅时
iot:Receive topic/<主题> 每条消息接收时

topic 与 topicfilter 的区别

这是最容易搞混的地方。订阅检查的是你申请订阅的过滤器(topicfilter/...),而接收检查的是每条消息的具体主题(topic/...)。所以一个完整订阅需要两个权限:iot:Subscribe授权topicfilter,iot:Receive授权topic。

通配符不是MQTT那套

策略里*是AWS IAM风格的通配符,可以匹配任意字符,包括斜杠。MQTT的通配符(+单层匹配、#多层匹配)在策略里不是通配符,会被当作普通字符处理。如果设备订阅了home/+/status,策略必须精确匹配topicfilter/home/+/status,或者用*兜底。

策略变量:一套策略管整个设备集群

AWS IoT在每次策略评估时会替换特定的变量,最实用的就是这个:

  • ${iot:Connection.Thing.ThingName}:连接到该证书的设备(Thing)名称

利用这个变量,同一份策略就能给所有设备差异化授权:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "iot:Connect", "Resource": "arn:aws:iot:REGION:CUENTA:client/${iot:Connection.Thing.ThingName}", "Condition": { "Bool": { "iot:Connection.Thing.IsAttached": "true" } } }, { "Effect": "Allow", "Action": "iot:Publish", "Resource": [ "arn:aws:iot:REGION:CUENTA:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update", "arn:aws:iot:REGION:CUENTA:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/get" ] }, { "Effect": "Allow", "Action": "iot:Subscribe", "Resource": [ "arn:aws:iot:REGION:CUENTA:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/delta", "arn:aws:iot:REGION:CUENTA:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/update/rejected", "arn:aws:iot:REGION:CUENTA:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/shadow/get/accepted" ] }, { "Effect": "Allow", "Action": "iot:Receive", "Resource": "arn:aws:iot:REGION:CUENTA:topic/$aws/things/${iot:Connection.Thing.ThingName}/shadow/*" } ]
}

这套策略保证了什么:

  • Client ID必须与设备名一致。设备无法通过伪造身份来冒充其他设备连接。
  • IsAttached条件要求证书必须绑定到设备。否则变量没有值,连接直接被拒绝。
  • 只能操作自己的影子(Shadow)。设备A看不到也改不了设备B的影子数据,即便用的是同一套策略。

要让这套机制正常工作,需要满足两个前提:证书必须绑定到对应设备(通过attach-thing-principal),设备连接时必须用设备名作为client ID。

其他有用的变量:${iot:ClientId}(连接的客户端ID)和${iot:Connection.Thing.Attributes[属性名]}(设备属性,比如所属区域或分组)。

自定义主题也用同样思路

如果除了影子(Shadow)还用到自定义主题,命名时把设备名嵌进去,这样也能统一管控:

智能家居/<thing>/遥测数据 ← 设备发布
智能家居/<thing>/指令 ← 设备接收

对应策略:

{ "Effect": "Allow", "Action": "iot:Publish", "Resource": "arn:aws:iot:REGION:CUENTA:topic/智能家居/${iot:Connection.Thing.ThingName}/遥测数据"
}

主题设计时把设备标识符嵌入路径,是实现最小权限的关键。如果用共享主题如智能家居/遥测数据,那就只能给所有设备开放全部权限,根本做不了隔离。

不连设备也能测试策略

AWS IoT提供了test-authorization命令,不用实际连接就能验证策略:

aws iot test-authorization \ --principal <certificateArn> \ --client-id 我的ESP32 \ --auth-infos '[{"actionType":"PUBLISH","resources":["arn:aws:iot:REGION:CUENTA:topic/$aws/things/另一个设备/shadow/update"]}]'

返回结果会说明操作是否被允许,以及匹配了哪条策略。这是验证"设备A确实无法访问设备B主题"最快的方式。

要监控整个设备集群的安全状况,AWS IoT Device Defender有审计功能,会标记权限过于宽松的策略,比如iot:Connect用了*通配符的。

常见错误

  • 设备反复连接断开。连接时缺少某个权限,通常是漏了Subscribe或者client/...与client ID不匹配。
  • 能订阅但收不到消息。漏了iot:Receive对topic/...的授权。
  • 变量没被替换。证书没绑定到设备,或者设备名写错了。
  • 策略里的$aws被解析。JSON里直接写就行,但如果用模板工具或CDK生成,要确保不会把它当作变量处理。
  • 改完策略不生效。策略变更可能需要几分钟才在现有连接上生效,断开重连即可验证。

常见问题

一个证书能挂多个策略吗?

可以,权限会叠加。适合把通用权限(影子操作)和特定权限(某些设备才需要的操作)分开管理。

IoT策略和IAM策略是一样的吗?

语法相似但本质不同:IoT策略挂在证书(或Cognito身份)上,在消息代理层生效;IAM角色用于API调用,比如从阿里云函数调用其他云服务。两者职责分明,别混用。

证书泄露了怎么办?

如果用的是这篇的策略设计,攻击者拿到证书也只能做那个设备本身能做的事。直接去AWS IoT禁用或吊销证书即可立刻失效。