Linux 文件权限、用户与组

😊

1 文件

1.1 文件类型

文件类型在 /usr/include/bits/stat.h 中进行定义。

该文件中共定义了 7 种类型的文件:目录文件、字符设备文件、块设备文件、常规文件、管道文件、符号连接文件、套接字文件。

1
2
3
4
5
6
7
8
9
10
#define __S_IFMT        0170000 /* These bits determine file type.  */

/* File types. */
#define __S_IFDIR 0040000 /* Directory. */
#define __S_IFCHR 0020000 /* Character device. */
#define __S_IFBLK 0060000 /* Block device. */
#define __S_IFREG 0100000 /* Regular file. */
#define __S_IFIFO 0010000 /* FIFO. */
#define __S_IFLNK 0120000 /* Symbolic link. */
#define __S_IFSOCK 0140000 /* Socket. */
文件类型 符号 解释
常规文件
(Regular)
- 普通的文本文件、可执行文件、图像、影音文件等。
目录文件
(Directory)
d 即目录,目录也属于文件,用来管理子目录及其文件。
字符设备文件
(Character device)
c Linux 将设备当做文件来处理,操作一个设备就像操作一个文件一样。设备文件一般放在 /dev 目录下。字符设备是每次只读取一个字符,如串口、终端或打印机等。
块设备文件
(Block device)
b 块设备文件:对该类型的文件访问,是以块大小为单位进行访问的。
符号链接文件
(Symbolic link)
l 可以链接其他文件或目录,分为软链接和硬链接。可以链接不同文件系统文件、不存在的文件等。系统会对符号链接的读写转换为对源文件/目录的读写,删除符号连接不会影响源文件。使用 ln 命令创建连接。
套接字文件
(Socket)
s 这种文件类型用于进程间的网络通信。也可以用在同一台主机上的进程通信。
管道文件
(FIFO)
p 用于进程间的通信,有时也叫做命名管道(IPC机制私用)。一个进程像命名管道中写入数据,另一个进程从命名管道中读取数据,数据读写时先入先出 FIFO。

有些材料中,将字符设备文件、块设备文件、套接字文件、管道文件称为特殊文件(Special file)。

文件类型的查看可以使用 ls -l(等价于 ll),或 ls -F

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@centos7 ~]# ls -al /
total 29
d /* 单独看第一列 */ r-xr-xr-x. 17 root root 224 Jul 11 20:13 .
d /* 单独看第一列 */ r-xr-xr-x. 17 root root 224 Jul 11 20:13 ..
l /* 单独看第一列 */ rwxrwxrwx. 1 root root 7 Jun 26 21:16 bin -> usr/bin
d /* 单独看第一列 */ r-xr-xr-x. 5 root root 4096 Jun 26 22:06 boot
d /* 单独看第一列 */ rwxr-xr-x. 21 root root 3280 Jul 14 00:24 dev
d /* 单独看第一列 */ rwxr-xr-x. 144 root root 8192 Jul 14 21:15 etc
d /* 单独看第一列 */ rwxr-xr-x. 4 root root 60 Jul 14 21:15 home
l /* 单独看第一列 */ rwxrwxrwx. 1 root root 7 Jun 26 21:16 lib -> usr/lib
l /* 单独看第一列 */ rwxrwxrwx. 1 root root 9 Jun 26 21:16 lib64 -> usr/lib64
l /* 单独看第一列 */ rwxrwxrwx. 1 root root 8 Jun 26 21:16 sbin -> usr/sbin
d /* 单独看第一列 */ rwxr-xr-x. 2 root root 6 Apr 11 2018 usr
- /* 单独看第一列 */ rw-r--r--. 1 root root 163 Jun 26 21:16 .updated

如上表,第一列表示文件类型,常见的 - 表示常规文件、d 表示目录、l 表示链接等。说明:

  • .:表示当前目录。
  • ..:表示上一级目录,即父目录。
  • .xxx:文件名之前有个点 . 的文件是隐藏文件,使用 -a 参数才能看到。

如果不想一列一列的显示看文件类型的话,可以使用 ls -F 在每一项后面添加一个后缀来进行显示:

1
2
3
[root@centos7 ~]# ls -aF /
bin@ dev/ home/ lib64@ mnt/ proc/ run/ srv/ tmp/ var/ ./ ../
boot/ etc/ lib@ media/ opt/ root/ sbin@ sys/ usr/ .updated

后缀表示文件类型如下(常规文件无后缀):

19.png

参考:

1.2 文件权限

Linux 一般将文件可存取的身份分为三个类别,分别是 owner/group/others,且三种身份有各自的 read/write/execute 权限。Linux 下的用户是按照 来进行组织的。

Linux 是多用户多任务的操作系统,不同的用户对同一文件的访问权限可能是不同的。本章节先简单介绍 owner/group/others,详细内容将在第 3 章节介绍。

  • owner:文件的拥有者。
  • group:组,每个用户都处在某个组中。如在一个开发项目中,这个组中的所有用户对开发项目都有一样的权限。但是组内的成员对自己的文件都可以设置不同的访问权限。注意:一个组内可以有对个用户,一个用户也可以属于多个分组。
  • others:这是相对 owner 来说的,不和当前用户在同一个 group 的用户,都可以称之为 others

Linux 系统中用户信息存储在 /etc/passwd 中,对应的账号密码保存在 /etc/shadow 中,Linux 所有的组名都记录在 /etc/group 内。shadow 文件只有 root 有权限访问。

下面开始介绍文件权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@centos7 ~]# ls -al
total 72
dr-xr-x--- . 17 root root 4096 Jul 14 21:22 .
dr-xr-xr-x . 17 root root 224 Jul 11 20:13 ..
-rw------- . 1 root root 1586 Jun 26 21:32 anaconda-ks.cfg
-rw------- . 1 root root 13845 Jul 16 16:08 .bash_history
-rw-r--r-- . 1 root root 18 Dec 29 2013 .bash_logout
-rw-r--r-- . 1 root root 176 Dec 29 2013 .bash_profile
-rw-r--r-- . 1 root root 176 Dec 29 2013 .bashrc
drwx------ . 15 root root 4096 Jul 8 14:03 .cache
drwxr-xr-x . 16 root root 4096 Jul 15 00:40 .config
-rw-r--r-- . 1 root root 100 Dec 29 2013 .cshrc
drwxr-xr-x . 2 root root 35 Jul 14 21:15 Desktop
drwxr-xr-x . 2 root root 6 Jul 14 21:11 Documents
-rw------- . 1 root root 16 Jun 26 22:12 .esd_auth
-rw------- . 1 root root 1554 Jul 14 21:22 .ICEauthority
-rw-r--r-- . 1 root root 1634 Jun 26 21:34 initial-setup-ks.cfg

[ 权限 ][启用SELinux][硬链接个数][owner] [group] [文件大小 byte] [文件上次修改日期][文件名称]
[ 1 ][ 2 ][ 3 ][ 4 ] [ 5 ] [ 6 ] [ 7 ][ 8 ]

可以将 ls -al 列出来的文件内容分成 8 个组进行介绍:

  1. 文件权限 [ 1 ]

    第一组共 11 个字符(最后一个 . 在 OS 2.6 版本开始添加)。

    22.png

    文件类型在上一小节已经介绍,需要说明的是 - 表示无权限。

  2. 启用 SELinux [ 2 ],可参考SELinux 入门,后续章节介绍。

  3. 硬链接的个数 [ 3 ]:表示链接该目录/文件的硬链接的个数。

  4. 拥有者身份、拥有者所属的组 [ 4 , 5 ]

  5. 文件/目录大小 [ 6 ],单位是 byte

  6. 文件创建或最近一次修改日期 [ 7 ],要想看创建的详细时间可以使用 ls --full-time 查看。

  7. 目录/文件名称 [ 8 ]:需要说明的是 . 表示当前目录,.. 表示父目录。

关于文件权限:

  1. 特殊文件(字符设备文件、块设备文件、套接字文件、管道文件)没有可执行权限 x,只有读写权限。
  2. root 不受文件权限约束。以 . 开头的文件会默认隐藏
  3. 对文件的写权限,并不代表可以删除文件。本章节说的读写执行仅是针对文件的内容而言。

关于目录权限:

  • 读权限 r:表示可以罗列该目录下的子目录、文件。即可以使用 ls 命令等。
  • 写权限 w
    • 创建子目录、文件。
    • 删除文件、子目录(不管文件权限如何)。
    • 更名已存在的文件、子目录。
    • 移动文件、子目录的位置。
  • 执行权限 x表示是否可进入到目录,即是否可使用 cd 命令,这是非常值得注意的点。
  • 目录的读权限 r 表示可以有权查看目录名称,执行权限 x 表示可以 cd 到目录里面去,并且可以查看目录相关权限。
  • 目录中的子目录、文件的权限受到目录的约束

上面关于目录权限最后一条约束,举例说明:

