跳到主要内容
版本:开发版

安全模型

本页说明 DeviceChain 在哪里划定信任边界、目前由什么强制执行每条边界,以及哪些尚未强制执行。已接受的缺口单独列在一节中,便于你据此规划。

报告漏洞请遵循安全政策中的流程。

信任边界一览​

边界两侧如何区分由谁强制执行
租户与租户每个租户拥有的行和每个消息 subject 都带有租户每次查询上的数据库作用域,以及代理上按租户划分的 subject
设备与设备设备连接只能使用自己的 subject代理,通过对平台的授权回调
陌生方与首次接触没有凭据的设备只能凭自动注册配置共用的密钥和密文注册服务器生成的 256 位密文、检查之前有界的工作量,以及每个配置的上限。参见自注册
人员或程序与 API带签名的令牌,在每个服务中校验令牌签名、租户声明和权限
密钥与读取者密钥被密封,且从不返回实例根密钥下的信封加密
租户与网络租户提供的目标无法到达内部地址连接建立时的检查
互联网与平台自身的端点服务令牌签发端点和签名密钥集在任何路由都无法到达的端口上提供独立的内部监听器,而不是 Ingress 规则
平台服务之间各自独立的代理和数据库登录部分。 参见尚未强制执行的内容

租户​

同一组服务为所有租户提供服务,因此隔离在数据和消息中强制执行,而不是依靠独立的 Pod。

  • 存储。 租户拥有的表带有租户,中央数据库作用域为每次读取添加租户条件,并为每次写入标记租户。没有租户上下文的租户作用域查询会被拒绝,而不是不加过滤地执行。
  • 消息。 subject 按租户划分,格式错误的租户段会在服务发布或订阅处被拒绝。
  • API。 请求的租户取自调用方已验证令牌中的租户声明。唯一的例外是服务调用另一个服务:它在请求头中指明租户,仅在服务令牌的签名验证通过后才会采用。

参见多租户。

