Skip to content

16.5 ZFS 委托管理

ZFS 提供全面的权限委托系统,允许非特权用户执行 ZFS 管理操作。

例如,如果每个用户的主目录是一个数据集,用户需要获得创建和销毁其主目录快照的权限。执行备份的用户可以获得使用复制功能的权限。

ZFS 还允许使用统计脚本,仅访问所有用户的空间使用数据。权限的委托能力本身也可委托。权限委托适用于每个子命令和大多数属性。

16.5.1 用户级 ZFS 管理

ZFS 委托管理(ZFS delegation)提供细粒度的权限控制,让系统管理员将特定的 ZFS 管理权限授予非特权用户。自 FreeBSD 14.1 起,adduser 会自动为非特权用户的 ZFS 主目录创建独立数据集并支持加密。

该变更(commit 516009ce8d38)使 adduser(8) 在用户主目录的父目录为 ZFS 数据集时,自动为用户创建独立 ZFS 数据集,例如 /home/xxx 继承自 /homeadduser-Z 参数可禁用此行为,同时支持为非特权用户的 ZFS 主目录启用加密。

16.5.2 基础用户级 ZFS 管理

以非特权用户的 ZFS 数据集为例。安装系统时手动创建了两个普通用户 aria2 和 safreya。

列出系统中所有 ZFS 文件系统及其属性:

sh
% zfs list
NAME                                           USED  AVAIL  REFER  MOUNTPOINT
zroot                                         53.7G   396G    96K  /zroot
zroot/ROOT                                    12.8G   396G    96K  none
zroot/ROOT/14.1-RELEASE-p3_2024-09-17_194642     8K   396G  11.6G  /
zroot/ROOT/default                            12.8G   396G  11.9G  /
zroot/aria2                                    187M   396G   187M  /usr/local/data/aria2
zroot/home                                    7.74G   396G    96K  /home
zroot/home/aria2                               128K   396G   128K  /home/aria2   # 请注意此行
zroot/home/safreya                            7.74G   396G  7.70G  /home/safreya # 请注意此行
zroot/jails                                   3.12G   396G  3.12G  /usr/jails
zroot/sec                                     28.5G   396G  28.5G  /usr/local/data/sec
zroot/tmp                                      102M   396G   102M  /tmp
zroot/usr                                     1.34G   396G    96K  /usr
zroot/usr/ports                               1.34G   396G  1.34G  /usr/ports
zroot/usr/src                                   96K   396G    96K  /usr/src
zroot/var                                     1.58M   396G    96K  /var
zroot/var/audit                                 96K   396G    96K  /var/audit
zroot/var/crash                                 96K   396G    96K  /var/crash
zroot/var/log                                 1.02M   396G  1.02M  /var/log
zroot/var/mail                                 168K   396G   168K  /var/mail
zroot/var/tmp                                  120K   396G   120K  /var/tmp

其中:

sh
zroot/home/aria2                               128K   396G   128K  /home/aria2
zroot/home/safreya                            7.74G   396G  7.70G  /home/safreya

即在创建用户时,系统已默认为用户 aria2 和 safreya 分别创建了各自独立的数据集 zroot/home/aria2 和 zroot/home/safreya。

接下来,分别查看两个数据集上的用户权限。

sh
% zfs allow zroot/home/aria2        # 显示或设置 zroot/home/aria2 文件系统的权限授权
---- Permissions on zroot/home/aria2 ---------------------------------
Local+Descendent permissions:
        user aria2 create,destroy,mount,snapshot
safreya ~ % zfs allow zroot/home/safreya
---- Permissions on zroot/home/safreya -------------------------------
Local+Descendent permissions:
        user safreya create,destroy,mount,snapshot

由输出可知,系统在创建用户时,会默认为其数据集授予 create、destroy、mount 和 snapshot 四项权限。

ZFS 委托权限存储在数据集的元数据中。“Local+Descendent permissions”表示该权限设置既适用于当前数据集,也会继承到其子数据集。

zfs allow <用户> allow <数据集> 赋予指定用户在目标数据集或其子数据集上将他们所拥有的任何权限委派给其他用户的能力。如果某用户拥有 snapshot 权限和 allow 权限,该用户可以将 snapshot 权限授予其他用户。

因此,对于这两个数据集,普通用户也可使用快照功能:

sh
% zfs snapshot zroot/home/safreya@snap1   # 为 zroot/home/safreya 文件系统创建快照 snap1
% zfs list -t snap                        # 列出所有 ZFS 快照
NAME                                       USED  AVAIL  REFER  MOUNTPOINT
zroot/home/safreya@snap1                     0B      -  7.70G  -

