权限、属主与 ACL

Linux 权限问题的根源通常只有三种:权限位算错了、属主/属组不对、或者父目录缺了执行位。这一节从读懂 ls -l 的第一列开始,到 chmod / chown / ACL / umask / 特殊权限位,每条都给出可复制的写法。

共 20 条命令 · 最近更新

先读懂权限

ls -l 第一列的 10 个字符就是全部信息,看懂它再谈修改。

ls -l

长格式输出的第一列形如 -rwxr-xr-- 或 drwxr-xr-x:第 1 位是文件类型(- 普通文件、d 目录、l 软链接、c 字符设备、b 块设备、s socket、p 命名管道),后面 9 位每 3 位一组,依次是属主、属组、其他人的 r(读 4)w(写 2)x(执行 1)权限。

ls -l /etc/passwd
ls -ld /var/www

目录上的 r 是「能列出文件名」,x 是「能进入并访问里面已知名字的文件」;只有 r 没有 x 时,ls 会看到文件名但 stat 会失败。

对目录而言 r / w / x 的含义

目录的读位允许列出条目,写位允许在其中创建、删除、重命名条目,执行位允许进入该目录并访问其中的文件。三者互相独立,缺哪个就会在哪一类操作上报 Permission denied。

ls -ld /var/log /var/log/nginx

「文件权限没问题但访问不了」几乎总是某个父目录缺少 x 位。排查时用 namei -l 从根目录一路看下来。

namei -l

逐级显示路径上每一层目录的权限和属主,专门用来排查「明明文件有权限却打不开」的问题。

namei -l /var/www/site/index.html

id / groups

显示当前用户的 UID、GID 和所属的全部组。判断「我到底在哪些组里」比看 /etc/group 更直接。

id
id deploy
groups

把用户加入新组(usermod -aG)后,该用户已经登录的会话不会自动获得新组,需要重新登录或者用 newgrp 开一个临时会话。

chmod:修改权限

数字写法和符号写法各有适用场景:批量设置用数字,微调某一组用符号。

chmod 数字写法

三位八进制数分别对应属主、属组、其他人,把 r=4、w=2、x=1 相加:7=rwx、6=rw-、5=r-x、4=r--、0=---。习惯用法是文件 644、脚本 755、目录 755。

chmod 644 index.html
chmod 755 deploy.sh
chmod 600 ~/.ssh/id_ed25519
chmod -R 750 /srv/app

chmod -R 会同时作用到目录和文件,把文件也变成 755(带执行位)通常不是本意。需要区分时用 find 配合 -type 分别设置。

chmod 符号写法

格式是 [ugoa][+-=][rwxXst]。u 属主、g 属组、o 其他人、a 所有人;+ 增加、- 去掉、= 精确设置。X 表示「仅当它是目录或已有执行位时才加 x」,批量给目录树加执行位时非常有用。

chmod +x script.sh
chmod go-w /etc/app.conf
chmod -R a+rX /var/www/site
chmod u=rw,g=r,o= secret.key

不带 ugoa 前缀时,chmod 受 umask 影响(x 不会被 umask 屏蔽)。给别人的文件加执行位直接写 chmod +x,语义明确。

chown / chgrp

chown 改属主,冒号后同时指定属组(chown user:group);chgrp 只改属组。-R 递归,-h 作用于软链接本身而不是它指向的文件(-R 时只有 -h 能阻止跟随链接)。

chown -R www-data:www-data /var/www
chown deploy: /srv/app
chgrp -R developers /srv/shared

只有 root 能把文件送给别的用户(普通用户不能 chown 给别人,即使自己是当前属主)。

umask

控制新建文件和目录的默认权限。系统按「0666 - umask」得到新文件权限,「0777 - umask」得到新目录权限。umask 022 产生 644/755,umask 027 产生 640/750。

umask
umask 027
umask -S

umask 是每个进程自己的属性,在终端里改只影响当前会话。永久生效要写进 /etc/profile、~/.bashrc 或 systemd 服务的 UMask=。

特殊权限位与文件属性

SUID / SGID / Sticky 是位掩码里最容易忽略、也最容易造成安全问题的三位。

三种特殊权限位对照
位数字ls 上的显示作用
SUID4000属主执行位变成 s(如 -rwsr-xr-x)执行该程序时以文件属主的身份运行。passwd 就是靠它让普通用户改密码。
SGID2000属组执行位变成 s对文件:以文件属组的身份运行;对目录:目录里新建的文件自动继承该目录的属组(团队共享目录常用)。
Sticky1000其他人执行位变成 t(drwxrwxrwt)只对目录有效:目录里的文件只有属主、目录属主或 root 能删除。/tmp 就是典型。

设置与查找特殊权限

用四位数字设置(如 4755、2775、1777),或用符号写法 u+s / g+s / +t。查找系统中的 SUID 文件是做安全巡检的常规动作。

chmod 2775 /srv/shared          # 目录:新文件继承属组
chmod 1777 /srv/upload          # 目录:只有属主能删自己的文件
sudo find / -perm -4000 -type f 2>/dev/null
sudo find / -perm -2000 -type f 2>/dev/null