设备​

  • 传输。 代理的 MQTT 和 NATS 监听器默认使用 TLS,只能为本地调试而关闭。HTTP 接入由实例 ingress 的 TLS 保护。
  • 身份由代理断言。 代理请求平台批准每个连接,平台将该连接绑定到那一台设备自己的 subject。平台对事件所信任的设备身份,是代理投递该事件所用的 subject,而不是载荷中的声明。设备无法向其他设备或其他租户的 subject 发布,也无法读取它们的命令。MQTT 客户端 ID 还必须是设备自己的 {instanceId}:{tenant}:{deviceToken},其后可选地跟上 : 和设备自选的后缀,因此一台设备无法接管另一台设备的会话。
  • 凭据。 设备认证的默认模式是 required:只接受来自已认证设备的事件。在平台的 MQTT 代理上,设备在连接时认证;通过 HTTP 或你自己的代理时,凭据随事件正文传输。凭据认证了某台设备之后,指名其他设备的事件会被拒绝。禁用、轮换或删除凭据会立即拒绝新连接,并结束该设备已打开的会话。参见设备凭据,其中包括撤销多快能影响处理中的事件。
  • 静态存储的凭据。 MQTT_BASIC 凭据的密码和 ACCESS_TOKEN 凭据的令牌都仅以带密钥的单向摘要形式存储,所用密钥由实例根密钥派生,不保存在数据库中。预配配置文件的密钥和设备认领的密钥以同样方式存储,各自使用独立的密钥。两者都只在设置它的那次调用的响应中显示一次,之后不再返回;读取访问令牌凭据时返回的是一个指纹,不能当作令牌使用。因此,数据库或备份泄露不会暴露任何可用的设备密码、令牌、预配密钥或认领密钥。
  • **自注册。**尚无凭据的设备可以通过 HTTPS 出示自动注册配置共用的密钥和密文来自行注册(设置方法)。密文由服务器生成(256 位随机数),仅以带密钥的摘要形式存储,并以恒定时间比较。认证失败的所有情形,包括未知的租户或密钥、已禁用或已过期的配置、正在删除的租户,都返回同样的 401,且代价相同,因此响应从不说明是哪一部分有误。为陌生方所做的工作在检查密文之前就受到限制:请求正文的期限、8 KiB 的上限、处理中请求数的上限,以及每个客户端地址的配额。只有当 infrastructure.http.trustedProxies 列出了服务前面的代理,该地址才是真实客户端的地址;参见客户端地址与受信代理。密文被接受之后,才会应用来自租户套餐或租户覆盖值的每租户上限。设备自己选择凭据,平台只保留它的摘要。**设备群密文是首次接触的信任锚。**持有它的人,在 ALLOW_NEW 下可以注册任何尚无人注册的序列号,在 CHECK_PRE_PROVISIONED 下可以注册任何已预指定但尚未注册的序列号,数量以该配置的 maxDevices 为限。它永远无法接管已持有凭据的设备,也无法接管未为该配置预指定的设备。如果它泄露,请轮换它并吊销它注册的设备。
  • **客户端证书。**设备可以在平台的 MQTT 代理上用 X.509 客户端证书认证,在 TLS 握手中证明自己持有密钥,因此不存储也不发送任何可重放的东西。平台在每次连接时对照租户添加的信任锚检查证书链,要求 clientAuth 密钥用途,并拒绝 SHA-1、弱密钥和带名称约束的 CA。**信任锚是身份的边界:**任何能从该 CA 获得证书的人都可以在证书配置下注册,直到其设备数量上限,所以请锚定在最窄的 CA 上,并在发生泄露时禁用信任锚。租户来自 MQTT 用户名,绝不来自证书。MQTT 客户端 ID 被固定到设备,因此一张证书无法接管另一台设备的会话,客户端 ID 不匹配的首次连接会在写入任何内容之前被拒绝。会话在证书链中最早的到期时间结束。没有 CRL 或 OCSP;通过禁用凭据或信任锚来撤销,这会结束已打开的会话,并使用短期证书。见证书身份。
  • 事件正文中的凭据。 在平台的 MQTT 代理上,连接已经认证了设备,因此正文中还携带凭据的事件会被拒绝,绝不会在移除凭据后被接受:仍在发送凭据的设备会停止上报,拒绝会被计数,以便找到该设备并轮换其凭据。通过 HTTP 或你自己的代理时,正文就是凭据的传输方式,密码或令牌会在事件放入平台内部事件流之前被密封。在所有传输方式上,为诊断而保留的无法解码消息的副本都会移除其中的凭据。
  • 网关。 网关(例如边缘代理)是代表其他设备发言的设备。它以自己的身份登录并在自己的命名空间中发布,平台只接受分配给它的设备的事件。网关不能在其他网关或其他设备的 subject 中发布;指向其并不代表的设备时,事件会被拒绝、记录并告知该网关,而不是被确认后丢失。网关代表它的子设备发言。 被攻陷的网关只能伪造分配给它的设备的事件,并且只在它被停用或其凭据被吊销之前有效。它所称事件来自哪个本地客户端,其可信度只取决于其自身网络的本地认证强度。把设备分配给网关所需的权限与为设备签发凭据相同,因为它授予的是同样的能力。

人员与程序​

  • 令牌。 登录会签发使用 RS256 签名的访问令牌和刷新令牌。服务根据已发布的密钥集在本地验证,因此没有请求需要等待对登录服务的调用。签名密钥仅由 user-management 持有,并在实例根密钥下密封。
  • 权限。 角色携带权限,API 在每次调用时检查。成员资格可以限制在某个客户的数据范围内,租户自行管理的成员、邀请和角色,授予的权限不能超过授予者自己拥有的权限。参见治理与配额。
  • 集成。 租户的服务账号用客户端 ID 和密钥换取有效期 5 分钟的访问令牌(OAuth client_credentials 授权方式),没有刷新令牌,也不涉及任何人的登录。账号的权限恰好是其角色的权限,其密钥只以带密钥摘要的形式保存,其操作以它自己的名称审计。
  • 入站集成。 LoRaWAN 网络服务器或 webhook 发送方使用每个集成各自的 bearer 令牌进行认证;令牌由服务器生成,只显示一次,仅以带密钥的摘要形式存储。令牌在读取正文之前检查一次,事件被处理时再检查一次。它代表其范围内设备类型中所有带别名的设备发言,因此请严格限定集成的范围,并在令牌可能泄露时轮换。
  • 单点登录。 租户可以要求通过自己的身份提供商登录。参见单点登录。
  • AI 代理。 可选的 MCP 服务器使用调用方自己的令牌调用平台。它自己不持有令牌,因此代理只能读取其用户可读取的内容。
  • 滥用限制。 接入有按租户的速率上限,出站连接器有按租户的上限,MCP 服务器限制每个调用方,GraphQL 请求的执行和请求体大小有上限,登录失败尝试有速率限制。GraphQL API 本身没有按租户的请求速率限制。