如果某个目录权限对其他用户不可读写、执行 ---,如下:

1
drwxr-x---.  4 root  root   115 Jul 11 20:13 program

该目录下 test.c 文件属于 alvin 用户,并具有可读写执行 rwx 权限:

1
2
[root@centos7 ~]# ls -l /home/program/
-rwxr--r--. 1 alvin root 140 Jul 8 14:39 test.c

但是 alvin 用户无权使用 ls/cat 等命令查看该文件,除非目录放开权限

1
2
3
4
[root@centos7 ~]# su alvin

[alvin@centos7 root]$ cat /home/program/test.c
cat: /home/program/test.c: Permission denied

如果此时 program 开放对 others 的可执行权限,然后再用 alvin 查看该文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@centos7 ~]# ls -l /home/
drwxr-x---. 4 root root 134 Jul 17 21:09 program

// 对 program 开放可执行权限
[root@centos7 ~]# chmod o+x /home/program/

[root@centos7 ~]# ls -l /home/
drwxr-x--x. 4 root root 134 Jul 17 21:09 program

// alvin 用户查看该文件内容
[root@centos7 ~]# su alvin
[alvin@centos7 root]$ cat /home/program/test.c
#include <stdio.h>

int main(int argc, char** argv[])
{
char* string = 0;

string = "Hello World";
printf("%s\n", string);

return 0;
}

// 因为没有开放可读权限,所以 alvin 仍然不可以查看该目录下的内容
[alvin@centos7 root]$ ls -l /home/program/
ls: cannot open directory /home/program/: Permission denied

除了 UNIX 权限外,Linux 还支持访问控制表(ACL)。ACL 支持更详细更精确的 权限和安全控制方式,其代价是复杂度变大以及更大的磁盘存储开销。

1.3 文件权限修改

更改权限的三个常用命令:

  • chgrp(change group):改变文件所属群组。
  • chown(change owner):改变文件拥有者、所属组。
  • chmod(change mode):改变文件的权限,SUID,SGID, SBIT 等等的特性。
  1. chgrp 语法。

    1
    chgrp [-R] groupname dirname/filename	# -R,表示递归地将目录下所有的子目录和文件都改变组
  2. chown 语法。

    1
    chown [-R] 账号名称[:组名] dirname/filename

    举例说明:

    如下,可以看到 program 属于 root 用户,所在组名称也为 root,但是没有可执行权限

    1
    2
    [root@centos7 ~]# ls -l /home/
    drw-r-x--x. 4 root root 134 Jul 17 21:09 program

    将目录所属用户改为 alvin 用户。

    1
    2
    3
    4
    [root@centos7 ~]# chown alvin /home/program/

    [root@centos7 ~]# ls -l /home/
    drw-r-x--x. 4 alvin root 134 Jul 17 21:09 program

    切换到 alvin 用户,并使用 ls 查看该目录下的文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [alvin@centos7 root]$ ls -l /home/program/
    ls: cannot access /home/program/test.c: Permission denied
    ls: cannot access /home/program/test.i: Permission denied
    ls: cannot access /home/program/test.s: Permission denied
    ls: cannot access /home/program/test.o: Permission denied
    ls: cannot access /home/program/test.exe: Permission denied
    ls: cannot access /home/program/testdiretory: Permission denied
    total 0
    -????????? ? ? ? ? ? test.c
    d????????? ? ? ? ? ? testdiretory
    -????????? ? ? ? ? ? test.exe
    -????????? ? ? ? ? ? test.i
    -????????? ? ? ? ? ? test.o
    -????????? ? ? ? ? ? test.s

    可以看到,虽然 alvin 具有可读权限能顺利罗列出该目录下的文件/子目录名称。但是不具有可执行权限,不能查看文件相关的属性,全是一堆问号 ?

  3. chmod 语法。

    1
    chmod -R xyz filename/dirname    // x:用户对象,y:权限操作,z:权限类型

    文件权限的设定有两种方法:数字或符号。

    • 数字权限。文件在 owner/group/others 下分别有 r/w/x 权限,对应的分数为:

      r = 4
      w = 2
      x = 1
      [-rwxrw-r-x] 对应的数字权限为 765

    • 符号权限。

      命令 用户对象 权限操作 权限类型 文件/目录名
      chmod u:user,即 owner。
      g:即 group。
      o:即 others。
      a:所有人。
      +:加入权限。
      -:除去权限。
      =:设定权限。
      r/w/x filename/dirname

      数字权限举例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 现在权限是 744
      [root@centos7 ~]# ls -l /home/program/
      -rwxr--r--. 1 alvin root 140 Jul 17 21:59 test.c

      // 将权限修改为 777
      [root@centos7 ~]# chmod 777 /home/program/test.c

      [root@centos7 ~]# ls -l /home/program/
      -rwxrwxrwx. 1 alvin root 140 Jul 17 21:59 test.c

      字符权限举例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // 去除 owner 的可执行权限、group 用户的写权限、others 的读权限
      [root@centos7 ~]# chmod u-x,g-w,o-r /home/program/test.c

      [root@centos7 ~]# ls -l /home/program/
      -rw-r-x-wx. 1 alvin root 140 Jul 17 21:59 test.c

      // 如果要让所有人可读写 test.c 文件
      [root@centos7 ~]# chmod a+r+w /home/program/test.c

      [root@centos7 ~]# ls -l /home/program/
      -rw-rwxrwx. 1 alvin root 140 Jul 17 21:59 test.c

1.4 SELinux

2 文件目录特殊权限

Windows 系统中,新建的文件和目录时通过继承上级目录的权限获得的初始权限,而 Linux 不同,它是通过使用 umask 默认权限来给所有新建的文件和目录赋予初始权限的。

2.1 特殊权限 SUID、SGID

我们查看如下:

1
2
3
4
[root@centos7 ~]# ls -ld /tmp/; ll /usr/bin/locate; ll /usr/bin/passwd 
drwxrwxrwt. 27 root root 4096 Jul 18 22:53 /tmp/
-rwx--s--x. 1 root slocate 40520 Apr 11 2018 /usr/bin/locate
-rwsr-xr-x. 1 root root 27856 Apr 1 2020 /usr/bin/passwd

可以看到文件、目录下面除了有常规的 r/w/x 权限之外,还有 s/t 等权限,称之为特殊权限

特殊权限 描述
Set UID(SUID) s 权限出现在文件 owner 的可执行权限上,此时称该文件具有 Set UID 权限,简称 SUID 权限。SUID 权限有以下约束和功能:
1、SUID 权限仅对二进制文件有效。
2、执行该二进制文件的用户(执行者)必须对该文件有可执行权限。
3、执行者将会在运行该文件的期间,拥有该文件 owner 的权限。

举例说明:
如上 /usr/bin/passwd 文件对 others 有可执行权限,且该文件 owner 的可执行权限位为 s,所以当用户 alvin 运行程序的期间,也就拥有了该文件所属的 root 的权限。所以 alvin 这个时候就可以改密码,更新 /etc/shadow 中相应的信息。
Set GID(SGID) s 权限出现在文件 group 的可执行权限上,此时称该文件具有 Set GID 权限,简称 SGID 权限。
SGID 权限有以下约束和功能:
1、SGID 可作用于二进制程序。
2、执行该二进制文件的用户(执行者)必须对该文件有可执行权限。
3、执行者将会在运行该文件的期间,拥有该文件 group 的权限。

1、SGID 可作用于目录。
2、若访问的用户对该目录有 r,x 权限,可以进入该目录。进入该目录后,该用户在该目录下的有效群组将会变为该目录所属的群组。
3、若用户具有 w 权限,新建文件时,该文件所属的 group 和这个群组所属的 group 相同。
Sticky Bit(SBIT) SBIT 只对目录有效。
有以下作用:当用户对于此目录具有 w,x 权限,当用户在该目录下建立文件或目录时,仅有自己与 root 才有权力删除该文件。
典型的就是 /tmp 目录。

设置 SUID/SGID/SBIT 权限

在 1.3 章节我们说过,文件/目录权限有两种设定方式:数字、字符。

权限形式 描述
数字 给文件/目录设定特殊权限,仅需在原 rwx 权限前再加一个数字即可。
4 为 SUID
2 为 SGID
1 为 SBIT

举例:如一个文件当前权限位 [-rwxr-xr-x],如果要给该文件加上 SUID 权限,可以使用 chmod 4755 即可。

特殊情况说明:如果权限位出现大写 S/T,则表示该权限位置上的权限无效,也不具有相应的特殊权限。如 chmod 7666 /testSUID 不可作用于目录,且该目录也没有可执行权限等一系列问题。
字符 这种形势下,给文件/目录加特殊权限如同加常规权限一样,如 chmod u=rwxs,go=x test

关于 SUID 需要进行说明一下:

