搭建 nfsv4 server
环境
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.10 (Groovy Gorilla)"
安装 nfs kernel server
nfs 已经在内核中进行了支持:
$ ls /lib/modules/$(uname -r)/kernel/fs/nfs/
blocklayout filelayout flexfilelayout nfs.ko nfsv2.ko nfsv3.ko nfsv4.ko
安装 nfs server:
$ sudo apt install nfs-kernel-server
...
Creating config file /etc/idmapd.conf with new version
Created symlink /etc/systemd/system/multi-user.target.wants/nfs-client.target → /lib/systemd/system/nfs-client.target.
Created symlink /etc/systemd/system/remote-fs.target.wants/nfs-client.target → /lib/systemd/system/nfs-client.target.
nfs-utils.service is a disabled or a static unit, not starting it.
Setting up nfs-kernel-server (1:1.3.4-2.5ubuntu6) ...
Created symlink /etc/systemd/system/multi-user.target.wants/nfs-server.service → /lib/systemd/system/nfs-server.service.
Job for nfs-server.service canceled.
Creating config file /etc/exports with new version
Creating config file /etc/default/nfs-kernel-server with new version
...
nfs-kernel-server
会一并安装 rpcbind
,nfs server 包含下列组件
nfs-config.service
nfs.conf(5)是一个 oneshot 服务,在 nfs 服务运行前被运行,主要功能为读取 /etc/default/nfs-common
/etc/default/nfs-kernel-server
两个文件内容为 nfs 其他组件创建 /run/sysconfig/nfs-utils
配置文件。
关于 nfs 组件的所有配置都可以在这两个文件中进行配置,集中了所有组件配置。其核心为下面这个脚本。
$ cat /usr/lib/systemd/scripts/nfs-utils_env.sh
#!/bin/sh
# Create /run/sysconfig/nfs-utils from NFS' /etc/default/ files, for
# nfs-config.service
nfs_config=/etc/sysconfig/nfs
[ -r /etc/default/nfs-common ] && . /etc/default/nfs-common
[ -r /etc/default/nfs-kernel-server ] && . /etc/default/nfs-kernel-server
mkdir -p /run/sysconfig
{
echo PIPEFS_MOUNTPOINT=/run/rpc_pipefs
echo RPCNFSDARGS=\"$RPCNFSDOPTS ${RPCNFSDCOUNT:-8}\"
echo RPCMOUNTDARGS=\"$RPCMOUNTDOPTS\"
echo STATDARGS=\"$STATDOPTS\"
# The rpc-svcgssd.service systemd file uses SVCGSSDARGS, not
# RPCSVCGSSDARGS, but for a long time just the latter was exported.
# To not break upgrades for people who have worked around this by
# overriding the systemd service to use RPCSVCGSSDARGS, both variables
# are being exported now.
# See https://bugs.launchpad.net/bugs/1616123 and
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=892654 for more details.
echo RPCSVCGSSDARGS=\"$RPCSVCGSSDOPTS\"
echo SVCGSSDARGS=\"$RPCSVCGSSDOPTS\"
} > /run/sysconfig/nfs-utils
# the following are supported by the systemd units, but not exposed in default files
# echo SMNOTIFYARGS=\"$SMNOTIFYARGS\"
# echo RPCIDMAPDARGS=\"$RPCIDMAPDARGS\"
# echo RPCGSSDARGS=\"$RPCGSSDARGS\"
# echo BLKMAPDARGS=\"$BLKMAPDARGS\"
# echo GSS_USE_PROXY=\"$GSS_USE_PROXY\"
nfs-server.service
后端启动的是rpc.nfsd(8).
依赖于:
-
nfs-mountd.service
提供的身份验证功能。 -
nfs-config.service
提供的配置文件。
rpc.nfsd 程序实现了 NFS 服务的用户级别部分。主要功能由 nfsd 内核模块处理。用户空间程序仅指定内核服务应侦听的套接字类型,应支持的 NFS 版本以及应使用的内核线程数。
rpc.mountd 服务提供了满足 NFS 客户端的挂载请求所需的辅助服务。
nfsd 提供了参数 -V -N 用于设置提供的 nfs 服务端版本。
-V or --nfs-version vers
This option can be used to request that rpc.nfsd offer
certain versions of NFS. The current version of rpc.nfsd
can support major NFS versions 2,3,4 and the minor
versions 4.0, 4.1 and 4.2.
-N or --no-nfs-version vers
This option can be used to request that rpc.nfsd does not
offer certain versions of NFS. The current version of
rpc.nfsd can support major NFS versions 2,3,4 and the
minor versions 4.0, 4.1 and 4.2.
在 nfs-config 中参数 RPCNFSDARGS
被用于设置 nfsd 的命令行参数。
nfs-mountd.service
后端启动的是rpc.mountd(8).
rpc.mountd 守护程序实现 NFS MOUNT 协议的服务器端,NFS MOUNT 协议是 NFS v2[RFC1094]和 NFS v3[RFC1813]使用的 NFS 方协议。 它还响应来自 Linux 内核的对客户端进行身份验证的请求,并提供访问权限的详细信息。NFS 服务器(nfsd)维护身份验证和授权信息的高速缓存,该高速缓存用于标识每个重复项的源,然后标识该源对任何本地文件系统的访问权限。 当在高速缓存中找不到所需的信息时,服务器将发送请求到 mountd 来填充缺少的信息。 mountd 使用存储在/var/lib/nfs/etab
文件的信息来响应每个请求,该文件由 exportfs 程序来维护,文件/etc/exports
也由 exportfs 解析并更新至/var/lib/nfs/etab
。
NFS MOUNT 协议有几个过程。最多其中重要的是 MNT 和 UMNT。
MNT 请求有两个参数:一个显式参数为包含根目录挂载路径的 export,一个隐式参数为发送者的 IP 地址。
当从 NFS 客户端接收到 MNT 请求时,rpc.mountd 会根据其导出表检查路径名和发送者的 IP 地址。
如果允许发送者访问请求的 export,则 rpc.mountd 将 export 根目录的 NFS 文件句柄返回给客户端。
然后,客户端可以使用根文件句柄和 NFS LOOKUP 请求来寻找到 export 的目录结构。
rpc.mountd 守护程序通过向/var/lib/nfs/rmtab
添加一个条目来注册每个成功的 MNT 请求。
rpc.mountd 接收到来自 NFS 客户端的 UMNT 请求时只是删除了 /var/lib/nfs/rmtab
的对应条目。
客户端可以使用 showmount(8)命令发现 NFS 服务器当前正在导出的文件系统列表,或已安装其导出的其他客户端列表。
showmount(8)使用 NFS MOUNT 协议中的其他过程来报告有关服务器的导出文件系统的信息
mountd 也提供命令行参数 -V -N 用于指定 nfs 支持版本。
-N mountd-version or --no-nfs-version mountd-version
This option can be used to request that rpc.mountd do not
offer certain versions of NFS. The current version of
rpc.mountd can support both NFS version 2, 3 and 4. If the
either one of these version should not be offered,
rpc.mountd must be invoked with the option --no-nfs-
version <vers> .
-V version or --nfs-version version
This option can be used to request that rpc.mountd offer
certain versions of NFS. The current version of rpc.mountd
can support both NFS version 2 and the newer version 3.
在 nfs-config 中参数 RPCMOUNTDARGS
被用于设置 mountd 的命令行参数。
nfs-idmapd.service
上面提到了 mountd 是实现了 NFS MOUNT 协议 V2 V3,NFS v4 版本不再使用 mountd 作为身份验证。
而使用了nfsidmap(5)。
NFSv4 协议代表本地系统的 UID 和 GID 的值作为在传输时转换为 user@domain 形式的字符串。
从 UID 转换为字符串和将字符串转换为 UID 的过程称为 "ID mapping".
系统通过执行密码或组查询来找到字符串的 user 部分,查找机制在/etc/idmapd.conf
中配置。
默认情况下,字符串的 domain 部分是系统的 DNS 域名,当系统是多宿主的或者系统的 DNS 域名存在与系统的 Kerberos 领域的名称不匹配时也可以在/etc/idmapd.conf
中指定。
如果未在/etc/idmapd.conf
中指定 domain,则本地将查询 DNS 服务器以获取\_nfsv4idmapdomain
文本记录。
如果记录存在,将用作 domain,当记录不存在时,DNS 的域部分将用于 domain。
/usr/sbin/nfsidmap
程序代表内核执行转换。内核使用 request-key
机制执行 upcall 调用。
其由/sbin/request-key
实际调用, 执行转换,并使用结果信息生成 key。内核然后缓存该 key。
nfsidmap 还可以清除内核中缓存的 ID 映射结果,或者撤销一个特定的密钥。
错误的缓存密钥可能会导致文件和目录所有权在 NFSv4 挂载上恢复为"nobody"点。
/etc/request-key.conf
文件将需要修改为了/sbin/request-key
可以正确的 upcall。
应该在调用 keyctl negate 之前应添加下面一行:
create id_resolver * * /usr/sbin/nfsidmap -t 600 %k %d
在 nfs-config 中参数 RPCIDMAPDARGS
被用于设置 idmapd 的命令行参数。
rpcbind.socket
rpcbind 实用程序是用于转换 RPC 程序号的服务器变成通用地址。
它必须在主机上运行才能够在该计算机上的服务器上进行 RPC 调用。
启动一个 RPC 服务时,它会告诉 rpcbind 它正在监听的地址以及准备的 RPC 程序服务编号。
当客户希望对给定的对象进行 RPC 程序号调用时 ,它首先与服务器计算机上的 rpcbind 联系以确定应将 RPC 请求发送到的监听地址
rpcbind 实用程序应在任何其他 RPC 服务之前启动。 通常,标准 RPC 服务器由端口监视器启动,因此在调用端口监视器之前,必须先启动 rpcbind。
启动 rpcbind 时,它将检查特定的名称到地址的转换功能正常。
如果失败,网络配置数据库可能已损坏。由于 RPC 服务无法在这种情况下正常运行,rpcbind 报告错误并终止运行。
rpcbind 实用程序只能由超级用户启动。
exports
exports文件/etc/exports
包含一个本地物理文件表 NFS 客户端可访问的 NFS 服务器上的系统。这个文件内容由服务器管理员维护。
该表中的每个文件系统都有一个选项列表和一个访问控制列表。该表由 exportfs(8) 用于提供信息给 mount(8).
该文件格式类似于 SunOS 导出文件。每行 包含一个导出点和一个用空格分隔的列表 客户端允许此时挂载文件系统。
每个列出的客户之后可能会立即加上括号,以逗号分隔的该客户端的导出选项列表。客户端及其选项列表之间不允许使用空格。
另外,每一行可能有一个或多个默认规格路径名称后的选项,后接破折号(“-”) 通过一个选项列表。选项列表用于所有后续 仅在该行上导出。
要将更改应用于此文件,请运行exportfs -ra
或重新启动 NFS 服务器。
NFS 客户端(访问控制列表)可以通过多种方式指定,具体参见文档。
您可以使用特殊字符串 "gss/krb5", "gss/krb5i", 或"gss/krb5p"使用 rpcsec_gss 限制对客户端的访问安全。
但是,不建议使用此语法。在 Linux 内核上 从 2.6.23 开始,您应该使用"sec="导出选项:
sec=
选项,后跟一个用冒号分隔的安全类型列表,它限制了使用这些类型的客户端的导出。
可用的安全性类型包括:
- sys(默认-无密码安全性)
- krb5(仅身份验证)
- krb5i(完整性保护)
- krb5p(隐私保护)
出于安全风味协商的目的,订单计数:首选风味应首先列出。sec=
选项相对于其他选项的顺序无关紧要,除非您希望根据口味不同地强制实施某些选项。
在这种情况下,您可以包括多个 sec=
选项,并且仅对使用紧接在 sec=
选项后列出的风味的访问强制实施以下选项。
允许以这种方式变化的唯一选项是 ro,rw,no_root_squash,root_squash 和 all_squash。
用户映射
nfsd 的访问控制基于每个 NFS RPC 请求中提供的 uid 和 gid 对服务器计算机上文件的访问。
用户期望的正常行为是,她可以像在普通文件系统上一样访问服务器上的文件。
这要求在客户端和服务器计算机上使用相同的 uid 和 gid。 这并不总是正确的,也不总是令人满意的。
通常,在访问 NFS 服务器上的文件时,不希望将客户机上的 root 用户也视为 root 用户。
为此,uid0 通常映射到另一个 id:所谓的匿名或 nobody
uid。
此操作模式(称为root squashing
)是默认设置,可以使用 no_root_squash
关闭。
默认情况下,exportfs
选择 65534 的 uid 和 gid 来限制访问。
这些值也可以被 anonuid
和 anongid
选项覆盖。
最后,您可以通过指定 all_squash
选项将所有用户请求映射到匿名 uid。
支持下列选项:
-
root_squash
,将请求从 uid/gid 0 映射到匿名 uid/gid。笔记:这不适用于其他任何可能同样敏感 uid 或 gid ,例如用户bin
或组stuff
。 -
no_root_squash
,关闭 root squashing,此选项主要用于无盘客户端。 -
all_squash
,将所有 uid 和 gid 映射到匿名用户。有用于 NFS 导出的公共 FTP 目录,新闻假脱机目录等。相反的选项是no_all_squash
这是默认设置。 -
anonuid
andanongid
,这些选项显式设置了的 uid 和 gid 匿名帐户。此选项主要用于 PC/NFS 客户端,您可能希望在其中出现所有请求来自一个用户。
子目录导出
通常,您只应仅导出文件系统的根。 NFS 服务器还将允许您导出文件的子目录。 文件系统,但是,它有缺点:
首先,恶意用户可能会通过访问猜测导出的子目录外部的文件系统的其他文件的文件句柄.
防止这种情况的唯一方法 是通过使用 no_subtree_check 选项,这可能导致其他问题。
其次,导出选项可能不会以您期望的执行的方式强制执行。例如,security_label 选项不会处理子目录 expoet,以及是否嵌套子目录更改了 security_label 或 sec= 选项,NFSv4 客户端通常仅在父级导出上看到选项。
额外的导出表
读完/etc/exports
后,exportfs
将读取/etc/exports.d
目录中的文件作为额外的导出表。
仅以.exports 结尾的文件将被考虑。以点开头的文件被忽略。额外导出表的格式与/etc/exports
相同。
示例:
# sample /etc/exports file
/ master(rw) trusty(rw,no_root_squash)
/projects proj*.local.domain(rw)
/usr *.local.domain(ro) @trusted(rw)
/home/joe pc001(rw,all_squash,anonuid=150,anongid=100)
/pub *(ro,insecure,all_squash)
/srv/www -sync,rw server @trusted @external(ro)
/foo 2001:db8:9:e54::/64(rw) 192.0.2.0/24(rw)
/build buildhost[0-9].local.domain(rw)
访问控制
nfs 默认提供了以下方式对 export 进行访问控制:
- 服务器通过 IP 地址、主机名或者 net group 限制允许哪些主机访问 export。
- NFS 使用 nfs 客户端声明的 uid/gid 映射到本地用户来进行文件访问权限控制。
恶意配置 uid 和 gid 的客户端也能够访问到非正常权限的文件。为了限制潜在的风险,管理员通常将访问权限限制为对普通用户和组 ID 只读或缩小用户权限。
但是这些解决方案阻止了 NFS 共享的原始目的。
这里还有有一种解决办法是对 NFS 和 rpcbind 上执行限制,就是为其配置 iptables 等防火墙,对 IP 包进行访问控制。但这并能解决目前的问题--允许任意客户端通过密码学的方式进行连接。参考SECURING NFS.
在 nfs 中,在必须不限制客户端的时候(使用了通配符*
的主机匹配),无法做到很好的访问控制。
在上面说到了,nfs 通过 rpcbind 对外提供服务。RFC5403 中定义的 RPCSEC_GSS 协议为基于 RPC 的协议(例如 NFS)提供了强大的安全性。
在使用 RPC 客户端 RPCSEC_GSS 交换 RPC 请求之前必须建立一个 GSS 安全上下文。
安全上下文是启用 GSS-的网络传输两端的共享状态 API 安全服务。
gssd
RFC 5403 中定义的 RPCSEC_GSS 协议用于提供为基于 RPC 的协议(例如 NFS)提供了强大的安全性。
在使用 RPC 客户端 RPCSEC_GSS 交换 RPC 请求之前必须建立一个 GSS 安全上下文。安全上下文是启用 GSS-的网络传输两端的共享状态 API 安全服务。
安全上下文是使用安全凭证建立的。一种凭据授予对安全网络服务的临时访问权限,就像火车票可以临时使用铁路服务。
用户通常通过向以下用户提供密码来获取证书所述的 kinit(1)命令,或者通过在登录时一个 PAM 库。
一种通过用户主体获取的凭证称为用户凭证(有关主体的更多信息,请参阅 kerberos(1)。
某些操作需要不代表任何身份的凭证特定用户或代表主机本身。这种凭证称为机器凭证。
主机使用服务主体建立其机器凭证,该服务主体的加密密码存储在本地文件中称为 keytab。机器凭证仍然有效只要主机可以续签,便无需用户干预。
一旦获得,凭证通常存储在本地具有已知路径名的临时文件。
要使用这些凭证文件建立 GSS 安全上下文,Linux 内核 RPC 客户端依赖于一个称为的用户空间守护程序 rpc.gssd。
该 rpc.gssd 守护程序使用 rpc_pipefs 文件系统与内核通信。
用户凭证
当用户使用诸如 kinit(1)之类的命令进行身份验证时,结果凭证存储在名称众所周知的使用用户的 UID 构造文件中。
用于代表特定的人与 NFS 服务器进行交互的 Kerberos 认证用户,Linux 内核 RPC 客户端请求 rpc.gssd 使用在该用户的凭证文件中凭证初始化安全上下文 。
通常,凭证文件放置在/tmp 中。但是,rpc.gssd 可以在多个目录中搜索凭证文件。有关详细信息,请参见 -d 选项的描述。
机器凭证
pc.gssd 按照以下顺序搜索默认 keytab, /etc/krb5.keytab 以寻找 principal 和密码以建立机器凭证。
对于搜索,rpc.gssd 将<hostname>和<REALM>替换为本地系统的主机名 和 Kerberos 领域。
<HOSTNAME>$@<REALM>
root/<hostname>@<REALM>
nfs/<hostname>@<REALM>
host/<hostname>@<REALM>
root/<anyname>@<REALM>
nfs/<anyname>@<REALM>
host/<anyname>@<REALM>
安装 kerberos
$ sudo apt install krb5-admin-server
...
The following NEW packages will be installed:
krb5-admin-server krb5-config krb5-kdc krb5-user libgssrpc4 libkadm5clnt-mit11 libkadm5srv-mit11 libkdb5-9 libverto-libevent1 libverto1
0
...
Setting up a Kerberos Realm
This package contains the administrative tools required to run the Kerberos master server.
However, installing this package does not automatically set up a Kerberos realm.
This can be done later by running the "krb5_newrealm" command.
Please also read the /usr/share/doc/krb5-kdc/README.KDC file and the administration guide found in the krb5-doc package.
...
Created symlink /etc/systemd/system/multi-user.target.wants/krb5-kdc.service → /lib/systemd/system/krb5-kdc.service.
...
Created symlink /etc/systemd/system/multi-user.target.wants/krb5-admin-server.service → /lib/systemd/system/krb5-admin-server.service.
安装时会指定 default realm,可以填写自己的主机域,可以不填,默认为 MIT 的 domain,后续可更改,对应的配置文件为/etc/krb5.conf
。
还会填写 Kerberos servers,Administrative(password changing) server 的主机名,也可保持空白。
参见:krb5.conf
根据上述的提示:在安装完成后需要使用 "krb5_newrealm"
setup KDC
$ sudo "krb5_newrealm"
This script should be run on the master KDC/admin server to initialize
a Kerberos realm. It will ask you to type in a master key password.
This password will be used to generate a key that is stored in
/etc/krb5kdc/stash. You should try to remember this password, but it
is much more important that it be a strong password than that it be
remembered. However, if you lose the password and /etc/krb5kdc/stash,
you cannot decrypt your Kerberos database.
Loading random data
Initializing database '/var/lib/krb5kdc/principal' for realm 'EXAMPLE.COM',
master key name 'K/M@EXAMPLE.COM'
You will be prompted for the database Master Password.
It is important that you NOT FORGET this password.
Enter KDC database master key:
Re-enter KDC database master key to verify:
Now that your realm is set up you may wish to create an administrative
principal using the addprinc subcommand of the kadmin.local program.
Then, this principal can be added to /etc/krb5kdc/kadm5.acl so that
you can use the kadmin program on other computers. Kerberos admin
principals usually belong to a single user and end in /admin. For
example, if jruser is a Kerberos administrator, then in addition to
the normal jruser principal, a jruser/admin principal should be
created.
Don't forget to set up DNS information so your clients can find your
KDC and admin servers. Doing so is documented in the administration
guide.
在设置完成 realm 后就可以使用kadmin.local
管理 principal 和其他东西了。
- 对于管理员创建一个以 /admin 结尾的 principal,用于管理员操作。
- 对于 nfs,在 gssd 文档中有写,会默认寻找以下几个特殊名称的 principal,我们为 nfs 创建一个 principal。
为了简单,在创建 nfs principal 的时候使用了 /damin 后缀,对于 /admin 后缀的 principal 是拥有更高的权限的,在生产环境中需要特别考虑。
$ sudo kadmin.local
Authenticating as principal root/admin@FATALC.CN with password.
kadmin.local:
kadmin.local: addprinc root/admin
WARNING: no policy specified for root/admin@FATALC.CN; defaulting to no policy
Principal "root/admin@EXAMPLE.COM" created.
kadmin.local: addprinc nfs/admin
为 nfs 增加 keytab,nfs 会在 keytab 中寻找 principal ticket。
$ sudo kadmin.local
Authenticating as principal root/admin@EXAMPLE.COM with password.
kadmin.local:
kadmin.local: ktadd nfs/admin
Entry for principal nfs/admin with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.
Entry for principal nfs/admin with kvno 2, encryption type aes128-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.
增加 nfs export,编辑文件 /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
# to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
/home/user/workspace gss/krb5(rw,sync,fsid=0,sec=krb5:krb5i:krb5p,crossmnt,no_subtree_check)
更改完成后使用 $ sudo exportfs -ar
执行更新,或者直接重启 nfs 服务也可 $ sudo service nfs-server restart
。
v4 only
查看 nfs-config 服务,其读取了 /etc/default/nfs-kerbel-server
文件中的配置,使用仅 nfs v4 的服务配置可以通过该文件进行配置。