在单用户环境中,chmod 755 往往足以解决大部分权限问题;到了服务器上的多人协作场景,它却很容易失效。例如:
- 开发组成员都能进入发布目录,但甲创建的文件,乙不能修改;
- 为了临时排障执行
chmod -R 777,结果任何本地用户都能篡改内容; - 目录已经设置为组可写,新建文件却仍然属于创建者的主组;
- 给用户配置了 ACL,程序依然报告
Permission denied; - 公共上传目录允许写入后,用户可以删除其他人的文件。
这些现象并不矛盾。Linux 权限判断涉及文件所有者、所属组、传统权限位、特殊权限位、ACL、创建进程的 umask,还可能受到只读挂载、SELinux 等机制影响。可靠的配置方法不是反复尝试权限数字,而是先把业务规则写清楚,再选择对应机制。
本文设计两类常见目录:
- 团队协作目录:同组成员可以创建和修改内容,新内容自动继承协作组。
- 公共投递目录:多人可以上传文件,但普通用户不能删除其他人的文件。
以下命令以常见 Linux 用户空间工具为例,需要管理员权限。不同发行版的软件包名称可能不同,修改生产目录前应先在临时目录验证。
先理解权限判断的关键规则
文件与目录的 rwx 含义不同
对普通文件而言:
r:读取文件内容;w:修改或截断文件内容;x:把文件作为程序执行,但能否成功还取决于文件格式、解释器、挂载参数等条件。
对目录而言:
r:读取目录项,也就是列出其中的名称;w:新增、删除或重命名目录项;x:穿越目录,并按名称访问其中对象。
因此,目录只有 r 而没有 x 时,即使能看到名称,也未必能读取文件属性或内容。目录具有 w+x 时,用户通常可以删除其中的文件,删除动作修改的是父目录的目录项,并不要求用户拥有目标文件的写权限。粘滞位可以进一步限制这种删除行为。
路径访问还要求每一级父目录都可穿越。检查深层路径时,namei 比只看末级目录更有效:
bash
namei -l /srv/team/releases/app.jar
权限三元组不是逐项叠加
传统权限分为 owner、group、other 三类。内核会根据进程身份选择匹配类别,并不是把三类权限全部合并。例如,一个用户正好是文件所有者时,使用的是 owner 位;即使文件的 group 位更宽,也不能依靠 group 位补足 owner 位。
进程实际使用的身份也未必等于登录用户。服务通常以专用账号运行。排查前应确认执行上下文:
bash
id
ps -o user,group,egroup,comm -p "$(pgrep -n my-service)"
umask 只能移除创建权限
应用创建文件时会传入期望模式,常见文件模式为 0666,目录模式为 0777。在没有默认 ACL 等额外因素时,最终权限可近似理解为:
text
最终模式 = 请求模式 & ~umask
例如 umask 002 下,按 0666 创建的普通文件通常得到 0664,按 0777 创建的目录通常得到 0775。之所以普通文件默认没有执行位,是因为应用通常没有请求执行位,而不是 umask 自动识别了文件类型。
umask 不会给文件增加权限,也不会修复已有文件。程序还可以在创建后显式调用 chmod,因此不能仅凭交互式 Shell 的 umask 推断服务进程结果。
实战一:建立团队协作目录
假设协作组为 release,用户 alice 和 bob 都需要维护 /srv/team/releases。
1. 创建组并加入用户
bash
sudo groupadd release
sudo usermod -aG release alice
sudo usermod -aG release bob
usermod -aG 中的 -a 不能遗漏,否则可能覆盖用户已有的附加组。组变更一般不会自动进入已存在的登录会话,用户应重新登录。临时测试可以使用:
bash
sudo -iu alice id
sudo -iu bob id
不要把 newgrp 当作所有长期运行服务的修复方案;服务应由其管理器重新启动,使进程获得更新后的组列表。
2. 设置所有权和 setgid
bash
sudo install -d -o root -g release -m 2775 /srv/team/releases
模式中的前导 2 表示目录 setgid。它的核心作用是让目录中新建对象继承父目录的所属组,而不是创建者当时的主组。这解决了"成员属于同一协作组,但新文件落入各自主组"的问题。
检查结果:
bash
stat -c '%A %a %U:%G %n' /srv/team/releases
权限字符串中组执行位的位置通常显示为 s。如果显示大写 S,表示设置了 setgid,但组执行位没有开启;对需要穿越的协作目录来说,这通常不是预期配置。
3. 用默认 ACL 固化继承权限
仅设置 setgid 只能保证组归属,不能保证组始终可写。用户若使用较严格的创建策略,新文件仍可能缺少组写权限。可为目录设置访问 ACL 和默认 ACL:
bash
sudo setfacl -m u::rwx,g::rwx,o::rx,m::rwx /srv/team/releases
sudo setfacl -d -m u::rwx,g::rwx,o::rx,m::rwx /srv/team/releases
第一条约束目录当前访问权限,第二条定义子对象继承规则。默认 ACL 只影响以后创建的对象,不会自动改造历史内容。
查看 ACL:
bash
getfacl -p /srv/team/releases
ACL 中的 mask 是命名用户、命名组和所属组条目的有效权限上限。某个条目写着 rwx,但 mask 只有 r-x 时,其有效权限仍然没有写权限。getfacl 通常会标出 effective 结果。
默认 ACL 与创建模式共同参与新对象权限计算,其结果不能简单概括为"先套默认 ACL,再直接套 Shell 的 umask"。可靠做法是按目标程序的真实创建方式执行验证,而不是只计算一个权限数字。
4. 验证跨用户修改
bash
sudo -u alice sh -c 'echo first > /srv/team/releases/demo.txt'
sudo -u bob sh -c 'echo second >> /srv/team/releases/demo.txt'
stat -c '%A %a %U:%G %n' /srv/team/releases/demo.txt
getfacl -p /srv/team/releases/demo.txt
验证时至少确认三件事:文件组是 release、组有效权限包含写入、另一成员确实能追加内容。不要只检查 ls -l,因为带 ACL 的文件会显示 +,有效权限还需结合 ACL mask 判断。
如果目录已有内容,可在确认影响范围后修正组和目录 setgid:
bash
sudo chgrp -R release /srv/team/releases
sudo find /srv/team/releases -type d -exec chmod g+rws {} +
sudo find /srv/team/releases -type f -exec chmod g+rw {} +
不要对整棵目录统一执行 chmod -R 2775。这样会把普通数据文件也加上执行位,并且不能针对文件与目录表达不同策略。
实战二:建立只能删除自己文件的投递目录
公共投递区与团队协作区的安全目标不同。若所有人都能在父目录中写入,普通目录规则通常也允许他们删除或重命名其他人的文件。对此应使用粘滞位。
bash
sudo install -d -o root -g root -m 1777 /srv/dropbox
前导 1 表示 sticky bit。启用后,在满足常规目录权限的前提下,文件删除和重命名通常进一步限定为文件所有者、目录所有者或具备相应特权的进程。系统的 /tmp 常采用这一模式。
可以用两个临时用户验证语义:
bash
sudo -u alice sh -c 'echo data > /srv/dropbox/alice.txt'
sudo -u bob rm /srv/dropbox/alice.txt
第二条命令应失败。如果成功,应检查目录是否确实显示为 drwxrwxrwt,以及测试进程是否拥有绕过普通权限检查的特权。
需要注意,1777 不是"上传后不可读取"。other 的 r-x 仍可能允许其他用户列出名称并读取权限允许的文件。若投递内容敏感,应采用应用接收上传、隔离每个用户目录或更严格 ACL,而不是把公共可写目录当作保密存储。
服务进程中的落地方式
为交互式 Shell 设置 umask,不会自动影响由 systemd 启动的服务。若服务需要在协作目录创建组可写文件,可在单元中显式声明:
ini
[Service]
User=deploy
Group=deploy
SupplementaryGroups=release
UMask=0002
ExecStart=/usr/local/bin/release-worker
修改后执行:
bash
sudo systemctl daemon-reload
sudo systemctl restart release-worker
sudo systemctl show release-worker -p User -p Group -p SupplementaryGroups -p UMask
具体服务若会自行调用 chmod、创建临时文件后再移动,或者运行在容器与网络文件系统中,最终行为可能不同,应通过该服务实际生成的文件验证。
常见问题与排查顺序
权限看起来正确,为什么仍然拒绝访问?
按以下顺序检查,通常比继续执行 chmod 更快:
bash
namei -l /srv/team/releases/demo.txt
id alice
getfacl -p /srv/team/releases/demo.txt
findmnt -T /srv/team/releases/demo.txt
重点确认父目录 x 权限、进程实际用户与附加组、ACL mask,以及文件系统是否只读或带有其他限制。启用 SELinux 的系统还应检查安全上下文和审计日志。传统权限通过不代表强制访问控制策略一定允许操作。
为什么 setgid 没让新文件组可写?
setgid 负责组继承,不负责自动增加 g+w。需要同时管理默认 ACL、服务的创建模式或 umask。这几个机制解决的是不同问题。
为什么 chmod 后 ACL 权限变了?
对带扩展 ACL 的文件执行 chmod,可能同步调整 ACL mask,使命名用户或组的有效权限发生变化。变更后应重新运行 getfacl,不能假定命名 ACL 条目保持原有效权限。
为什么 root 创建的文件其他成员仍不能修改?
如果目标目录没有默认 ACL,而管理脚本又使用严格 umask,文件可能继承了正确的组,却没有组写权限。另一种情况是脚本在临时目录创建文件,再通过复制或移动发布;最终属性取决于工具行为、源目录和是否跨文件系统,必须针对真实发布流程测试。
可以直接使用 777 吗?
777 只是在传统权限层面允许所有本地用户读、写和穿越,既不能保证正确的组继承,也不能防止互删,更不能解决 ACL mask、只读挂载或 SELinux 拒绝。团队目录通常应使用受控组配合 2775 和默认 ACL;公共临时目录若确实需要所有人写入,至少应评估 1777 及数据保密要求。
总结
Linux 共享目录应从访问模型出发配置:rwx 决定基础访问,setgid 解决所属组继承,默认 ACL 固化新对象权限,umask 限制进程请求的创建权限,粘滞位约束公共目录中的删除与重命名。
落地时应避免递归设置同一个数字,也不要只看末级文件。先检查完整路径、真实进程身份和 ACL 有效权限,再检查挂载与强制访问控制;最后用两个低权限账号执行真实的创建、修改和删除测试。权限配置只有经过行为验证,才算形成可维护的安全边界。