进程运行时,有 RUID(真实 UID)、EUID(有效 UID)、SUID(设置 UID)、SSUID(saved SUID)之分。

  • 进程真实 UID:RUID。运行程序文件的 UID,并非文件 owner
  • 进程有效 UID:EUID。执行具有 SUID 权限的程序时,运行该程序的用户将继承此程序的所有者 owner 的权限。
  • 文件 SUID:只可以用于只可执行的文件,使运行该文件的用户具有该文件 owner 的权限。
  • 保存 SUID:Saved SUID,由 Linux 的 exec() 函数保存,可以把 SSUID 理解为 exec() 函数里面的一个局部变量。在《Unix高级环境编程 第3版》的 8.11节里面定义的。

对于 /usr/bin/passwd 文件来说,假如以 alvin(UID = 1001) 运行时,该进程的真实 UID RUID = 1001、有效 UID EUID = 0。有效 UID 即为该文件 owner 的 UID。

1
2
[root@centos7 ~]# ll `which passwd`	// ll $(which passwd)
-rwsr-xr-x. 1 root root 27856 Apr 1 2020 /usr/bin/passwd

但是 alvin 文件已经具有 root 权限了,为什么不能修改其他用户的密码?这个就是 passwd 程序内部处理逻辑,也就是当真实 UID RUID = 0,才允许修改其它用户密码,否则只能修改当前用户密码。这部分在分析进程、内核学习时再进行详细分析,先给自己留个坑。

参考材料:

2.2 umask 默认权限

每个登录系统的用户创建文件/目录时,这些文件和目录都有对应的默认权限。文件/目录默认权限由用户掩码 user file-creation mode maskumask)来控制,系统也封装了 umask 这个工具。umask 显示的权限是要拿掉的权限。

系统开放给目录的默认基本权限是 777,开放给文件的默认基本权限是 666。而具体到每个用户下创建文件/目录权限由 umask 决定。

——《Red Hat Enterprise Linux9-配置基本系统设置-第 20 章-管理文件权限》

计算公式:

  • 创建文件的默认权限 = 文件基本权限(rw-rw-rw-) - umask创建文件的默认权限 = 666 & (~umask)
  • 创建目录的默认权限 = 目录基本权限(rwxrwxrwx) - umask创建目录的默认权限 = 777 & (~umask)

使用 umask 查看默认权限掩码时,可以有两种方式显示默认权限:

  1. 字符形式。直接显示的是目录默认权限。

    1
    2
    [root@centos7 alvin]# umask -S
    u=rwx,g=rx,o=rx
  2. 数字形式。以八进制显示当前 umask 的值。因为文件权限 rwx 三种权限,所以系统也就采用 8 进制来进行描述了。

    1
    2
    [root@centos7 ~]# umask
    0022

注意:以八进制模式显示 umask 时,可以注意到它显示了 12 bits(00020022)。umask 的第一个数字(前 3 bits)代表一个特殊权限(spicky 位、SGID、SUID、SBIT 位)。如果第一个数字设定为 0,则代表没有设置特殊位。

umask 的第一个数字代表特殊权限(sticky 位)。umask 的最后三位数字分别代表从用户拥有者(u)、组群所有者(g)和其它(o)中删除的权限。

1
2
[root@centos7 alvin]# umask
0022

举例说明:

1
2
3
4
5
6
7
8
// umask 查看当前用户掩码值(八进制)
[alvin@centos7 ~]$ umask
0002

// 新建一个文件,权限应该是 666 & (~0002) = 664
[alvin@centos7 ~]$ touch test
[alvin@centos7 ~]$ ll
-rw-rw-r--. 1 alvin alvin 0 718 21:59 test

umask 的值在 /etc/bashrc/etc/login.defs 或 /etc/profile 文件中指定,root 为 022,一般用户为 002。这几个文件做以下区分居具体见 Shell 部分——“bash 环境配置文件”。

2.3 隐藏权限

隐藏权限是指在使用 ll 指令列出的前 10 个 -rwxrwxrwx. 权限之外的权限,必须使用 lsattr 才能查看到。相关权限使用 chattr 进行设置,Ext2/Ext3/Ext4 的 Linux 传统文件系统上能支持完整的 chattr 指令参数,使用 XFS 的CentOS 上仅支持部分参数。

23.png

对于 chattr 常用的参数是 a/i,对于 lsattr 可不指定参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 查看原始权限,无隐藏权限
[root@centos7 ~]# lsattr /home/program/test.c
---------------- /home/program/test.c

// 增加不可删除、改名等权限
[root@centos7 ~]# chattr +i /home/program/test.c

// 查看权限
[root@centos7 ~]# lsattr /home/program/test.c
----i----------- /home/program/test.c

// 已经不可删除
[root@centos7 ~]# rm -f /home/program/test.c
rm: cannot remove ‘/home/program/test.c’: Operation not permitted

// 已经不可改名
[root@centos7 ~]# rename .c .cc /home/program/test.c
rename: /home/program/test.c: rename to /home/program/test.cc failed: Operation not permitted

rename 改名指令的格式:

1
rename 要修改的字符串  目标字符串  要修改的文件

3 用户与组

3.1 用户介绍

系统中的每个账号对应一个 ID,称作 User ID(UID)。系统通过 UID 来管理账号,而账号名称是方便使用者而存在的。所有账号信息保存在 /etc/passwd 中。

平时我们查看文件属性列出来的账号名称( owner/group)都是通过 UID/GID 从 /etc/passwd 文件中找到对应的账号/组名称的。

用户登录系统的大概流程中,会使用终端输入的账号名称,到 /etc/passwd 中找,如果存在就继续读出 UID、GID 和账号的家目录。然后就是拿着 UID 到 /etc/shadow 中进行密码认证。

3.2 /etc/passwd 结构

/etc/passwd 的结构要从行和列两个角度来看,每一行表示一个账号,每一行有 7 列。

1
2
3
[root@centos7 ~]# head /etc/passwd
root : x : 0 : 0 : root : /root : /bin/bash
[账号名称] [密码] [UID] [GID] [账号说明备注] [家目录] [shell路径]
  1. 第 1 列:账号名称。

  2. 第 2 列:密码。早期 Unix 系统将密码放在该列,为了安全性考虑,后来账号密码已经放到 /etc/shadow 中。 目前该列使用 x 表示。

  3. 第 3 列:UID。

    1

    ID 范围 ID 相关特性
    0
    (系统管理员)
    UID == 0 表示该账号属系统管理员。Linux 上默认将 root 的 UID 设置为 0
    1~999
    (系统账号)
    保留给系统使用的 1D,其实除了0之外,其他的 UID 权限与特性并没有不一样。
    默认 1000 以下的数字让给系统作为保留账号只是一个习惯。25.png
    1000~60000
    (普通账号)
    用户新建的普通账号。这个 UID 的大小范围限制在 /etc/login.defs 文件中进行定义。
  4. 第 4 列:GID。用户所在群组的 ID,GID 的信息保存在 /etc/group 中,相当于 UID 之于 /etc/passwd

  5. 第 5 列:账号信息说明。这个字段几乎没啥作用,就是简单用来为当前账号做说明、备注等。

  6. 第 6 列:用户家目录。该列指定当前账号的家目录。

  7. 第 7 列:账号的 shell 路径。该列指定账号登录系统后 shell 的路径。有的系统账号是用来运行系统服务的,则相应的 shell 路径会指定为 /sbin/nologin,表示该账号不可以使用 shell。

3.3 /etc/shadow 结构

进程的运行和用户权限有关,而权限和 UID/GID 相关,所以进程需要读取 /etc/passwd 来获取不同账号的权限(该文件的权限为 -rw-r--r--. 以保证其他用户可读)。

早期的用户密码是保存在 /etc/passwd 的第二列,但是由于安全性问题,后来已经将账号密码加密后保存至 /etc/shadow 的第二列中,该文件对其他用户不可访问,只有 root 能访问。

1
2
[root@centos7 ~]# ll /etc/shadow
----------. 1 root root 1261 Jun 26 22:20 /etc/shadow

文件中每行代表一个用户,每一行有 8 个字段。用户的各个字段用:冒号分隔。/etc/shadow 中各个字段的含义如下:

26.png

字段/列 说明
1 账号名称。对应于 /etc/passwd 中的账号。
2 账号密码。该字段保存的是账号加密后的密码。使用 authconfig --test | grep hashing 查看当前系统使用的加密算法,CentOS 7.x 使用的是 sha512
3 上次更改密码的时间戳。该时间戳的单位是天 days。时间戳的起始日期为 1970-01-01 00:00:00。如 19560 即为 2023-07-22
4 密码不可被更改的天数。该字段指定密码在规定天数内不可以被更改。如果该字段为 0,表示密码可以随时更改。
5 下次改密在多少天后。该字段表示在上次改密的基础上,指定天数后必须修改密码,否则账号将会被禁用。实际可以理解为账号的有效期99999 为 273 年。
6 密码到期前几天提示警告。在密码到期前可以设置密码到期提示,一般系统设置为 7 天。
7 密码过期后多少天账号失效。如果该字段等于 n,表示字段 5 指定的时间之后的第 n+1 天该账号就将会被禁用。在这个 n 天内登录账号就会强制提示更改密码了。
8 账号失效日期。该时间戳的单位是天 days。时间戳的起始日期为 1970-01-01 00:00:00。如 19560 即为 2023-07-22
9 该字段保留,看以后有没有新功能加入。