再来看 createdestroymount 权限:

sh
% zfs create zroot/home/safreya/dataset_1        # 在 zroot/home/safreya 下创建一个新的 ZFS 数据集 dataset_1
cannot mount 'zroot/home/safreya/dataset_1': Insufficient privileges
filesystem successfully created, but not mounted

创建新数据集涉及挂载,这要求将 FreeBSD 的 sysctl vfs.usermount 设置为 1,以允许非 root 用户挂载文件系统。

sh
% su -m root -c 'sysctl vfs.usermount=1'
Password:
vfs.usermount: 0 -> 1
% zfs create zroot/home/safreya/dataset_2        # 在 zroot/home/safreya 下创建一个新的 ZFS 数据集 dataset_2
% zfs list        # 列出所有 ZFS 文件系统及其属性
NAME                                           USED  AVAIL  REFER  MOUNTPOINT

      ...此处省略一部分...

zroot/home                                    7.79G   396G    96K  /home
zroot/home/aria2                               128K   396G   128K  /home/aria2
zroot/home/safreya                            7.79G   396G  7.68G  /home/safreya
zroot/home/safreya/dataset_1                    96K   396G    96K  /home/safreya/dataset_1
zroot/home/safreya/dataset_2                    96K   396G    96K  /home/safreya/dataset_2

      ...此处省略一部分...
sh
% zfs destroy zroot/home/safreya/dataset_1       # 删除 ZFS 数据集 zroot/home/safreya/dataset_1
% zfs destroy zroot/home/safreya/dataset_2        # 删除 ZFS 数据集 zroot/home/safreya/dataset_2

由输出可知,创建和销毁权限可正常使用。为了防止滥用,还有另一个限制:非 root 用户必须拥有挂载点的所有权,才能挂载文件系统。

至此,用户级 ZFS 管理的基本功能已就绪。但 rollback 权限默认不可用,需由 root 用户为普通用户单独授予。

将 zroot/home/safreya 文件系统回滚到 snap1 快照状态:

sh
% zfs rollback zroot/home/safreya@snap1
cannot rollback 'zroot/home/safreya': permission denied

以 root 用户授予 safreya 用户对 zroot/home/safreya 文件系统执行回滚操作的权限:

sh
% su -m root -c 'zfs allow safreya rollback zroot/home/safreya'
Password:
% zfs rollback zroot/home/safreya@snap1

16.5.3 用户级 ZFS 加密功能

在 FreeBSD 14.1 中,如果要在用户级使用 ZFS 加密功能,必须为用户授予特定权限。

授权用户 safreya 对 zroot/home/safreya 文件系统执行密钥管理和加密操作:

sh
% su -m root -c 'zfs allow safreya change-key,load-key,keyformat,keylocation,encryption zroot/home/safreya'
Password: # 此处输入 root 账户密码然后按回车键

显示 zroot/home/safreya 文件系统的当前权限授权设置:

sh
% zfs allow zroot/home/safreya
---- Permissions on zroot/home/safreya -------------------------------
Local+Descendent permissions:
        user safreya change-key,create,destroy,encryption,keyformat,keylocation,load-key,mount,snapshot

change-keyload-keykeyformatkeylocationencryption 这五项权限属性用于 ZFS 的加密功能。

创建启用加密的 ZFS 数据集 zroot/home/safreya/secret,并将密码用作密钥格式:

sh
% zfs create -o encryption=on -o keyformat=passphrase zroot/home/safreya/secret
Enter new passphrase:     # 在此处输入密码,密码不会回显
Re-enter new passphrase:  # 在此处重复输入密码,密码不会回显

查看加密情况:

sh
% zfs get mounted zroot/home/safreya/secret        # 查询 zroot/home/safreya/secret 数据集是否已挂载
NAME                       PROPERTY  VALUE    SOURCE
zroot/home/safreya/secret  mounted   yes      -

查看 mounted 属性,加密数据集创建即挂载,现在创建一份文件,然后卸载加密数据集:

sh
% cd secret                                      # 进入 secret 数据集目录
% echo "a secret makes a man mad" > abc.txt     # 在 secret 数据集中创建文件 abc.txt 并写入内容
% cd ..                                          # 返回上一级目录
% zfs unmount zroot/home/safreya/secret         # 卸载 secret 数据集
% zfs unload-key zroot/home/safreya/secret      # 卸载 secret 数据集的加密密钥
% zfs get mounted zroot/home/safreya/secret     # 查询 secret 数据集是否已挂载
NAME                       PROPERTY  VALUE    SOURCE
zroot/home/safreya/secret  mounted   no       -
% ls secret # 并无输出