双重身份验证​

账号可以要求在密码之后再输入验证器应用生成的代码。使用方法见该指南;本节说明它能保证什么,以及边界在哪里。

  • 第二因素把关令牌的签发。 当设有第二因素的账号密码正确时,登录不会签发任何东西:没有会话,没有令牌,只有一个短期有效的质询。令牌在代码被接受后才签发。由于在此之前令牌并不存在,信任任何有效令牌的运维管理 API 也在保护范围之内。
  • 此人如何证明身份,由服务器记录。 由该次登录派生的每个令牌都带有 amr 声明:应用代码为 ["pwd","otp","mfa"],恢复码为 ["pwd","mfa"],仅密码为 ["pwd"]。它读取自会话记录,从不取自客户端。租户的要求在每一次进入租户的授权处检查:选择租户、刷新会话,以及 MCP 或 OAuth 连接的授权步骤。升级之前创建的会话没有记录 amr,因此要求该因素的租户会拒绝它。服务令牌和服务账号令牌从不携带该声明,声称带有它的令牌会被拒绝。
  • 静态存储的密钥。 验证器的密钥在实例根密钥之下密封于密钥存储中。它只在校验代码时读取,在设置时返回一次,此后不再返回,也不缓存。恢复码带有 80 位随机数据,仅以单向摘要形式保存。
  • 一个代码只能用一次。 代码只在其步长新于上一次被接受的步长时才被接受,两个使用同一代码的请求只会有一个成功。接受当前时间前后各一个 30 秒步长内的代码。
  • 先计数,再检查。 每个错误代码在被评估之前就记在账号名下,因此大量同时发起的猜测无法越过限制。同一来源地址的错误代码达到 5 次后,该地址被锁定;所有地址合计达到 20 次后,验证器对所有人锁定;每次锁定持续一分钟,之后每多一次失败就加倍,最长 15 分钟。被锁定的尝试得到与错误代码完全相同的回答。恢复码因无法猜测而不受锁定影响。
  • 服务账号、设备和单点登录不在其内。 服务账号从不使用密码登录。单点登录会话由其连接要求的认证方式约束,而不是由租户的设置约束。参见单点登录。

它不做的事,直说如下:

  • 面对已经掌握密码的人,它能减缓猜测,但不能阻止猜测。 每一次对代码的猜测都来自通过了密码这一步的人。在上限下,前 20 次猜测之后,攻击者每小时约有四次猜测机会,每次在一百万个可能中有三个有效代码可以命中。以这个速度持续猜测一年的人,约有 10% 的机会进入。MfaIdentityLocked 告警因此在第一次锁定时就触发,其补救办法是重置密码。
  • 持有密码的人可以让所有者的验证器一直处于锁定状态。 续期锁定只需要在每次到期时输错一个代码。所有者仍可使用恢复码登录,重置密码即可结束这种情况。设计上没有永久锁定,因为永久锁定会让任何知道某个地址的人把它的主人锁在外面。
  • 能够控制转发地址头的发送者可以刷新按地址的额度。 所有地址合计的限制仍然有效。
  • 租户的要求约束该租户,而不约束运维人员。 它决定谁可以进入该租户。它不限制管理控制台或管理 API,面向运维人员的整个实例范围的要求目前尚不可用。仅用密码会话的超级用户可以进入要求该因素的租户,每次进入都会被审计。运维人员应当开启它,而审计日志和告警是你监视针对其账号的尝试的方式。
  • 质询表明密码是正确的。 这是任何跟在密码之后的第二因素所固有的。在代码被接受之前,不会透露账号的其他任何信息。
  • 恢复码在令牌签发之前就被消耗。 如果服务在两者之间失败,该恢复码就作废了,用户需要用另一个重新登录。