密码找回方法

  1. 普通用户的密码找回:可以通过 root 找好修改密码,root 账号无需知道以前的旧密码就可以更改。
  2. root 密码找回:重新启动系统时进入维护模式,系统会主动的给予 root 权限的 bash 接口, 此时再以 passwd 修改密码即可;或以 Live CD 开机后挂载根目录去修改 /etc/shadow, 将里面的 root 的密码字段清空,再重新启动后 root 将不用密码即可登入!登入后再赶快以 passwd 指令去设定 root 密码即可。

3.4 用户组

账号的信息和密码分别保存在 /etc/passwd/etc/shadow 中。群组的信息和密码存放在 /etc/group/etc/gshadow 中。

/etc/passwd 的第 4 个字段为 GID,使用该字段到 /etc/group 中找到对应的群组信息。

3.5 /etc/group 结构

文件中同样每行代表一个用户组,每一行有 4 个字段。用户的各个字段用:冒号分隔。/etc/group 中各个字段的含义如下:

1
2
3
4
5
6
7
[alvin@centos7 root]$ cat /etc/group
root : x : 0 :
bin : x : 1 :
daemon : x : 2 :
sys : x : 3 :
alvin : x : 1000 :
[组名称] [组管理员的密码] [GID] [附加成员账号]
字段/列 说明
1 群组名称。
2 组管理员的密码。为了安全性考虑,该字段已经移至 /etc/gshadow 中。该字段目前使用 x 表示。
用户组密码主要是用来指定组管理员的,由于系统中的账号可能会非常多,root 用户可能没有时间进行用户的组调整,这时可以给用户组指定组管理员,如果有用户需要加入或退出某用户组,可以由该组的组管理员替代 root 进行管理。但是这项功能目前很少使用,我们也很少设置组密码。如果需要赋予某用户调整某个用户组的权限,则可以使用 sudo 命令代替。
3 GID。组的 ID,对应于 /etc/passwd 的第 4 个字段为 GID
4 该组的附加成员账号。
初始群组附加群组有效群组
1、初始群组:每个账号 /etc/passwdGID 即为该账号的初始群组,每个账号只有一个初始群组。
2、附加群组:每个账号可以加入到多个群组中,除了初始群组外,这个账号加入的其他群组称为附加群组。
3、有效群组:groups 命令输出的第一个组即为当前账号的有效群组,可以使用 newgrp 在已加入的群组中指定该用户的有效群组(该指令做的改变仅在当前会话中有效,如果使用 exit 后该账号的有效群组仍然是初始群组)。新建文件/目录的权限中 GID有效群组

/etc/group 的最后一列表示该组的附加成员。注意:如果该用户组是某个用户的初始组,则该用户不会写入到 /etc/group 的第 4 个字段中(该字段显示的用户都是这个用户组的附加用户)。

可以使用 groups 命令查看当前账号加入的组有哪些。有个问题,一个账号可以加入到多个组中,当这个账号创建文件/目录时,该文件/目录对应的 GID 是多少呢?实际上该 GID 为有效群组的 ID(effective group)。

1
2
3
4
5
6
7
8
9
10
11
// groups 命令显示的第一个组为有效组
[alvin@centos7 root]$ groups
alvin wheel

[root@centos7 ~]# cat /etc/group | grep wheel
wheel:x:10:alvin

// 可以看到新创建的文件属于有效群组
[alvin@centos7 /]$ touch /tmp/test_group
[alvin@centos7 /]$ ll /tmp/test_group
-rw-rw-r--. 1 alvin alvin 0 Jul 23 22:36 /tmp/test_group

可以使用 newgrp 在已加入的群组中指定该用户的有效群组,注意:有效群组并不一定等于初始群组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 此时的有效群组为初始群组
[alvin@centos7 root]$ groups
alvin wheel

// 更改用户的有效群组为 wheel
[alvin@centos7 root]$ newgrp wheel
[alvin@centos7 root]$ groups
wheel alvin

// 新建个文件并查看该文件对应的群组是谁
[alvin@centos7 root]$ touch /tmp/test_effe_group

// 该文件的组属于新更改的有效群组 wheel
[alvin@centos7 root]$ ll /tmp/test_effe_group
-rw-r--r--. 1 alvin wheel 0 Jul 23 22:42 /tmp/test_effe_group

// 查看 /etc/passwd 中账号对应的初始群组是否更改
[alvin@centos7 root]$ cat /etc/passwd | grep alvin
alvin:x:1000:1000:alvin:/home/alvin:/bin/bash
// 可以发现,newgrp 只会更改账号的有效群组,不会更改初始群组
[alvin@centos7 root]$ cat /etc/group | grep 1000
alvin:x:1000:
[alvin@centos7 root]$ cat /etc/group | grep wheel
wheel:x:10:alvin

// 该指令做的改变仅在当前会话中有效,如果使用 exit 指令后该账号的有效群组仍然是初始群组
[root@centos7 /]# su root
[root@centos7 /]# su alvin
[alvin@centos7 /]$ groups
alvin wheel

可以使用 newgrp 在已加入的群组中指定该用户的有效群组(该指令做的改变仅在当前会话中有效,如果使用 exit 后该账号的有效群组仍然是初始群组)。如下图:

27.png

让用户加入新的组有两个方法:

  1. 一个是透过系统管理员(root)利用 usermod 帮你加入
  2. 如果系统有设定群组管理员,那么可以通过群组管理员以 gpasswd 帮你加入他所管理的群组中。

参考资料:

3.6 /etc/gshadow

/etc/gshadow 文件中同样每行代表一个用户组,每一行有 4 个字段。用户的各个字段用:冒号分隔。各个字段的含义如下:

1
2
3
4
5
6
[root@centos7 ~]# cat /etc/gshadow
root : : :
bin : : :
unbound: ! : :
kvm : ! : : qemu
[组名称] [组管理员的密码] [组管理员账号] [附加成员账号]

可以看到,该文件中的每一行和 /etc/group 非常像,有区别的是第 3 字段:

  • /etc/group:第 3 字段为 GID
  • /etc/gshadow:第 3 字段为组管理员账号。

说明:如果第 2 字段为或者 !,表示该群组不具有组管理员。

4 用户管理

  • 账号管理

    • 新增账号:useradd 命令

    • /etc/shadow 字段修改:passwdchage 命令。

    • /etc/passwd 字段修改:usermod 命令。

    • 账号删除:userdel 命令。

  • 组管理

    • 创建组:groupadd 命令。
    • 修改 GID、组名称:groupmod 命令。
    • 删除组 :groupdel 命令。
    • 创建组管理员:gpasswd 命令。

注意useradd/usermod/userdel 都是系统管理员所能够使用的指令,作为一般普通账号可以使用 id [username] 查看其他账号的信息,或者直接读取 /etc/passwd 的信息。

4.1 创建账号

新增账号使用 useradd 命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@centos7 ~]# useradd [-u UID] [-g 初始群组] [-G 次要群组] [-mM] \
> [-c 说明栏〕[-d 家目录绝对路径] [-S shell] 账号名称

选项与参数:
-u :指定账号 UID
-g :指定账号的初始群组;该群组的 GID 会被放置到 /etc/passwd 的第 4 个字段内
-G :后面接的组名则是这个账号还要加入的群组,即附加群组
-M:强制不要建立用户家目录!(系统账号默认值)
-m:强制要建立用户家目录!(一般账号默认值)
-c:这个就是 /etc/passwd 的第 5 个字段的说明内容啦
-d:指定某个目录成为家目录,而不要使用默认值。务必使用绝对路径!
-r:建立一个系统的账号,这个账号的 UID 会有限制(参考 /etc/login.defs。200~1000)
-s:后面接一个 shell,若没有指定则预设是 /bin/bash
-e:账号失效日期。格式为 YYYY-MM-DD,此项目可写入 shadow 第 8 字段
-f:后面接 shadow 的第 7 字段,0->密码到期后立即失效,-1->密码永不失效

如果不指定 useradd 命令的参数,系统会按照预设默认值进行普通账号创建,主要有以下几项:

  1. /etc/passwd 中创建一行数据,并填充每一列。
  2. /etc/sahdow 中创建一行数据,默认密码为空。
  3. /etc/group 中创建一行数据,会创建一个和账号名称一样的组名称,默认不会创建组管理员。
  4. /etc/gshadow 中创建一行数据,默认没有管理员账号和密码。
  5. /home 下创建一个和账号名一样的目录作为家目录。

由于系统账号主要是用来运行系统服务,所以系统账号默认都不会创建家目录。

3.2 账号默认值

如果使用 useradd 命令不指定任何参数来创建账号时,可创建一个普通账号,该账号的默认属性由两个文件指定:

  1. /etc/default/useradd。指定家目录基址、账号过期时间、shell 类型等。
  2. /etc/login.defs。指定账号家目录的 umask=077,注意不是账号的 umask。指定账号 UIDGID 的大小、范围,密码的限制等。

创建账号默认的值由 useradd 这个程序指定,可以使用 useradd -D 查看,这个指令会输出 /etc/default/useradd 文件的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@centos7 ~]# useradd -D
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/bash
SKEL=/etc/skel
CREATE_MAIL_SPOOL=yes

