NFSv4 客户端标识符¶
本文档解释了 NFSv4 协议如何标识客户端实例,以便在系统重启期间维护文件打开和锁定状态。每个客户端都维护一个特殊的标识符和主体。这些可以通过管理员、站点管理员提供的脚本或 Linux 发行版提供的工具进行设置。
如果客户端的 NFSv4 标识符及其主体选择不当,则存在风险。
简介¶
NFSv4 协议使用“基于租约的文件锁定”。租约帮助 NFSv4 服务器提供文件锁定保证并管理其资源。
简单来说,NFSv4 服务器为每个 NFSv4 客户端创建一个租约。服务器在客户端的租约下收集每个客户端的文件打开和锁定状态。
客户端负责定期续订其租约。在租约保持有效期间,持有该租约的服务器保证客户端创建的文件锁保持有效。
如果客户端停止续订其租约(例如,如果它崩溃了),NFSv4 协议允许服务器在一段时间后删除客户端的打开和锁定状态。当客户端重新启动时,它会向服务器指示与其先前租约相关的打开和锁定状态不再有效,可以立即销毁。
此外,每个 NFSv4 服务器管理一个持久的客户端租约列表。当服务器重新启动并且客户端尝试恢复其状态时,服务器使用此列表来区分在服务器重新启动之前持有状态的客户端和发送新的 OPEN 和 LOCK 请求的客户端。这使得文件锁可以在服务器重启后安全地持久存在。
NFSv4 客户端标识符¶
每个 NFSv4 客户端都向 NFSv4 服务器提供一个标识符,以便它们可以将客户端与其租约相关联。每个客户端的标识符由两个元素组成
co_ownerid:一个任意但固定的字符串。
引导验证器:一个 64 位化身验证器,使服务器能够区分同一客户端的连续引导周期。
NFSv4.0 规范将这两个项目称为 “nfs_client_id4”。NFSv4.1 规范将这两个项目称为 “client_owner4”。
NFSv4 服务器将此标识符与客户端在呈现它时使用的主体和安全类型相关联。服务器使用此主体来授权客户端发送的后续租约修改操作。实际上,这个主体是标识符的第三个元素。
作为呈现给服务器的身份的一部分,一个好的“co_ownerid”字符串具有几个重要的属性
“co_ownerid”字符串在重启恢复期间标识客户端,因此该字符串在客户端重启后是持久的。
“co_ownerid”字符串帮助服务器将客户端与其他客户端区分开来,因此该字符串是全局唯一的。请注意,没有中央机构分配 “co_ownerid” 字符串。
由于 “co_ownerid” 字符串经常在网络上以明文形式出现,因此不会泄露有关客户端本身的私人信息。
“co_ownerid” 字符串的内容在客户端尝试重启后进行 NFSv4 挂载之前设置且不变。
NFSv4 协议对 “co_ownerid” 字符串的大小设置了 1024 字节的限制。
保护 NFSv4 租约状态¶
NFSv4 服务器使用如上所述的 “client_owner4” 为每个客户端分配一个唯一的租约。在这种方案下,在某些情况下,客户端会互相干扰。这被称为 “租约窃取”。
如果不同的客户端呈现相同的 “co_ownerid” 字符串并使用相同的主体(例如,AUTH_SYS 和 UID 0),服务器无法判断客户端是否不是同一个客户端。每个不同的客户端都呈现不同的引导验证器,因此服务器会认为有一个客户端在频繁重启。在这种情况下,任何一个客户端都无法维持打开或锁定状态。
如果不同的客户端呈现相同的 “co_ownerid” 字符串并使用不同的主体,服务器很可能会允许第一个客户端正常运行,但会拒绝随后使用相同 “co_ownerid” 字符串的客户端。
如果客户端的 “co_ownerid” 字符串或主体不稳定,则无法保证服务器或客户端重启后的状态恢复。如果客户端意外重启,但向服务器呈现不同的 “co_ownerid” 字符串或主体,服务器会孤立客户端以前的打开和锁定状态。这将阻止访问锁定的文件,直到服务器删除孤立状态。
如果服务器重启,并且客户端向服务器呈现更改后的 “co_ownerid” 字符串或主体,服务器将不允许客户端恢复其打开和锁定状态,并且可能会在此期间将这些锁授予其他客户端。这被称为 “锁定窃取”。
租约窃取和锁定窃取增加了拒绝服务的可能性,并且在极少数情况下甚至会导致数据损坏。
选择合适的客户端标识符¶
默认情况下,Linux NFSv4 客户端实现构造其 “co_ownerid” 字符串,首先是 “Linux NFS” 字样,然后是客户端的 UTS 节点名称(顺便说一句,该节点名称与 AUTH_SYS 凭据中的 “机器名称” 相同)。在小型部署中,这种结构通常是足够的。但是,通常情况下,节点名称本身不够唯一,并且可能会意外更改。有问题的情况包括
NFS 根(无盘)客户端,其中本地 DHCP 服务器(或等效服务器)不提供唯一的主机名。
单个 Linux 主机内的 “容器”。如果每个容器都有单独的网络命名空间,但不使用 UTS 命名空间来提供唯一的主机名,则可能会有多个具有相同主机名的 NFS 客户端实例。
跨多个管理域访问公共 NFS 服务器的客户端。如果主机名不是集中分配的,则除非主机名中包含域名,否则无法保证唯一性。
Linux 提供了两种机制来为其 “co_ownerid” 字符串添加唯一性
- nfs.nfs4_unique_id
此模块参数可以通过内核命令行或在加载 “nfs” 模块时设置任意唯一字符串。
- /sys/fs/nfs/net/nfs_client/identifier
此虚拟文件自 Linux 5.3 起可用,它对访问它的网络命名空间是本地的,因此当主机名保持一致时,可以区分网络命名空间(容器)。
请注意,此文件在命名空间创建时为空。如果容器系统可以访问某种每个容器的标识,则可以使用该唯一符。例如,可以使用容器的内部标识在启动时形成一个唯一符
- sha256sum /etc/machine-id | awk ‘{print $1}’ \
> /sys/fs/nfs/net/nfs_client/identifier
安全注意事项¶
强烈建议对租约管理操作使用加密安全。
如果未配置使用 Kerberos 的 NFS,Linux NFSv4 客户端会使用 AUTH_SYS 和 UID 0 作为其客户端身份的主体部分。此配置不仅不安全,而且会增加租约和锁定窃取的风险。但是,对于没有本地持久存储的客户端配置,这可能是唯一的选择。在这种情况下,“co_ownerid” 字符串的唯一性和持久性至关重要。
当 Linux NFS 客户端上存在 Kerberos keytab 时,客户端会尝试使用该 keytab 中的一个主体来向服务器标识自身。“sec=” 挂载选项不控制此行为。或者,具有 Kerberos 主体的单用户客户端可以使用该主体代替客户端的主机主体。
为此目的使用 Kerberos 使客户端和服务器能够对所有 “sec=” 设置涵盖的操作使用相同的租约。此外,Linux NFS 客户端将 RPCSEC_GSS 安全类型与 Kerberos 和完整性 QOS 一起使用,以防止传输中租约修改请求被修改。
其他说明¶
Linux NFSv4 客户端在访问的每个 NFSv4 服务器上建立一个租约。然后,来自特定服务器的 Linux NFSv4 客户端的 NFSv4 挂载会共享该租约。
一旦客户端建立打开和锁定状态,NFSv4 协议允许租约状态转移到其他服务器,跟随已迁移的数据。这完全对正在运行的应用程序隐藏了数据迁移。Linux NFSv4 客户端通过向其遇到的所有服务器呈现相同的 “client_owner4” 来促进状态迁移。
另请参阅¶
nfs(5)
kerberos(7)
NFSv4.0 规范的 RFC 7530
NFSv4.1 规范的 RFC 8881。