不会离开集群的端点​

有两个端点只供平台自身的服务使用。/auth/service-token 用共享的服务密钥换取短期令牌,/auth/jwks 是所有服务用于校验令牌的密钥集。user-management 在单独的内部端口(8082)上提供这两个端点,而不是在公开端口上。Chart 只在集群内部的 Service 上发布该端口,没有任何 Ingress、路由或负载均衡器引用它。

因此,无论前面放的是什么,甚至完全没有 Ingress,公开端口对这两个路径都返回 404。边缘不承担任何安全控制:

  • 自带的 Ingress 控制器不需要为这些路径添加任何规则。 像往常一样路由到 Chart 创建的 Service 即可,外部无法访问令牌签发端点。
  • 令牌签发端点由共享的服务密钥保护。 密钥以恒定时间比较,未配置密钥的实例会拒绝签发。内部端口让该端点远离公共网络;密钥则用于认证能够访问它的调用方。
  • Chart 不设置任何 nginx snippet 注解。 配置 snippet 允许任何能写入 Ingress 对象的人注入 nginx 配置,而这里没有任何东西需要它。仍在运行 ingress-nginx 的集群上,它在本版本中仍接受 snippet,因为尚未升级的实例依赖它们;待没有实例再依赖时,后续版本会将其关闭。
  • 网络策略进一步限制内部端口,但它只是纵深防御,且默认关闭。 启用 networkPolicy.enabled 后,在网络插件强制执行策略的集群上,Chart 会将该端口限制为实例自身命名空间中的 Pod。dcctl 安装时该策略为关闭状态。
  • 外部令牌验证方改用公开的密钥集。 授权服务器元数据中的 jwks_uri 指向 /oauth/jwks,在配置了签发者 URL 后,它提供相同的公钥。

如果你运行自己的策略引擎,请仅允许实例命名空间中的 Pod 访问 user-management 的 8082 端口。每个服务都会通过它在启动时获取密钥集,并签发自己的服务令牌。

密钥​

租户存储的凭据,例如连接器设置和身份提供商的客户端密钥,保存在加密的密钥存储中。每个值用各自的 AES-256-GCM 数据密钥密封,该数据密钥由实例根密钥包裹,根密钥位于实例的 Kubernetes Secret 中。通过 API 该值只写:使用时在服务器内部解析,从不返回。变更会按谁、何时、哪个句柄进行审计,绝不记录值。

根密钥是密钥存储无法自行恢复的唯一内容。请保留一份托管副本:没有它,恢复后的实例无法解密其密钥,也没有人能登录。参见灾难恢复。密钥存储支持可插拔的密钥提供者,但目前随附的唯一提供者是实例根密钥。

出站流量​

Webhook、邮件中继和出站连接器会向租户提供的目标发送数据。每个此类连接都会在建立的那一刻,根据名称实际解析到的地址进行检查,因此在检查与连接之间更改应答的名称无法绕过检查。回环地址、私有地址、运营商级 NAT 地址、链路本地地址和云元数据地址都会被拒绝,被拒绝的目标属于永久失败,而不是重试。运营商可以放行特定的私有地址。chart 的 networkPolicy 是第二层,默认关闭。参见出站连接器。

审计​

每个服务都会为其对租户数据所做的每次创建、更新和删除记录一条日志,并包含执行操作的身份;登录服务还会记录登录事件,例如登录、登录失败和单点登录结果。租户的审计页面显示注册表日志,且只显示该租户自己的条目。登录事件通过运营方的管理 API 读取;其他功能区(例如连接器、仪表板和更新)的日志会被记录,但不会显示在审计页面上。