不要给 shell 脚本设 SUID:内核会忽略脚本上的 SUID 位,看起来生效其实没有,反而容易产生误解。

chattr / lsattr

ext4 / xfs 等文件系统上的扩展属性。+i(immutable)让文件连 root 都不能修改或删除,常用于保护关键配置;+a(append only)只允许追加,适合日志。lsattr 查看当前属性。

sudo chattr +i /etc/resolv.conf
sudo chattr -i /etc/resolv.conf
lsattr /etc/resolv.conf

chattr +i 之后连 rm -f 和重定向写入都会报 Operation not permitted,排查「文件改不了」时别忘了检查这一项。

ACL:比 rwx 更细的授权

需要「额外某个用户可读写,但不改变属组」时,传统 rwx 就不够了,用 ACL。

getfacl

查看文件或目录的 ACL。所有文件都有基础 ACL(就是 rwx 那三位),额外条目会以 user:名字:权限、group:名字:权限 的形式列出。

getfacl /srv/shared
getfacl -R /srv/shared | head -50

ls -l 权限后面的那个 + 号,就表示该文件带有额外 ACL 条目。

setfacl -m

添加或修改 ACL 条目。-m 修改,-x 删除指定条目,-b 清除全部额外 ACL。u:用户名:权限 给单个用户授权,g:组名:权限 给组授权,o: 表示其他人。

sudo setfacl -m u:alice:rw /srv/shared/report.txt
sudo setfacl -m g:devs:rwx /srv/shared
sudo setfacl -x u:alice /srv/shared/report.txt
sudo setfacl -b /srv/shared/report.txt

setfacl -R -d

-R 递归应用到已有内容,-d 设置默认 ACL(只对目录有效,决定目录里**新建**文件继承哪些条目)。两者通常一起用,才能让新老文件权限一致。

sudo setfacl -R -m g:devs:rwx /srv/shared
sudo setfacl -R -d -m g:devs:rwx /srv/shared
getfacl /srv/shared

默认 ACL 不会顺带设置目录本身的权限位,新目录仍受 umask 影响;配合 chmod g+s 让新文件继承属组更稳妥。

ACL 的 mask 与有效权限

getfacl 输出里的 mask:: 是「除属主和其他人之外所有条目的权限上限」。effective: 注释显示条目被 mask 削减后的实际权限。改了权限却不生效,多半是被 mask 挡住了。

getfacl /srv/shared
sudo setfacl -m m::rwx /srv/shared

chmod 修改组权限位时,实际改的是 ACL mask,而不是真的动了属组权限。

提权与身份切换

能用 sudo 就不要一直开 root shell;能只给一条命令就不要给整台机器。

sudo

以其他用户(默认 root)身份执行一条命令。-u 指定用户,-i 启动目标用户的登录 shell,-l 列出当前用户被允许执行的命令,-k 立即让缓存的密码失效。

sudo systemctl restart nginx
sudo -u postgres psql
sudo -l
sudo -i

sudo 默认只缓存密码几分钟。写脚本时不要依赖交互式输入密码,应改用 NOPASSWD 或者干脆让脚本以 root 运行。

su / su -

切换用户。su - 会加载目标用户的登录环境(PATH、家目录、环境变量),不加 - 则保留当前环境。不带用户名默认切到 root。

su - deploy
su -
sudo su - postgres

su 需要知道目标用户的密码,sudo 需要自己的密码和授权。服务器上优先用 sudo,便于审计。

visudo

安全地编辑 /etc/sudoers:带语法检查,写错不会让你彻底失去 sudo 权限。推荐的授权写法是「用户名 主机=(运行身份) 命令」。

sudo visudo
sudo visudo -f /etc/sudoers.d/deploy

永远不要用普通编辑器直接改 /etc/sudoers,一个语法错误就会让所有人无法提权。

usermod -aG

把已有用户追加到附加组(-aG 缺一不可:只用 -G 会覆盖原有附加组)。改完后用户需要重新登录才生效。

sudo usermod -aG docker deploy
sudo usermod -aG sudo deploy
id deploy

把用户加进 docker 组等价于给了 root 权限,这一点在授权前要清楚。

权限问题怎么查

遇到 Permission denied,按下面的顺序排一遍,通常三分钟内能定位。

SELinux 基本开关与上下文

getenforce 查看状态(Enforcing / Permissive / Disabled),setenforce 0 临时切到宽容模式(重启失效),restorecon 恢复文件默认上下文,chcon 手动指定上下文,semanage fcontext 做持久化规则。

getenforce
sudo setenforce 0
sudo restorecon -Rv /var/www
sudo ls -Z /var/www/html

生产环境不要长期把 SELinux 关掉;正确做法是用 semanage fcontext 配好上下文,而不是一关了之。

getcap / setcap

文件能力(capabilities)让程序不需要 SUID 就能获得某一项特权,比如让普通程序绑定 1024 以下端口。getcap 查看,setcap 设置,-r 移除。

getcap -r /usr/bin 2>/dev/null
sudo setcap cap_net_bind_service=+ep /usr/local/bin/app
sudo setcap -r /usr/local/bin/app

给二进制文件加 cap 后,如果程序被替换或升级,能力会丢失或落到新文件上,升级脚本里要注意重设。