卸载加密数据集时必须同时卸载其密钥,挂载数据集时也必须先加载密钥:

sh
% zfs load-key zroot/home/safreya/secret   # 加载 secret 数据集的加密密钥
Enter passphrase for 'zroot/home/safreya/secret':  # 提示输入密钥密码
% zfs mount zroot/home/safreya/secret      # 挂载 secret 数据集
% ls secret                                 # 列出 secret 数据集中的文件
Permissions Size User    Date Modified Name
.rw-r--r--    25 safreya 19 Sep 20:26  abc.txt

警告

因为 destroy 权限默认已授予用户,所以无论数据集是否挂载,destroy 子命令都可以成功销毁数据集。因此如果操作者不是该用户本人,仍可能删除数据集。

授权即授予普通用户代理权限,使其在操作授权的数据集时拥有类似 root 的权限,无需输入密码。因此,在授予权限时应合理限制授权范围和权限属性,例如禁用 destroy 权限属性等。

撤销用户 safreya 对 zroot/home/safreya 文件系统的销毁权限:

sh
% su -m root -c 'zfs unallow safreya destroy zroot/home/safreya'
Password:        # 此处输入 root 账户密码然后按回车键

显示 zroot/home/safreya 文件系统的当前权限授权设置:

sh
% zfs allow zroot/home/safreya
---- Permissions on zroot/home/safreya -------------------------------
Local+Descendent permissions:
        user safreya change-key,create,encryption,keyformat,keylocation,load-key,mount,rollback,snapshot

此处,zroot/home/safreya/secret 受 zroot/home/safreya 上“Local+Descendent”权限的作用范围覆盖。ZFS 委托权限不存在继承机制,权限存储在授予它的数据集上,“Local+Descendent”表示该权限的作用范围同时覆盖本数据集及其子孙数据集。在父数据集上撤销权限时,其 Descendent 范围也会移除,子孙数据集上的用户将失去相应权限。如果需要对子孙数据集单独控制权限,需要显式地在子孙数据集上授权或撤销。

16.5.4 adduser 与用户主目录加密

adduser 命令可直接使用加密的用户主目录数据集,但默认授予的权限不足,卸载后普通用户无法直接挂载。

sh
# adduser        # 在系统中添加新用户,按照提示输入用户名、密码及其他信息
Username: test
Full name:
Uid (Leave empty for default):
Login group [test]:
Login group is test. Invite test into other groups? []:
Login class [default]:
Shell (sh csh tcsh zsh rzsh git-shell bash rbash nologin) [sh]:
Home directory [/home/test]:
Home directory permissions (Leave empty for default):
Enable ZFS encryption? (yes/no) [no]: yes #该功能为新增功能
Use password-based authentication? [yes]:
Use an empty password? (yes/no) [no]:
Use a random password? (yes/no) [no]:
Enter password:
Enter password again:
Lock out the account after creation? [no]:
Username    : test
Password    : *****
Full Name   :
Uid         : 1003
ZFS dataset : zroot/home/test
Encrypted   : yes
Class       :
Groups      : test
Home        : /home/test
Home Mode   :
Shell       : /bin/sh
Locked      : no
OK? (yes/no) [yes]:
Enter encryption keyphrase for ZFS dataset (zroot/home/test):
Enter new passphrase:
Re-enter new passphrase:
adduser: INFO: Successfully created ZFS dataset (zroot/home/test).
adduser: INFO: Successfully added (test) to the user database.
Add another user? (yes/no) [no]: no
Goodbye!

查看 zroot/home/test 文件系统的当前权限授权设置:

sh
# zfs allow zroot/home/test
---- Permissions on zroot/home/test ----------------------------------
Local+Descendent permissions:
        user test create,destroy,mount,snapshot

卸载数据集和密钥:

sh
# zfs unmount zroot/home/test        # 卸载 zroot/home/test 数据集
# zfs unload-key zroot/home/test     # 卸载 zroot/home/test 数据集的加密密钥

切换到普通用户 test 尝试挂载:

sh
# su test        # 切换到普通用户 test
$ zfs load-key zroot/home/test        # 加载 zroot/home/test 数据集的加密密钥
Enter passphrase for 'zroot/home/test':
Key load error: Permission denied.

Permission denied 表示权限不足,访问被拒绝。

16.5.5 参考文献