// /etc/default/useradd
[root@centos7 ~]# cat /etc/default/useradd
# useradd defaults file
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/bash
SKEL=/etc/skel
CREATE_MAIL_SPOOL=yes

一、/etc/default/useradd 文件

字段 说明
GROUP=100 Linux 设定是让新建账号的初始群组属于 users(GID=100),但是 CentOS 上预设的初始群组和账号同名。Linux 上有两种不同的机制:
1、私有群组机制:创建账号时,会自动创建一个与账号同名的群组作为该账号的初始群组。使得每个账号都有自己的家目录,并且权限为 700,具有较好的保密性。该机制并不会参考使用 GROUP=100 这个设定值,这样的系统有 REHL、CentOS、Fedora。
2、公共群组机制:创建账号时,使用 GROUP=100 这个设定值作为账号的初始群组,默认的家目录权限是 755,这样的账号都可以共享家目录的文件,这样的操作系统由 SuSE。
HOME=/home 指定用户家目录的基准目录(basedir)。用户的家目录通常与账号同名,在 /home 目录下。如 /home/alvin
INACTIVE=-1 密码到期后,多少天时效。即为 /etc/shadow 的第 7 个字段。0:密码到期后立即失效,-1:密码永不失效。
EXPIRE= 账号的失效日期,是个时间戳,单位是 day。对应 /etc/shadow 的第 8 个字段。
SHELL=/bin/bash 系统默认使用的 shell 类型。如果是运行系统服务的系统账号,shell 类型可以设置为 /sbin/nologin,这样的账号就不使用 shell 登录系统。
SKEL=/etc/skel 用户家目录的参考基准目录。每个新创建的用户家目录下的文件都是从这个目录复制过去的。
CREATE_MAIL_SPOOL=yes 创建用户的邮箱 mailbox。在 /var/spool/mail/ 目录下创建一个和用户同名的邮箱文件。

二、/etc/login.defs 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
[root@centos7 ~]# cat /etc/login.defs 
#
# Please note that the parameters in this configuration file control the
# behavior of the tools from the shadow-utils component. None of these
# tools uses the PAM mechanism, and the utilities that use PAM (such as the
# passwd command) should therefore be configured elsewhere. Refer to
# /etc/pam.d/system-auth for more information.
#

# *REQUIRED*
# Directory where mailboxes reside, _or_ name of file, relative to the
# home directory. If you _do_ define both, MAIL_DIR takes precedence.
# QMAIL_DIR is for Qmail
#
#QMAIL_DIR Maildir
MAIL_DIR /var/spool/mail #用户默认邮件信箱放置目录
#MAIL_FILE .mail

# Password aging controls:
#
# PASS_MAX_DAYS Maximum number of days a password may be used.
# PASS_MIN_DAYS Minimum number of days allowed between password changes.
# PASS_MIN_LEN Minimum acceptable password length.
# PASS_WARN_AGE Number of days warning given before a password expires.
#
PASS_MAX_DAYS 99999 # /etc/shadow 的第 5 列,下次改密在多少天后。
PASS_MIN_DAYS 0 # /etc/shadow 的第 4 列,密码不可被更改的天数。
PASS_MIN_LEN 5 # 密码最短的字符长度,已被 pam 模块取代,失去效用。
PASS_WARN_AGE 7 # /etc/shadow 的第 6 列,密码到期前几天提示警告。

#
# Min/max values for automatic uid selection in useradd
#
UID_MIN 1000 # 普通账号最小 UID
UID_MAX 60000 # 普通账号最大 UID
# System accounts
SYS_UID_MIN 201 # 用户创建的最小系统账号 UID
SYS_UID_MAX 999 # 用户创建的最大系统账号 UID

#
# Min/max values for automatic gid selection in groupadd
#
GID_MIN 1000 # 普通账号初始组的最小 GID
GID_MAX 60000 # 普通账号初始组的最大 GID
# System accounts
SYS_GID_MIN 201 # 用户创建的最小系统账号初始组 GID
SYS_GID_MAX 999 # 用户创建的最大系统账号初始组 GID

#
# If defined, this command is run when removing a user.
# It should remove any at/cron/print jobs etc. owned by
# the user to be removed (passed as the first argument).
#
#USERDEL_CMD /usr/sbin/userdel_local

#
# If useradd should create home directories for users by default
# On RH systems, we do. This option is overridden with the -m flag on
# useradd command line.
#
CREATE_HOME yes # useradd 在不加 -M 及-m 时,是否主动建立用户家目录

# The permission mask is initialized to this value. If not specified,
# the permission mask will be initialized to 022.
UMASK 077 # 用户家目录的 umask,对应的家目录权限是 700

# This enables userdel to remove user groups if no members exist.
#
USERGROUPS_ENAB yes # 使用 userdel 删除账号时,是否会删除初始群组

# Use SHA512 to encrypt password.
ENCRYPT_METHOD SHA512 # 账号密码、组管理员密码的加密算法

/etc/login.defs 文件中特别需要注意两点:

  • 1)umask 表示家目录的掩码值,并非 /etc/profile 中指定的账号 umask
  • 2)USERGROUPS_ENAB yes 指定使用 userdel 删除账号时,是否会删除初始群组。如果使用 userdel 去删除一个账号时,且该账号所属的初始群组己经没有人隶属于该群组了,那么就删除掉该群组。

总结来说:创建账号时,要参考 /etc/default/useradd/etc/login.defs 文件指定的属性。并在 /etc/passwd/etc/shadow/etc/group/etc/gshadow 中写入相关数据。

新的一些发行的操作系统使用 PAM 模块来管理用户密码,这个管理机制在 /etc/pam.d/passwd 控制,该文件中,与密码有关的测试模块为 pam_cracklib.so,该模块会检验密码相关的信息,并且该模块的设定会取代 /etc/login.defsPASS_MIN_LEN 的设定。

3.3 修改/删除账号

修改账号 /etc/passwd/etc/shadow 的字段可以使用 usermod/passwd/chage

如果账号不使用了,可以修改 /etc/shadow 第 8 个字段(账号失效日期)为 0,以达到禁用账号的目的,但是账号的相关数据还会保存在系统上。如果要彻底删除账号和相关数据,可以使用 userdel -r 用户名,以完整删除账号,之后使用 find / -user 用户名 来查看是否还存留该账号的数据。

usermod -L/U 用户名 类似于 passwd -l/u 用户名 将某个账号冻结(Lock)起来,锁起来时该账号 /etc/shadow 第 2 列会显示 !

3.4 创建/修改/删除组(用户)

