多读书多实践,勤思考善领悟

实现NFS服务用户身份映射

本文于2003天之前发表,文中内容可能已经过时。

NFS(Network File System,网络文件系统)是一种在企业内部网络使用比较广泛的文件共享服务,主要用于Linux以及类UNIX系统之间的文件共享。它采用C/S工作模式,在NFS服务器上将某个目录设置为共享目录,然后在客户端可以将这个目录挂载到本地使用。NFS服务诞生于上世纪80年代,虽然在CentOS7.6系统中采用的是目前最新的NFSv4版本,但由于NFS服务本身比较简单,尤其是在权限设置方面功能比较弱,所以如果对NFS服务设置不当,将会在企业网络中产生比较严重的安全隐患。本文就NFS服务的用户身份映射问题进行了分析,并给出了推荐的配置和使用方法。文中准备了两台Linux虚拟机来搭建实验环境,虚拟机所使用的操作系统版本为CentOS7.6。其中名为Server的虚拟机IP地址是192.168.80.10,名为Client的虚拟机IP地址是192.168.80.101。

1. NFS的基本配置

NFS服务在CentOS7系统中默认已经安装,但并未运行,因而首先需要在虚拟机Server中执行“systemctl start nfs”命令启动服务,然后再执行“systemctl enable nfs”命令将服务设置为开机自动运行。
在服务器端新建一个/var/share目录,并在其中创建一个测试文件test.txt。

1
2
[root@server ~]# mkdir /var/share
[root@server ~]# echo 'hello,world!' > /var/share/test.txt

下面将/var/share目录设置为NFS共享,并允许所有客户端访问。
NFS服务的主配置文件是/etc/exports,在/etc/exports文件中,每一行定义一个共享目录。利用vi编辑器打开配置文件/etc/exports,在其中增加下面的一行:

1
2
[root@server ~]# vim /etc/exports
/var/share *(ro,sync)

在设置项中,“/var/share”表示要共享的目录,“”表示所有客户端都可以访问该共享目录,选项“ro”用于定义客户端的权限为read-only(只读),选项“sync”表示启用同步模式,可以将内存中的数据实时写入到磁盘中。
修改完配置文件之后,重启NFS服务生效。
[root@server ~]# systemctl restart nfs
然后在客户端就可以将共享目录挂载到本地使用。

1
2
3
4
[root@client ~]# mkdir /mnt/nfs                                 #创建挂载点目录
[root@client ~]# mount -t nfs 192.168.80.10:/common /mnt/nfs #挂载共享目录
[root@client ~]# ls /mnt/nfs #查看共享目录中的文件
test.txt

由于NFS服务本身并不具备用户身份验证的功能,而仅支持基于客户端IP进行认证。也就是说,我们在对NFS服务进行权限设置时,不能针对用户来分配权限,而只能针对客户端IP进行权限分配。所以如果希望IP地址为192.168.80.101的客户端可以对共享目录执行写入操作,那么可以在服务器端对配置文件进行如下修改,添加客户端的IP,并设置权限选项为“rw(read-write)”:

1
2
[root@server ~]# vim /etc/exports
/var/share *(ro,sync) 192.168.80.101(rw,sync)

修改完成后,需要重启NFS服务生效。但是如果NFS共享正在被某些服务器使用的话,那么NFS服务是不允许随便重启的,所以在CentOS系统中提供了exportfs命令,可以在不重启NFS服务的情况下,重新加载/etc/exports文件,使得新的设置项生效。exportfs命令的常用选项有:-a(全部挂载或全部卸载)、-r(重新挂载)、-v(显示详细信息),通常都是将这三个选项组合使用。下面通过exportfs命令使得我们刚才所做的设置生效。

1
2
3
[root@server ~]# exportfs -arv
exporting 192.168.80.101:/var/share
exporting *:/var/share

在客户端先将共享目录卸载,然后再重新挂载,以使得服务器端的设置生效。但在对共享目录进行写入测试时失败。

1
2
3
4
[root@Client ~]# umount /mnt/share                          #卸载共享目录
[root@Client ~]# mount 192.168.80.10:/var/share /mnt/share #重新挂载共享目录
[root@Client ~]# touch /mnt/share/a.txt #写入测试失败
touch: 无法创建"/mnt/share/a.txt": 只读文件系统

这是由于虽然在服务器端的配置文件/etc/exports中设置了允许用户对/var/share目录具有读写权限,但在操作系统层面,用户对/var/share目录却不具备写入权限。因而要实现对共享目录的写入操作,必须要保证在NFS服务和操作系统两个层面全部都具有写入权限才可以。
如何使得客户端可以在操作系统层面对共享目录具有写入权限,这就要涉及到用户身份映射问题。

2. 用户身份映射的原理

