NFS 即网络文件系统(Network File System),是一种分布式文件系统协议,该协议允许客户端主机可以像访问本地文件系统一样通过网络访问服务器端文件,即可以将远程服务器文件直接 mount(挂载)到本地的文件目录结构中进行访问。
一、软件安装
服务器端需要安装 nfs-kernel-server 软件包:
$ sudo apt-get update
$ sudo apt-get install nfs-kernel-server
二、服务器配置
默认情况下,NFS 服务器上定义了某个共享目录,则该目录及其子目录下的所有文件都可被访问。
出于对安全的考虑,客户端任何需要超级用户(即 root 用户,UID=0 & GID=0)权限的文件操作都默认映射到 UID=65534 和 GID=65534 的用户,即 Ubuntu 系统中的 nobody:nogroup。
例如客户端使用 root 权限在挂载的共享目录中创建文件时,该文件的属主和属组自动变为 nobody:nogroup,而非 root:root 。
1. 在服务器端创建共享目录
sudo mkdir -p /var/nfs/gernel
sudo mkdir -p /var/nfs/public
sudo chown nobody:nogroup /var/nfs/gernel
2. 修改 exports 文件
为了使 NFS 服务器定义的共享文件可被指定的客户端主机访问,需要在服务器端的 /etc/exports
文件中添加对应的记录。
该文件的格式如下:
Directory Host(Options ...) Host(Options) #comment
关于 /etc/exports
文件的详细语法格式可参考 man exports
。
文件示例:
/var/nfs/gernel 192.168.56.0/24(rw,insecure,sync,no_subtree_check)
/var/nfs/public *(ro,insecure,sync,no_subtree_check)
/home/starky 192.168.56.1(rw,insecure,no_root_squash,sync,no_subtree_check)
第一条纪录表示 192.168.56.0/24 子网中的所有主机都可挂载
var/nfs/gernel
目录并拥有读写(rw)权限第二条纪录表示所有主机都可挂载
/var/nfs/public
目录且拥有只读(ro)权限第三条纪录表示客户端 IP 地址为 192.168.56.1 的主机可以挂载
/home/starky
目录并拥有读写权限,而且任何 root 权限(UID=0 , GID=0)的文件操作都不默认映射给 nobody:nogroup,而保持属主(组)仍为 root(no_root_squash)insecure 选项:允许通过任意端口的远程访问
sync 选项:强制 NFS 服务器在响应请求之前将文件的改动写入磁盘(强调客户端和服务端文件内容的一致性,但会降低文件操作的效率)。
no_subtree_check 选项:禁用 subtree_check 。subtree_check 用来设置服务器在收到请求时,检查该文件是否在指定目录结构中依旧可用(该选项会在某些情况下发生错误:重命名某文件的同时,该文件在客户端打开)。
三、客户端挂载共享目录
列出 nfs 服务器上的共享目录
$ showmount -e 192.168.56.102
Exports list on 192.168.56.102:
/home/starky 192.168.56.1
/var/nfs/public *
/var/nfs/gernel 192.168.56.0/24
创建挂载点
sudo mkdir -p /mnt/nfs/gernel
sudo mkdir -p /mnt/nfs/public
sudo mkdir -p /mnt/nfs/starky
挂载远程目录
sudo mount 192.168.56.102:/var/nfs/gernel /mnt/nfs/gernel
sudo mount 192.168.56.102:/var/nfs/public /mnt/nfs/public
sudo mount 192.168.56.102:/home/starky /mnt/nfs/starky
权限测试
如截图所示:NFS 的权限设定基于 Linux 文件系统的权限管理,即客户端挂载远程共享目录后,会把它们当成本地磁盘目录一样对待,也是根据文件的属主(组)及其对应的权限设定来限制访问。
gernel 目录的属主(组)为 nobody:nogroup(65534:65534),所以虽然该目录为读写权限,非 root 用户无法执行新建操作。而 root 用户由于 NFS 默认的安全机制,会自动映射到 nobody:nogroup。
由于我在客户端和服务端都有一个名为 starky 的用户,且它们的 UID:GID 都为1000:1000,所以服务端的 /home/starky
目录可以直接被客户端的 starky 用户访问。且由于 no_root_squash 选项,通过 sudo 命令创建的文件其属主仍为 root(而不会再映射为 nobody)。
当然这会导致一些安全问题,比如多个客户端同时都有 UID(GID)为1000的用户(不管用户名是什么),则这些用户会共享服务端 /home/starky
目录里的文件权限。
四、系统启动时自动挂载共享目录
可编辑 /etc/fstab
文件令挂载共享目录的 mount 操作成为系统的固定配置(手动输入的 mount 命令属于临时挂载,重启会自动卸载),使得系统重启后可以自动挂载远程文件系统。/etc/fstab
文件的示例内容如下:
# filesystem mountpoint fstype flags dump fsck
192.168.56.102:/var/nfs/gernel /mnt/nfs/gernel nfs rw,bg,intr,hard,nodev,nosuid 0 0
192.168.56.102:/var/nfs/public /mnt/nfs/public nfs4 ro,bg,intr,soft,nodev,nosuid 0 0
192.168.56.102:/home/starky /mnt/nfs/starky nfs rw,bg,intr,hard,nodev,nosuid 0 0
附录:
1. /etc/exports 文件中的 Host 格式
/etc/exports
文件的格式为:Directory Host(Options ...) Host(Options) #comment
其中的 Host 项用来指定可访问对应共享目录的主机,其格式可分为以下几种:
- 单个主机
Host 项可以为一个或多个单独的 TCP/IP 主机名或 IP 地址
admin
admin.starky.net
192.168.56.101
- IP 子网
10.0.0.0/255.0.0.0
172.16.0.0/255.255.0.0
192.168.56.0/24
- TCP/IP 域
通过使用通配符,可以指定某个特定域中的全部或部分主机
*.starky.net
*craft.starky.net
???.starky.net
- NIS 组
可以指定某个 NIS 组中所有主机的访问权限,使用@group
2. /etc/exports 文件中的 Options
选项 | 描述 |
---|---|
ro | 只读权限 |
rw | 读写权限(默认) |
rw=list | 通过 list 指定具有写权限的客户端主机,其他主机则为只读权限 |
root_squash | 将 UID 0 和 GID 0 映射到 anonuid 和 anongid(即 Ubuntu 系统中的 nobody 和 nogroup) |
no_root_squash | 允许需要 root 权限的文件操作,有安全风险 |
all_squash | 将所有的 UID 和 GID 映射到它们的匿名形式,主要针对不信任的主机 |
anonuid=xxx | 指定客户端 root 权限的操作需要映射到的 UID(默认是65534) |
anongid=xxx | 指定客户端 root 权限的操作需要映射到的 GID(默认是65534) |
insecure | 允许通过任意端口的远程访问 |
async | 服务器可以在写入硬盘之前响应客户端的写入请求 |
wdelay | 通过延迟同步多个客户端对文件的更新 |
sec=flavor | 指定共享目录的安全验证方法,包括 sys (UNIX 验证),dh (DES),krb5i ,krb5p 和 none (匿名访问) |
3. NFS 挂载选项
选项 | 描述 |
---|---|
rw | 以读写模式挂载文件系统(rw 也需在服务端定义) |
ro | 以只读模式挂载文件系统 |
bg | 如挂载失败(服务器无响应),在后台继续尝试并执行其他挂载请求 |
hard | 如果服务器无响应,重复发送请求直到服务器回复 |
soft | 如果服务器无响应,重复发送请求,超过一定时间后返回错误,而不会一直阻塞 |
intr | 允许用户中断阻塞的文件操作(并返回错误) |
nointr | 不允许用户中断客户端的文件操作请求 |
retrans=n | 在 soft 模式下,指定返回错误前重复发送请求的次数 |
timeo=n | 设置超时后重复发送请求的时间间隔(单位 1/10 秒) |
rsize=n | 设置读取 buffer 大小为 n bytes |
wsize=n | 设置写入 buffer 大小为 n bytes |
sec=flavor | 设置安全验证方法 |
proto=proto | 设置传输协议,NFSv4 必须为 TCP |
4. NFS 协议讨论
传输协议
最初的 NFSv2 由于性能原因使用 UDP 协议,虽然 NFS 添加了自己的包序列重组和错误检查功能,但 UDP 和 NFS 都不具备阻塞控制算法,所以在大型的互联网络环境中缺乏足够的性能。
NFSv3 提供了 UDP 和 TCP 协议之间的选择。NFSv4 只能使用 TCP 协议。
随着 CPU,内存等硬件设备和网络传输速度的提高,最初由于性能需求而倾向 UDP 协议的选择也变得不再必要。
State
NFSv2 和 NFSv3 是无状态的连接,服务端不会跟踪客户端对共享目录的挂载情况,而是使用 "cookie" 来记录一次成功的挂载。"cookie" 不会因为服务器重启而删除,可以用来在服务器挂掉之后保留客户端的连接信息。
NFSv4 是有状态的连接,客户端和服务端都会维护文件操作纪录及文件锁的状态。所以不再需要 "cookie" 的使用。
文件锁
早期版本的 NFS 协议(v2 & v3)由于是无状态的连接,它们并不清楚哪些主机正在使用哪些文件。但是文件锁的实现又需要获取状态信息。所以早期协议中的文件锁是独立于 NFS 实现的。
而 NFSv4 将文件锁的实现整合到了核心协议中,虽然此举增加了复杂度,但同时也解决了早期版本中的很多问题。
但是为了兼容使用 V2 和 V3 协议的客户端,独立的 locked 和 statd 守护进程仍旧需要。
安全相关
NFS 协议最初在设计时并不关注安全性,NFSv4 通过引入对更强大的安全服务和身份验证的支持,加强了该协议的安全性。
传统的 NFS 协议大多使用 AUTH_SYS 验证方式,基于 UNIX 的用户和组标识。在这种方式下,客户端只需要发送自己的 UID 和 GID 并与服务器上的 /etc/passwd
文件内容作对比,以决定其拥有怎样的权限。
所以当多个客户端存在 UID 相同的用户时,这些用户会拥有相同的文件权限。更进一步,拥有 root 权限的用户可以通过 su 命令切换到任意 UID 登录,服务器会因此给予其对应 UID 的权限。
为了防止上面的问题出现,服务器可选择使用更健壮的验证机制比如 Kerberos 结合 NFS PRCSEC_GSS。
NFS 共享目录的访问控制基于 /etc/exports
文件中定义的主机名或 IP 地址。但是客户端很容易针对其身份和 IP 地址造假,这也会导致一些安全问题。
NFSv4 只使用 TCP 作为自己的传输协议,而且通常只开放 2049 端口进行数据传输。在配置防火墙时,除了放开 2049 端口的限制外,还要时刻注意数据传输的源地址和目标地址。
5. Windows 系统挂载共享目录
win10 系统默认不能挂载 NFS 共享目录,需要进入控制面板 - 程序 - 程序和功能 - 启用或关闭 Windows 功能,勾选上 NFS 服务。
之后就可以使用 mount 命令挂载共享目录了。
只是 Windows 系统并不使用 Linux 那样的用户管理,导致挂载的共享目录只能读取而没有写入的权限。
解决办法是在 注册表中新建两个 DWORD 值,用作 匿名用户的 UID 和 GID。
默认参数下的挂载选项,UID 和 GID 都为 -2:
可进入 注册表编辑器( regedit),定位到 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Default,新建两个名为 AnonymousUid 和 AnonymousGid 的 DWORD(32位)值,改成自己需要用到的数字(我都改成了 0 ,即对应 Linux 系统中的 root 用户。如需要改为 0 以外的数字,注意先转换成 16 位)。
此时的挂载选项变为:
如更改未生效,可重启电脑。
参考资料
UNIX and Linux System Administration Handbook, 4th Edition
How to Mount an NFS Share Using a Windows 10 Machine