组操作 命令 (参数)解释
创建组 groupadd 命令格式:groupadd [-g gid][-r〕组名
参数:
-g :指定 GID。
-r:创建系统组(200<GID<1000)。
修改 GID、组名称 groupmod 命令格式:groupmod [-g gid] [-n group_name] 群组名
参数:
-g:要修改成该 GID。
-n:要修改成该组名称
删除组 groupdel 命令格式:groupdel [groupname]
说明:
如果要删除的组是某个账号的初始群组,则无法通过 groupdel 删除群组(初始群组不支持删除)。
创建组管理员 gpasswd 命令格式:gpasswd [-A user1,...] [-M user2,...] 组名
主要功能:该命令可以指定组管理员、修改组管理员密码、将账号加入/移出群组。
选项与参数表:

[]:无参数,修改组管理员密码
-A :指定一个账号为该组的管理员,该账号无需加入该组也可以被设定
-a :将指定的用户加入到指定的群组
-d :将指定的用户从指定的群组中删除
-M :同 -a 参数,将指定的用户加入到指定的群组
-r :将组管理员密码移除
-R :使组管理员和密码失效,即在 /etc/gshadow 的第二列(管理员密码)设置 !

注意

  • usermod -G 组名 用户名 是用户附加的组,从原来的附加组移到新的附加组。
  • gpasswd -a 用户 用户组 是将用户再加入到另外一个组,累加的关系。

参考资料:

5 文件 ACL

5.1 ACL 介绍

有一个需要解决的事,在文件权限中,我们已经学习了 owner, group, others 三种身份下文件的 r, w, x 三种常规权限。还有文件/目录的 SUID, SGIT, SBIT特殊权限,如某个可执行文件的权限为4755,表示 -rwsr-xr-x。最后学了 chattr 设置的隐藏权限,只有 lsattr 才可以看到。

但是这些权限都是针对 owner, group, others 三种身份来设置的,无法做到对某个单独的用户、群组设置对某文件的权限控制,“如何指定某个文件仅允许某个用户/组来访问”,所以引入了文件的控制列表(Access Control List,ACL)来解决这些问题。

ACL 可以针对单一用户,单一文件或目录来进行 r/w/x 的权限控制。大部分文件系统(File System)都支持 ACL。ACL 可以做到:

  1. 针对单个用户:可以针对某个用户设置对某个文件/目录的访问权限。
  2. 针对某个组:可以针对某个组设置对某个文件/目录的访问权限。
  3. 预设权限(mask):可以对某个目录设置好预设权限,之后在该目录下创建子目录或文件时,将会自动继承预设好的文件 ACL 权限。

在有的文献中,有两种类型的 ACL 规则(rules):

  1. access ACLs:针对单个文件/目录的访问信息。
  2. default ACLs:仅对目录有关,也就是上述的预设的目录权限。

ext2/ext3/ext4/xfs 文件系统基本都对 ACL 提供支持,可以通过如下命令查看:

1
2
3
[alvin@centos7 ~]$ dmesg | grep -i acl
[ 1.086056] systemd[1]: systemd 219 running in system mode. (+PAM +AUDIT +SELINUX ... +ACL +XZ ...)
[ 3.743006] SGI XFS with ACLs, security attributes, no debug enabled

可以看到系统中 XFS 文件系统支持 ACL。

5.2 setfacl 设置权限

setfacl 用来设置文件/目录的 ACL 访问权限。指令格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@centos7 ~]# setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ...
例:
setfacl -m u:用户名:权限 文件名
setfacl -m g:组名称:权限 文件名

常用参数说明:
-m: 给目标文件设定指定用户/组的访问权限,如 -m u:alvin:rw
-M: 给目标文件设定指定访问权限,访问权限写在后面指定的文件中,如 -M acl_file
-x: 移除目标文件指定用户/组下的所有权限,如 -x u:alvin
-X: 移除目标文件指定用户/组下的所有权限,ACL 指定规则在后面文件中,如 -X acl_file
-b: 移除目标文件所有 ACL 权限(所有用户/组)
-k: 移除已经预设给目标目录的ACL 权限
-d: 给某个目录设定预设 ACL 权限,使得新建的子目录、文件继承已经预设的 ACL 权限。该参数只能用于目录。
-R: 递归地设定权限,包括该目录下的子目录、文件

-x 和 -b 的区别:
-x:只移除特定用户/组对应的所有 ACL 权限,不会移除文件权限中的 + 号
-b:移除作用在该文件中设置的所有 ACL 权限,也会移除该文件权限中的 + 号

举例说明:

  1. 使用 root 新建一个仅 root 有 rwx 权限的文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 新建文件,umask(root) = 022, file_full_mask = 666
    [root@centos-7 ~]# touch /tmp/test_acl; echo "Hello ACL." > /tmp/test_acl
    [root@centos-7 ~]# ll /tmp/test_acl
    -rw-r--r--. 1 root root 0 Jul 26 20:40 /tmp/test_acl

    // 修改文件权限
    [root@centos-7 ~]# chmod 700 /tmp/test_acl
    [root@centos-7 ~]# ll /tmp/test_acl
    -rwx------. 1 root root 0 Jul 26 20:40 /tmp/test_acl

    // 此时一般的用户无法访问该文件
    [alvin@centos-7 ~]$ cat /tmp/test_acl
    cat: /tmp/test_acl: Permission denied
  2. alvin 用户设置对该文件的可读权限。

    1
    2
    3
    4
    5
    [root@centos-7 ~]# setfacl -m u:alvin:r /tmp/test_acl 

    // 注意观察此时的文件权限
    [root@centos-7 ~]# ll /tmp/test_acl
    -rwxr-----+ 1 root root 11 Jul 26 20:48 /tmp/test_acl

    可以看到权限最后一列有个 +,说明一个情况:文件/目录权限最后一列有 + 时,说明该文件/目录有相应的 ACL 权限设置。

    还要注意到:此时在该文件 group 对应的权限上显示了 r,而此时 group 对应的这三列是 mask 的值。这个地方很微妙,ACL 设置时,原先 group 上的三列的权限值就是默认的 mask 值。

    上述仅限于 root 创建的文件,如果是一般用户创建的文件则有不同,具体可以修改。

  3. 使用 alvin 访问该文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [alvin@centos-7 ~]$ cat /tmp/test_acl 
    Hello ACL.

    // 查看 ACL 权限,mask::r--
    [alvin@centos-7 ~]$ getfacl /tmp/test_acl
    getfacl: Removing leading '/' from absolute path names
    # file: tmp/test_acl
    # owner: root
    # group: root
    user::rwx
    user:alvin:r--
    group::---
    mask::r--
    other::---

    可以看到 alvin 已经可以访问该文件了。

  4. 移除 alvin 的访问权限。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [alvin@centos-7 ~]$ cat /tmp/test_acl 
    cat: /tmp/test_acl: Permission denied

    [alvin@centos-7 ~]$ ll /tmp/test_acl
    -rwx------+ 1 root root 11 Jul 26 20:48 /tmp/test_acl

    // 查看 ACL 权限,mask::---
    [alvin@centos-7 ~]$ getfacl /tmp/test_acl
    getfacl: Removing leading '/' from absolute path names
    # file: tmp/test_acl
    # owner: root
    # group: root
    user::rwx
    group::---
    mask::---
    other::---

    此时该文件权限中还有 +,但是 alvin 已经不能访问该文件了。要想完全清除该文件的 +,必须要使用 -b 参数。

    1
    2
    [alvin@centos-7 ~]$ ll /tmp/test_acl 
    -rwx------. 1 root root 11 Jul 26 20:48 /tmp/test_acl

说明:关于 getdacl 命令中展示的 user:alvin:r-- 等格式的语句,具体说明参见《Linux/UNIX 系统编程手册(上册)第17章 访问控制列表》。

5.3 getfacl 读取权限

查看文件权限时,如果有 +,则表示该文件有 ACL 指定的权限,这个时候可以使用 getfacl 进行查看。

命令格式:

1
getfacl [-aceEsRLPtpndvh] file ...

参数不进行单独说明,但是需要对命令返回信息进行说明:

  • #:文件的常规默认属性。
  • user:文件的 owner
  • user:alvin:r-x:表示该文件指定给 alvin 用户 r-x 权限。
  • mask:指示文件/目录最大权限(也叫做有效权限,effective permission),指定给其他用户/组的权限必须在该权限范围内。

5.4 mask 最大权限

关于最大权限(有效权限)需要进行说明,这个权限会体现在 mask 这个关键字上,可以使用 m:[rwx] 来对 mask 进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@centos-7 ~]# ll /tmp/test_acl 
-rwx------. 1 root root 11 Jul 26 20:48 /tmp/test_acl

// 设置 ACL
[root@centos-7 ~]# setfacl -m u:alvin:rw /tmp/test_acl
[root@centos-7 ~]# setfacl -m g:alvin:r /tmp/test_acl

// 查看 ACL 权限
[root@centos-7 ~]# getfacl /tmp/test_acl
getfacl: Removing leading '/' from absolute path names
# file: tmp/test_acl
# owner: root
# group: root
user::rwx // 文件 owner(root)的权限
user:alvin:rw- // 指定给 alvin 用户读写权限
group::--- // 文件所属组的权限
group:alvin:r-- // 给 alvin 这个组可读的权限
mask::rw- // 文件的最大权限
other::---

将文件最大权限 mask 修改为只读 r--

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 修改 mask
[root@centos-7 ~]# setfacl -m m:r /tmp/test_acl

[root@centos-7 ~]# getfacl /tmp/test_acl
getfacl: Removing leading '/' from absolute path names
# file: tmp/test_acl
# owner: root
# group: root
user::rwx
user:alvin:rw- #effective:r-- // 有效权限,此时 alvin 用户仅拥有只读权限
group::---
group:alvin:r--
mask::r-- // 已经修改
other::---

说明:关于 getdacl 命令中展示的 user:alvin:r-- 等格式的语句,具体说明参见《Linux/UNIX 系统编程手册(上册)第17章 访问控制列表》。

5.5 目录预设权限