注册表条目与其所描述的变更在同一事务中写入,两者要么一起保存,要么都不保存。如果条目无法写入,该变更不会生效,调用会报告失败,因此变更绝不会在无记录的情况下完成,而报告的失败也绝不对应已保存的变更。登录条目写入失败只会记入日志,不会阻止登录。条目从不被编辑,唯一的例外是删除租户时会清空其条目中的名称。该日志不会自动清理。参见审计。

尚未强制执行的内容​

这些缺口已知,并在当前版本中被接受。请据此规划。

  1. **升级后的实例在下一次升级之前仍保留代理的共用登录。**每个功能区都以自己的登录连接代理,登录被限定在它所使用的 subject、流、消费者和存储桶范围内,而且没有任何服务可以创建、修改或删除流或存储桶:这件事由一个单独的管理登录完成,在 Chart 于每次安装和升级之前运行的作业中执行;服务只检查自己打开的对象是否与其版本的声明一致,不一致时拒绝启动并指明差异。因此,被攻陷的服务无法通过重新配置流来写入只有设备才能写入的 subject,也就不能通过这条途径伪造设备事件、命令响应或设备上报。剩下的是早期版本使用的共用登录:新实例从不拥有它;从拥有它的版本升级而来的实例,会在为每个功能区提供自己登录的那次升级中保留它,它留在代理的配置和每个 Pod 读取的实例配置里,并保留原有的权限,包括管理流,因此该实例上的 Pod 仍可出示它。下一次 dcctl upgrade 会把它从这两处移除。接收设备流量的服务(event-sources、device-management、lwm2m-ingest 和 sparkplug-ingest)仍是平台可信核心的一部分,因为它们本来就要写入设备数据。另有两项授权有意更宽:event-sources 可以更新代理自带的 MQTT 存储,user-management 可以在所有流中清除已删除租户的数据。被攻陷的服务仍可能造成丢失(而不是伪造):往它可以创建消费者的流里填充消息,直到较旧的消息被丢弃。
  2. 实例中所有服务共用一个数据库登录。 所有服务以同一个登录访问实例的关系数据库,因此被攻陷的服务可以读写其他功能区的表。数据库中的租户隔离依赖应用的租户作用域,而不是数据库权限。按服务划分的独立角色计划在 1.0.x 版本中提供。
  3. 代理可能丢失的 MQTT 确认。 默认情况下,代理可能已确认某条 QoS 1 消息却随后丢失它,有两种方式。一是在捕获流更换主节点期间(代理重启、升级或节点丢失都会引起),无论负载高低。二是不需要任何重启:在持续负载下,如果 event-sources 落后、捕获流的积压达到上限,该流会丢弃最早的未读消息,devicechain_<area>_jetstream_consumer_unread_skipped_total 会统计这些丢失,JetStreamDurableLostUnread 告警也会触发。开启 infrastructure.nats.mqttFailClosed 后,确认即表示平台已保存该消息;代价是没有主节点或消息存储已满期间代理会拒绝 QoS 1 发布,且实例能跟上的流量更低,因此它适用于按低于该上限的规模部署的安装。该设置默认关闭;dcctl bootstrap 或 dcctl upgrade 上的 --mqtt-fail-closed 可将其开启,权衡见可观测性。
  4. 任何能读取实例 Kubernetes Secret 的人都能读取根密钥。 请相应限制对该 Secret 和集群 etcd 的访问。
  5. 双重身份验证只保护密码,也仅保护密码。 它不会让运维人员的管理平面遵守租户的要求;已掌握密码并按锁定上限持续猜测的攻击者,每年约有 10% 的机会得手;持有密码的人可以通过续期锁定,让账号的验证器一直处于锁定状态。参见双重身份验证。
  6. 尚未发布外部渗透测试。

本页不能取代你自己的审查。如果这里的某一条对你的部署构成问题,上文对应的章节会指出控制措施所在的位置。