NFS服务虽然不具备用户身份验证的功能,但是NFS提供了一种身份映射的机制来对用户身份进行管理。当客户端访问NFS服务时,服务器会根据情况将客户端用户的身份映射成NFS匿名用户nfsnobody。nfsnobody是由NFS服务在系统中自动创建的一个程序用户账号,该账号不能用于登录系统,专门用作NFS服务的匿名用户账号。

1
2
 [root@Server ~]# grep nfsnobody /etc/passwd        #查看nfsnobody用户的信息。
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin

所谓用户身份映射,是指当客户端访问NFS服务器时,会自动被视作服务器中的nfsnobody用户,并按照该用户的权限设置去执行操作。但是并非所有的客户端都会被映射为nfsnobody用户,在/etc/exports配置文件中提供了以下选项,以决定是否将NFS客户端映射为nfsnobody用户:

  • root_squash,当NFS客户端以root用户身份访问时,映射为NFS服务器的nfsnobody用户。

  • no_root_squash,当NFS客户端以root身份访问时,映射为NFS服务器的root用户,也就是要为超级用户保留权限。这个选项会留下严重的安全隐患,一般不建议采用。

  • all_squash,无论NFS客户端以哪种用户身份访问,均映射为NFS服务器的nfsnobody用户。

    其中默认值是root_squash,即当客户端以root用户的身份访问NFS共享时,在服务器端会自动被映射为匿名账号nfsnobody。

    下面将分几种情况分别予以说明。

    3. root用户的身份映射

    我们之前之所以无法在客户端执行写入操作,是因为还没有在系统层面赋予nfsnobody用户对共享目录/var/share具有写入权限。这里通过设置ACL规则赋予nfsnobody用户rwx权限。
    [root@Server ~]# setfacl -m u:nfsnobody:rwx /var/share`
    然后在客户端重新挂载共享目录,并测试能否写入。

    1
    2
    3
    [root@Client ~]# umount /mnt/share
    [root@Client ~]# mount -t nfs 192.168.80.10:/var/share /mnt/share
    [root@Client ~]# touch /mnt/share/b.txt

    可以看到此时客户端可以写入,并且所创建文件的所有者正是nfsnobody。

    1
    2
    [root@Client ~]# ll /mnt/share/b.txt
    -rw-r--r--. 1 nfsnobody nfsnobody 0 3月 21 07:39 /mnt/share/b.txt

    由于客户端当前所使用的用户身份是root,默认情况下,当客户端访问NFS服务器时,在服务器端会将其用户身份映射为nfsnobody,所以在服务器端只要赋予nfsnobody用户对共享目录具有写入权限,那么客户端自然就可以写入了。
    下面我们再验证一下no_root_squash设置项,即root用户不进行身份映射。
    首先在服务器端修改配置文件/etc/exports,为/var/share共享目录添加no_root_squash选项。

    1
    2
    3
    4
    [root@server ~]# vim /etc/exports           #修改配置文件
    /var/share *(rw,no_root_squash,sync)
    [root@Server ~]# exportfs -arv #重新加载服务
    exporting *:/var/share

    然后去掉对共享目录/var/share所设置的ACL规则,取消nfsnobody用户对该目录的写入权限。
    [root@Server ~]# setfacl -b /var/share
    最后在客户端重新挂载共享目录,并测试能否写入。

    1
    2
    3
    4
    5
    [root@Client ~]# umount /mnt/share
    [root@Client ~]# mount -t nfs 192.168.80.10:/var/share /mnt/share
    [root@Client ~]# touch /mnt/share/c.txt
    [root@Client ~]# ll /mnt/share/c.txt
    -rw-r--r--. 1 root root 0 414 17:08 /mnt/share/c.txt

    可以发现,此时客户端仍然是可以写入的。因为对于NFS服务器而言,访问共享目录的客户端就是服务器中的root用户,对共享目录具有完全权限。所以no_root_squash选项会产生很大的安全隐患,一般情况下都不建议采用。

    4. 普通用户的身份映射

    如果客户端所使用的用户身份不是root,而是一个普通用户,那么默认情况下在服务器端会将其视作其它用户(other)。下面在客户端以普通用户的身份继续进行测试。
    首先在服务器端修改配置文件/etc/exports,将共享目录/var/share中的no_root_squash选项去掉,重新加载服务之后,再次通过设置ACL规则的方式赋予nfsnobody用户读写执行权限。

    1
    2
    3
    4
    5
    6
    [root@server ~]# vim /etc/exports       #修改配置文件
    /var/share *(rw,sync)
    [root@Server ~]# exportfs -arv #重新加载服务
    exporting *:/var/share
    #通过设置ACL赋予nfsnobody用户rwx权限
    [root@Server ~]# setfacl -m u:nfsnobody:rwx /var/share

    在客户端重新挂载共享,并测试以root用户身份可以正常写入。

    1
    2
    3
    4
    5
    [root@Client ~]# umount /mnt/share
    [root@Client ~]# mount 192.168.80.10:/var/share /mnt/share
    [root@Client ~]# touch /mnt/share/d.txt
    [root@Client ~]# ll /mnt/share/d.txt
    -rw-r--r--. 1 nfsnobody nfsnobody 0 414 17:29 /mnt/share/d.txt

    下面在客户端创建一个admin用户,并设置密码。

    1
    2
    [root@Client ~]# useradd admin
    [root@Client ~]# echo 123 | passwd --stdin admin

    切换到admin用户身份,尝试向共享目录中写入文件,写入失败,但是可以读取目录中的内容。因而如果客户端是以普通用户的身份访问NFS共享,那么默认情况下在服务器端并不将其映射为nfsnobody,而是视作其他用户(other)。

    1
    2
    3
    4
    [root@Client ~]# su - admin
    [admin@Client ~]$ touch /mnt/share/e.txt
    touch: 无法创建"/mnt/share/e.txt": 权限不够
    [admin@Client ~]$ cat /mnt/share/e.txt

    下面在服务器端继续修改配置文件/etc/exports,在共享设置中添加“all_squash”选项,将所有客户端用户均映射为nfsnobody。

    1
    2
    3
    4
    [root@server ~]# vim /etc/exports           #修改配置文件
    /var/share *(rw,sync,all_squash)
    [root@Server ~]# exportfs -arv #重新加载服务
    exporting *:/var/share

    然后在客户端再次重新挂载共享(具体操作从略),此时以admin用户身份就可以写入了,并且可以发现所创建文件的所有者同样是nfsnobody。

    1
    2
    3
    [admin@Client ~]$ touch /mnt/share/e.txt
    [admin@Client ~]$ ll /mnt/share/e.txt
    -rw-rw-r--. 1 nfsnobody nfsnobody 0 414 17:37 /mnt/share/e.txt

    5. 用户身份重叠

    在使用NFS共享的过程中,有时还可能会遇到用户身份重叠的问题。所谓用户身份重叠,是指在NFS服务采用默认设置(用户身份映射选项为root_squash)时,如果在服务器端赋予某个用户对共享目录具有相应权限,而且在客户端恰好也有一个具有相同uid的用户,那么当在客户端以该用户身份访问共享时,将自动具有服务器端对应用户的权限。下面举例予以说明。
    首先在服务器端将/var/share共享还原为默认设置,并且取消/var/share目录针对nfsnobody用户的ACL规则,具体操作从略。
    假设服务器端存在一个名为teacher的用户账号,uid为1246,将该用户设置为共享目录的所有者。

    1
    2
    3
    4
    5
    6
    [root@Server ~]# id teacher                 #查看teacher用户的身份信息`
    uid=1246(teacher) gid=1246(teacher) 组=1246(teacher
    #将teacher用户设置为/var/share目录的所有者
    [root@Server ~]# chown teacher /var/share
    [root@Server ~]# ll -d /var/share
    drwxr-xr-x. 2 teacher root 45 414 17:37 /var/share

    下面在客户端进行操作。首先仍是重新挂载共享目录,然后将原先admin用户的uid也改为1246。

    1
    2
    3
    [root@Client ~]# usermod -u 1246 admin
    [root@Client ~]# id admin
    uid=1246(admin) gid=1002(admin) 组=1002(admin)

    然后以admin用户身份测试能否对共享目录执行写入操作,发现可以正常写入,并且所创建文件的所有者是admin。

    1
    2
    3
    4
    [root@Client ~]# su - admin
    [admin@Client ~]$ touch /mnt/share/f.txt
    [admin@Client ~]$ ll /mnt/share/f.txt
    -rw-rw-r--. 1 admin admin 0 414 17:47 /mnt/share/f.txt

    在服务器端查看admin用户所创建的文件,发现所有者则是teacher。

    1
    2
    [root@Server ~]# ll /var/share/f.txt 
    -rw-rw-r--. 1 teacher 1002 0 4月 14 17:47 /var/share/f.txt

    这是因为对于Linux系统而言,区分不同用户的唯一标识就是uid,至于用户名只是为了方便人类理解。所以在系统层面,无论是teacher用户还是admin用户,只要他们的uid一样,就认为是同一个用户。但也正是因为这个原因,才会导致出现用户身份重叠的问题,对于NFS服务而言,这也是一个比较严重的安全隐患。
    如何避免用户身份重叠呢?可以从以下两个方面着手:

  • 一是在设置NFS共享时,建议采用“all_squash”选项,将所有客户端用户均映射为nfsnobody。这样就可以有效避免用户身份重叠的问题。

  • 二是应严格控制NFS共享目录的系统权限,尽量不用为普通用户赋予权限。