当使用 setfacl -m [u|g]:[user|group]:权限 目录名称 给某个用户/组指定 ACL 权限后,当该用户/组成员在该目录下创建新的文件时,新的文件并不具备父目录的 ACL 权限可以使用 setfacl -d 参数来给目录预设权限,使得该目录下新建文件/子目录都能够继承相关的 ACL 权限-d 参数只能用于目录。

  1. 观察无预设 ACL 权限的目录。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    // 使用 root 账号在 /tmp 目录下新建一个目录 test_dir
    [root@centos-7 ~]# mkdir /tmp/test_dir

    // 修改目录权限为 750
    [root@centos-7 ~]# chmod 750 /tmp/test_dir
    [root@centos-7 ~]# ll /tmp | grep test_dir
    drwxr-x---. 2 root root 6 Jul 26 23:52 test_dir

    // 给 alvin 用户开放 r、x 权限
    [root@centos-7 ~]# setfacl -m u:alvin:rx /tmp/test_dir/

    [root@centos-7 ~]# getfacl /tmp/test_dir/
    getfacl: Removing leading '/' from absolute path names
    # file: tmp/test_dir/
    # owner: root
    # group: root
    user::rwx
    user:alvin:r-x
    group::r-x
    mask::r-x
    other::---

    // 再用 root 在 test_dir 目录创建新的文件,并查看 ACL 权限
    // 可以看到新的文件并没有继承父目录 test_dir 具有的 ACL 权限
    [root@centos-7 ~]# touch /tmp/test_dir/0727.txt
    [root@centos-7 ~]# ll /tmp/test_dir/0727.txt
    -rw-r--r--. 1 root root 0 Jul 27 00:00 /tmp/test_dir/0727.txt

    [root@centos-7 ~]# getfacl /tmp/test_dir/0727.txt
    getfacl: Removing leading '/' from absolute path names
    # file: tmp/test_dir/0727.txt
    # owner: root
    # group: root
    user::rw-
    group::r--
    other::r--
  2. 给目录预设权限。注意:当目录有预设权限时,使用 getfacl 命令查看文件 ACL 权限时就会显示 default 关键字。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    // 删除目录重新创建
    [root@centos-7 ~]# rm -rf /tmp/test_dir/
    [root@centos-7 ~]# mkdir /tmp/test_dir
    [root@centos-7 ~]# chmod 750 /tmp/test_dir
    [root@centos-7 ~]# ll /tmp | grep test_dir
    drwxr-x---. 2 root root 6 Jul 27 00:06 test_dir

    // 给 alvin 用户预设 ACL 权限,指定该目录的 r、w、x 权限
    // 没有 x 权限,进不去目录
    [root@centos-7 ~]# setfacl -d -m u:alvin:rwx /tmp/test_dir/

    [root@centos-7 ~]# ll /tmp | grep test_dir
    drwxr-x---+ 2 root root 6 Jul 27 00:21 test_dir

    [root@centos-7 ~]# getfacl /tmp/test_dir
    getfacl: Removing leading '/' from absolute path names
    # file: tmp/test_dir
    # owner: root
    # group: root
    user::rwx
    group::r-x
    other::---
    default:user::rwx
    default:user:alvin:rwx // 这里显示的就是给 alvin 预设读写权限
    default:group::r-x
    default:mask::rwx
    default:other::---

    // 再用 root 在 test_dir 目录创建新的文件,并查看 ACL 权限
    // 可以看到新创建的文件已经给 alvin 父目录的 ACL 权限了
    [root@centos-7 ~]# touch /tmp/test_dir/0727.txt
    [root@centos-7 ~]# ll /tmp/test_dir/0727.txt
    -rw-rw----+ 1 root root 0 Jul 27 00:24 /tmp/test_dir/0727.txt

    [root@centos-7 ~]# getfacl /tmp/test_dir/0727.txt
    getfacl: Removing leading '/' from absolute path names
    # file: tmp/test_dir/0727.txt
    # owner: root
    # group: root
    user::rw-
    user:alvin:rwx // alvin 用户继承父目录 test_dir 的 ACL 权限
    group::r-x #effective:r--
    mask::rw-
    other::---

    // 使用 alvin 用户在 0727.txt 文件写入数据测试
    // 可以看到是失败的,这是因为我们只给目录预设了权限,但是还没有讲相应的 ACL 权限给 alvin
    [alvin@centos-7 ~]$ echo "Hello, my permissions inherited from parent directory." >> /tmp/test_dir/0727.txt
    -bash: /tmp/test_dir/0727.txt: Permission denied

    最后我们看到 alvin 用户并没有成功写入 0727.txt 文件。这是因为我们只是给目录 test_dir 预设了 ACL 权限,还没有给 alvin 用户添加访问 test_dir 目录的 ACL 权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 给 alvin 添加访问 test_dir 目录的 ACL 权限
    [root@centos-7 ~]# setfacl -m u:alvin:rwx /tmp/test_dir/

    // 查看该目录的 ACL 权限
    [root@centos-7 ~]# getfacl /tmp/test_dir/
    getfacl: Removing leading '/' from absolute path names
    # file: tmp/test_dir/
    # owner: root
    # group: root
    user::rwx
    user:alvin:rwx // 可以看到比上面多出来这行,也就是给 alvin 添加访问 test_dir 目录的 ACL 权限
    group::r-x
    mask::rwx
    other::---
    default:user::rwx
    default:user:alvin:rwx
    default:group::r-x
    default:mask::rwx
    default:other::---

    下面使用 alvin 用户在 0727.txt 文件写入数据测试。

    1
    2
    3
    4
    [alvin@centos-7 ~]$ echo "Hello, my permissions inherited from parent directory." >> /tmp/test_dir/0727.txt

    [alvin@centos-7 ~]$ cat /tmp/test_dir/0727.txt
    Hello, my permissions inherited from parent directory.

    可以看到写入成功。

参考内容:

6 用户切换

Linux 是多用户多任务的操作系统,为了保证系统的安全性,有的服务不允许某些账号登录。不同用户之间也必须有相应的隔离。

如 telnet、ssh 服务都可以拒绝 root 用户登录。在服务器中,一般都是以普通账号身份进行登录。要在不同的账号账号进行切换,可以使用 susudo 两个命令。

susudo 的区别:

  • su:可以切换到目标用户的 shell 环境,但是必须要知道目标用户的密码才能进行切换。
  • sudo:以目标用户身份运行命令,需验证用户自己的密码。但是需要设置 /etc/sudoers 文件,给用户进行授权,指定哪些用户/组可以使用 sudo 命令。

6.1 su

su 命令切换用户身份时,有 login-shell 和 non-login shell 两种方式,即是否切换用户环境变量的两种方式。通过 man su 可以获取这些信息:

  • non-login shell 方式:用户使用 su 命令时不加任何参数,如 su alvin
    • 不会改变当前目录
    • 切换到目标用户后,关于环境变量,只会改变 HOME/SHELL 环境变量(如果是从 root 切换到普通用户,则还会改变 USER/LOGNAME 变量),其他环境变量还是切换前用户的值。特别 PATH/PWD 等环境变量并不会更改。
  • login-shell 方式:使用 --l 参数,会完整地切换到新的 shell 中。
    • 切换至目标用户的家目录中。
    • 环境变量内容都全部为目标用户的环境

man su 输出信息中可以看到,系统使用 PAM 模块来管理 su 的认证、账号和会话管理。

命令格式:

1
2
3
4
5
6
7
su [-lm] [-c 指令] 用户名

常用参数说明:
- :即 su - username,以 login shell 登录方式切换至目标用户(如果缺省用户则切换至 root)
-l :即 su -l username,以 login shell 登录方式切换至目标用户 // - 和 -l 类似
-m :和 -p 一样,只是身份切换至目标用户,环境变量完全是原先用户的环境变量
-c : 仅以目标用户身份执行一条命令,命令的内容跟在 -c 参数之后

举例说明:

  1. 以 non-login shell 方式切换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 不加任何参数时,以 non-login shell 方式
    [alvin@centos-7 ~]$ su root
    Password:

    // 查看当前用户身份,发现已经成功切换至 root
    [root@centos-7 alvin]# id
    uid=0(root) gid=0(root) groups=0(root)
    context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

    // 查看当前用户的环境(仅列出部分环境变量)
    // 可以看到,USER、PATH、MAIL、PWD、LOGNAME 都是原 alvin 的环境
    [root@centos-7 alvin]# env
    USER=alvin
    PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:
    /home/alvin/.local/bin:/home/alvin/bin
    MAIL=/var/spool/mail/alvin
    PWD=/home/alvin
    HOME=/root
    LOGNAME=alvin
  2. 以 login shell 方式切换。切换用户后会有提示上次 login shell 的时间信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 使用 - 或 -l 参数切换,不指定用户的话默认切换至 root(su -)
    [alvin@centos-7 ~]$ su -l root
    Password:
    Last login: Thu Jul 27 22:49:15 CST 2023 on pts/0

    [root@centos-7 ~]# id
    uid=0(root) gid=0(root) groups=0(root)
    context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

    // 可以看到所有的环境变量已经全部被切换为目标用户的了(仅列出部分环境变量)
    [root@centos-7 ~]# env
    USER=root
    MAIL=/var/spool/mail/root
    PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
    PWD=/root
    HOME=/root
    LOGNAME=root

6.2 sudo

当使用 su 命令切换至 root 时,必须要输入 root 密码,并且还会切换至 root 环境。这样很不妥当,如果每次使用 su - -c 执行一条命令,每次都要输入 root 密码,可能会带来密码泄露的问题。另外系统中还有很多指定 /sbin/nologin 运行系统服务的账号,不能使用 su - 切换登录到该用户。为了解决这些问题,可以使用 sudo 命令来以目标用户的身份来执行命令。

sudo 的功能:以指定用户身份运行命令。

除了 root 以外,其他用户要想使用 sudo 命令,必须使用 root 在 /etc/sudoers 文件中对用户进行授权,否则没有权限使用 sudo 命令。修改该文件必须直接使用 visudo,该命令的语法类似于 vi/vim,但是不能使用 vi/vim 对该文件进行编辑。

/etc/sudoers 文件遵循相关的规范,文件保存时会自动检查相关语法是否符合规范,如果不符合则会提示,并且无法保存。

命令格式:

1
2
3
4
5
sudo [-bACEh] [-u 目标用户]

常用参数说明:
-u: 指定要切换的目标用户名称,如果缺省则表示切换至root
-b: 将指定的命令放到背景中去执行,而不影响到当前shell

一般的普通用户,没有在 /etc/sudoers 中对齐权限设置,是没有权限执行 sudo 命令的,如下。

1
2
[alvin@centos-7 ~]$ sudo -u root cat /etc/shadow
alvin is not in the sudoers file. This incident will be reported.

alvin 用户使用 sudo 命令后,在 root 账户的操作中会有提示,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos-7 ~]# ll /etc/sudoers
-r--r-----. 1 root root 4328 Sep 30 2020 /etc/sudoers
You have new mail in /var/spool/mail/root // 提示收到邮件

// 打开邮件内容查看
[root@centos-7 ~]# cat /var/spool/mail/root
... // 省略以前的信息
From root@centos-7.9.shared Thu Jul 27 23:25:45 2023
Return-Path: <root@centos-7.9.shared>
X-Original-To: root
Delivered-To: root@centos-7.9.shared
Received: by centos-7.9.shared (Postfix, from userid 0)
id 7DD15404298E; Thu, 27 Jul 2023 23:25:45 +0800 (CST)
To: root@centos-7.9.shared // 收件人是 root
From: alvin@centos-7.9.shared // 发件人是 alvin
Auto-Submitted: auto-generated
Subject: *** SECURITY information for centos-7.9.shared ***
Message-Id: <20230727152545.7DD15404298E@centos-7.9.shared>
Date: Thu, 27 Jul 2023 23:25:45 +0800 (CST)
// 下面是邮件内容,实际上就是提示 alvin 用户执行了
// sudo -u root cat /etc/shadow 命令,以及执行后的一些信息
centos-7.9.shared : Jul 27 23:25:45 : alvin : user NOT in sudoers ; TTY=pts/0 ;
PWD=/home/alvin ; USER=root ; COMMAND=/bin/cat /etc/shadow

6.3 /etc/sudoers

Linux 上不同用户之间有严格控制,而要以目标用户身份运行命令,一般是用 sudo -u 目标用户 要执行的命令行 来进行操作。而除了 root 之外,其他所有用户要实行 sudo 命令,必须要在 /etc/sudoers 中设置了相关的权限才行。

修改 /etc/sudoers 文件直接使用 visudo 即可(不参数),关于用户权限这儿,主要是:

1
2
当前登录账号  登入者的来源主机名=(可切换的身份)  可下达的指令
root ALL=(ALL) ALL
  • 当前登录账号:以哪个账号执行 sudo 命令。
  • 登入者的来源主机名:“当前登录账号”由哪部主机联机到本 Linux 主机,这个设定值可以指定客户端计算机(信任的来源的意思)。ALL 表示任何主机(包括网络主机)。
  • (可切换的身份):要切换至的目标账号。
  • 可下达的指令:切换到目标账号之后,指定可以执行的命令有哪些。这个命令的路径必须以绝对路径进行指定。如 /usr/bin/cat

简单介绍 /etc/sudoers 给用户指定简单权限的操作:

  1. 给某个用户指定权限。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@centos-7 ~]# visudo
    ...
    root ALL=(ALL) ALL // root 账号可以切换至任何用户,执行任何命令。
    alvin ALL=(testuser) NOPASSWD: ALL // alvin 账号可以切换至 testuser 账号,
    // 切换时不需要再输入 alvin 的密码。切换之后可以执行 testuser 账号的所有命令。
    alvin ALL=(michael) NOPASSWD: /usr/bin/telnet// alvin 账号可以切换至 michael 账号,
    // 切换后仅能执行 telnet 命令。
    alvin ALL=(maria) !/usr/bin/telnet // alvin 账号可以切换至 maria 账号,
    // 但是不可以执行 telnet 命令
  2. 给某个组指定权限。组名之前加上 %,以区别用户名。

    1
    2
    3
    4
    5
    [root@centos-7 ~]# visudo
    ...
    %wheel ALL=(ALL) ALL // wheel 组的任何用户可以切换至任何用户,执行任何命令。
    %alvin ALL=(ALL) NOPASSWD:ALL // alvin 组的任何用户可以切换至任何用户,执行任何命令。
    // 切换时无需再输入自己的密码

    CentOS 7.x 之后,wheel 组就默认开放了如上的权限。

  3. 使用别名。

    如果是批量给多个用户指定相关的权限,可以通过别名的方式进行操作,实际上也就是通过定义变量来进行操作,而定义变量类型的关键字是 /etc/sudoers 文件预设好的,直接用即可。主要有以下四个变量类型关键字(定义的变量名必须大写):

    • Host_Alias:定义主机别名。
    • User_Alias: 用户别名,别名成员可以是用户,用户组(前面要加 % 号)
    • Runas_Alias:这个别名指定的是“目标用户”, 即 sudo 允许切换至的用户
    • Cmnd_Alias:命令别名
    1
    2
    3
    4
    [root@centos-7 ~]# visudo
    User_Alias PASSWDUSER = alvin,cater,god
    Cmnd_Alias PASSWDCOM = !/usr/bin/passwd, /usr/bin/passwd [A-Za-z]*, !/usr/bin/passwd root
    PASSWDUSER ALL=(root) PASSWDCOM

    上述指令说明:指定 PASSWDUSER 表示的三个用户,可以以 root 身份修改其他用户的密码。但是要注意,禁用了 passwdpasswd root 以防止这三个账号修改 root 的命令。

    备注:变量名必须使用大写英文字母,不能包含小写英文、数字、特殊符号(包括 _)等。

    举例:

    设置 alvinsudo 权限。

    1
    2
    3
    4
    [root@centos-7 ~]# visudo
    ...
    alvin ALL=(root) NOPASSWD: !/usr/bin/passwd, /usr/bin/passwd [A-Za-z]*, !/usr/bin/passwd root
    ...

    使用 alvin 账号修改 nana(uid=1002) 账号的密码。但是不能修改 root 的密码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [alvin@centos-7 ~]$ sudo -u root passwd nana
    Changing password for user nana.
    New password:
    BAD PASSWORD: The password is shorter than 8 characters
    Retype new password:
    passwd: all authentication tokens updated successfully.

    [alvin@centos-7 ~]$ sudo -u root passwd root
    Sorry, user alvin is not allowed to execute '/bin/passwd root' as root on centos-7.9.shared.
  4. 时间间隔。

    在不使用 NOPASSWD 指定时,用户(非 root)执行 sudo 命令必须输入自己的密码,但是在 5 分钟内该用户再次使用 sudo 命令时无需再次输入自己的密码。类似于锁屏的超时时间。

  5. sudo 搭配 su

    实际上就是给指定用户权限,让这些用户输入自己的密码即可提权到 root,这些用户就无需每次都是用 sudo 来执行命令了。

    1
    2
    User_Alias ADMINS = prol, pro2, pro3, myuser1
    ADMINS ALL=(root) /bin/su -

    验证:

    nana 用户授权。

    1
    2
    3
    4
    [root@centos-7 ~]# visudo
    ...
    nana ALL=(root) /usr/bin/su -
    ...

    nana 用户提权并切换至 root 账号。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [nana@centos-7 ~]$ sudo su -

    We trust you have received the usual lecture from the local System
    Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

    [sudo] password for nana:
    Last login: Thu Jul 27 23:19:19 CST 2023 on pts/0
    Last failed login: Fri Jul 28 21:00:19 CST 2023 on pts/0
    There was 1 failed login attempt since the last successful login.

    [root@centos-7 ~]# id
    uid=0(root) gid=0(root) groups=0(root)
    context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023