大数跨境

Linux 权限问题排查:为什么明明有文件却访问不了?

Linux 权限问题排查:为什么明明有文件却访问不了? 红帽Linux认证
2026-07-10
3
导读:Linux 权限问题排查:为什么明明有文件却访问不了?

Linux 权限问题排查:为什么明明有文件却访问不了?

一、问题背景

“为什么明明有文件却访问不了”这类权限工单,几乎是 Linux 运维里最常见、也最容易被新人低估的一个领域。它的常见表现:

  • Permission denied
    ,文件在那但 cat 不了
  • Bash: /usr/local/bin/xxx: Permission denied
    ,明明已经加了 chmod +x
  • 同一个目录,root 能进,普通用户进不去
  • 服务启动成功,但读不到配置文件,日志里偶尔出现 Read-only file system
  • 容器内 ls 看到文件,但应用报 EACCES
  • 应用前几分钟能跑,几分钟后突然报错(NFS / 文件被改为 chmod 000 等)

它的根因往往不在“chmod 一个数字 644”,而在四个层面之间的相互作用:

  • 标准 UNIX 权限位(owner/group/other + rwx)
  • 文件系统 ACL 与扩展属性
  • Linux 安全模块(SELinux / AppArmor)
  • 进程身份与命名空间(uid/gid、capability、容器 namespace)

如果只看一层,常常查不出真正的根因。本文用一条完整的排查主线,逐步把这四层都串起来。让读者遇到类似工单时,能按照这个流程稳定收敛并定位。

二、适用场景

下面这些场景明确在本文覆盖范围内:

  1. cat / vi / less / cp / mv / bash / sh
     报 Permission denied
  2. Web / 数据库等服务不能读取配置文件、写入日志目录、创建 socket 文件。
  3. 容器内应用 EACCESpermission denied,宿主机上看着正常。
  4. SELinux 触发 avc: denied 日志。
  5. AppArmor 触发 DENIED 操作。
  6. NFS、SMB 挂载点写入报错,能读不能写。
  7. setuid
     程序在某些环境运行不正常。
  8. 二进制无法执行(典型错误:cannot enable executable stack、缺少 x 权限、挂载了 noexec)。
  9. systemd 服务启动后拿到错误的 uid / gid。
  10. Operation not permitted
    ,特别是 mount、chroot、capability 类。

不适用场景(不在本文深挖):

  • 业务代码层的鉴权(OAuth / RBAC 角色绑定等),不在系统权限讨论范围。
  • 文件系统损坏、磁盘 IO 错误、ACL 模块支持未编译等更偏“文件系统 / kernel 模块”的故障。

三、核心知识点

下面分模块讲权限相关的关键概念。每一块都是后面排查命令的依据,看命令时可以回查这些知识点。

1. UNIX 经典权限三元组

每个文件有三组权限位:owner / group / otherls -la 看到的 rwxr-xr-- 就是这三组。

数字表示:

  • r = 4
  • w = 2
  • x = 1

例如 rwxr-xr-- 即 754

文件 vs 目录的 rwx 含义差异:

权限
文件含义
目录含义
r
能读文件内容
能列出目录内容(ls
w
能修改文件内容
能在目录里增删改文件
x
能作为可执行文件运行
能 cd 进入目录 / 访问目录内文件 inode

关键点

  • 目录必须同时有 r 和 x,只有 r 看不到内容但能拿 inode 信息(某些系统行为不同)。
  • 缺 xcd 进不去;缺 rls 列不出但能 cd
  • 父目录没有 x,即使文件本身是 0777 也打不开。

例:

  
  
  
drwxr-x---  root  www  /var/www/html

/var/www/html 下属文件如果是 www 用户能改,但其他用户连 cd 都做不到。

2. 所有者与属组

文件有 owner 与 group,分别对应一个用户 / 组。访问权限匹配逻辑:

  • 如果当前进程 uid 与文件 owner 相同:套 owner 位的权限。
  • 否则如果进程的 primary group 或 supplementary groups 命中文件 group:套 group 位。
  • 否则套 other 位。

关键点

  • 进程的 supplementary group 列表很长,id 显示的全部都可能参与匹配,不仅仅是 primary。
  • chgrp
     是只改 group,不动 owner;chown 改 owner 后,group 通常不变。
  • chown user:group file
     是常用写法;如果 group 留空(chown user: file),group 会保持不变(取决于实现)。

3. setuid / setgid / sticky bit

三个特殊位,影响权限匹配:

ls 显示位
含义
setuid
-rws------
 文件 owner 位的 s
执行时进程 euid 取文件 owner
setgid
-rwxrws---
 文件 group 位的 s
执行时进程 egid 取文件 group
sticky
drwxrwxrwt
 目录 other 位的 t
目录内只有 owner / root 能删自己文件

典型场景:

  • /usr/bin/passwd
     是 setuid root,能写 /etc/shadow
  • /tmp
     是 sticky,普通用户不能删别人的 tmp 文件。
  • ssh-agent
     等会用 setgid 切换到特定组。

关键点

  • 挂载了 nosuid 时,setuid / setgid 会被忽略。
  • 没有 x 位时 s 显示为大写 S,表示“设了特殊位但无效”。
  • 一些发行版默认屏蔽 setuid 程序(如 mount -o nosuid)。

4. ACL(POSIX ACL)

Linux 标准的 ugo 权限之外,可挂载 acl 选项后,用 getfacl / setfacl 设置更多访问控制。

例如:

  
  
  
# user::rw-
# user:lisi:rw-
# group::r--
# mask::rw-
# other::---
  • user:lisi:rw-
     给了 lisi 特殊权限。
  • mask
     是“有效 mask”,用于限制所有 named user 与 group 的最高权限。

关键点

  • 修改文件后没 chmod 改变 mask,会发现 chmod 600 后,named user 访问也不通了,因为 mask 被锁死。
  • setfacl -b
     清空所有扩展 ACL,不只是改 mask。
  • 备份工具(tarrsync)要带 --acls 才能正确保留 ACL。
  • 一些场景(比如 NFS)在不支持 ACL 时静默失败,需要 mount -o noacl 或 -o acl

5. SELinux(CentOS / RHEL / Fedora 默认)

SELinux 是 LSM 框架(Linux Security Module),基于策略对每个操作做额外校验。即使 rwx 全部通过,仍然可能因为 SELinux 拒绝。

基本概念:

  • enforcing
    :拒绝并审计。
  • permissive
    :只审计不拒绝(适合排错)。
  • disabled
    :彻底关闭(重启生效,不能直接切到 disabled)。

核心字段:

  • 进程的 domain(httpd_tsshd_tunconfined_t 等)。
  • 文件的 type(httpd_sys_content_ttmp_tdefault_t 等)。
  • 通过 te rules 规则允许特定 domain → type 访问。

常见错误关键字:

  • avc: denied { read } for pid=1234 comm="nginx" ...
     在 audit.log 或 /var/log/messages
  • 应用报 Permission denied,但 ls -Z 看上下文是合理的,例如 /var/www/html 被标成 admin_home_t

排错策略:

  1. getenforce
     看模式。
  2. ausearch -m AVC -ts recent
     / journalctl -t setroubleshoot 找拒绝记录。
  3. audit2why < /var/log/audit/audit.log
     看 SELinux 为何拒绝。
  4. audit2allow
     自动生成允许规则(生产慎用)。
  5. 长期解:调整目录 context(semanage fcontext + restorecon)。

6. AppArmor(Ubuntu / SUSE 默认)

AppArmor 是另一种 LSM,机制与 SELinux 不同:用路径而不是 ino 安全上下文来定义策略。

排错命令:

  • aa-status
    :当前加载的策略。
  • cat /var/log/audit/audit.log | grep -i apparmor
  • aa-logprof
    :基于日志自动生成建议规则。
  • apparmor_parser -R /etc/apparmor.d/<profile>
    :临时禁用某 profile。

7. 文件系统挂载选项

挂载选项对权限影响很大:

选项
影响
ro
只读,任何写都会 Read-only file system
noexec
文件不能执行(如 /tmp 默认)
nosuid
setuid / setgid 失效
nodev
不解释设备文件
relatime
 / atime
与权限无关,性能问题
acl
 / noacl
是否启用 POSIX ACL
usrquota
 / grpquota
启用磁盘配额
xattr
是否启用扩展属性

排查时 mount 命令直接看,再 /proc/mounts/etc/fstab/proc/self/mountinfo

8. 进程身份:uid / gid / groups / euid / egid

进程跑起来时有一组身份字段:

  • uid
    gid:进程启动时真实身份。
  • euid
    egid:实际权限判断的身份(setuid 后会变)。
  • groups
    :supplementary group 列表。
  • cap
    :能力集,用于精细控制 root 能力。

工具:

  • id
    :当前 shell 身份。
  • cat /proc/<pid>/status | grep -E '^(Uid|Gid|Groups|Cap)'
    :进程全部身份信息。

sudo -u user id:以 user 身份校验。

9. Linux Capability(root 能力分片)

CAP_DAC_OVERRIDE:绕过文件读 / 写 / 执行权限检查。CAP_CHOWN:绕过 chown 检查。CAP_NET_BIND_SERVICE:绑定 < 1024 端口。CAP_SYS_ADMIN:一堆管理员能力。

排查 setuid 程序或服务时,getcap /usr/bin/xxx 看授予了哪些能力。

10. 容器场景:UID 映射与 namespace

容器内的 root(UID 0)与宿主机的 root 不一定是同一身份。常见做法:

  • 默认映射:容器内 0 ↔ 宿主机大 UID(实际安全策略下)。
  • user_namespaces
    :把容器内 UID 1 映射到宿主机普通用户。

容器内文件出现在宿主机时,权限会显示成宿主机 UID。例如容器内 chmod 666 /data/file,宿主机看到的是宿主机 UID chmod 666

排查容器文件权限时:

  • docker exec -u <uid> <container> ls -la /path
  • 镜像构建时是否通过 USER 切了普通用户
  • 是否使用了 read_only: true / tmpfs 挂载

四、整体排查思路

按从最外层到最内层的顺序排查:

  1. 明确报错的具体命令、用户、文件、上下文。
  2. 看文件和父目录的标准权限位、owner、group。
  3. 检查进程实际用户和 supplementary groups。
  4. 检查 ACL、扩展属性。
  5. 检查挂载选项(特别是 ro / nosuid / noexec / acl)。
  6. 检查 SELinux(如果启用)。
  7. 检查 AppArmor(如果启用)。
  8. 用 strace 取内核证据。
  9. 拿 audit 日志查被拒绝的系统调用。
  10. 验证修复 + 准备回滚。

具体执行每一步时都有明确判断逻辑,进步骤章节会展开。

五、实战步骤

下面从“现场拿到一个报错”开始,每一步给出命令、判断、动作。

步骤 0:明确报错上下文

目的:把问题描述压到一句最小可复现的句子:who: 用户; what: 操作; where: 路径; what happens: 报错

收集的命令

  
  
  
bash
whoami
id
ls -la /path/to/file
ls -la /path/to/dir
# 触发报错
sudo -u user cat /path/to/file
sudo -u user bash -lc 'cd /dir && ls'

预期输出:得到报错原文(错误信息、错误码、关联错误号)。

异常表现

  • 错信息中没有“Permission denied”,可能是不同层的问题(路径不存在、磁盘只读、capability 缺失)。
  • 多个用户报错,可能不是同一根因,需要样本分流。

下一步动作:进入步骤 1。

步骤 1:基础属性三件套

目的:用最少命令看清 owner、group、permission、type、modification、size。

命令

  
  
  
bash
ls -la <path>
stat <path>
file <path>      # 文本 / 二进制 / 软链 / 块设备

判断逻辑

  • 文件 owner 是 root,用户不是 rootother 位决定。
  • 是软链接:ls -la 看链指哪里,stat 看的是目标文件。
  • mount
     卷下文件权限位看起来对但仍然 Permission denied:考虑 ACL、mask、SELinux。
  • 文件类型是 block / char device:可能是 nodev 阻止。

下一步动作:进入步骤 2。

步骤 2:查看完整权限数字与特殊位

目的:避免被 ls 显示迷惑(比如误把大写的 S 当 s),一定要看准 setuid / setgid / sticky。

命令

  
  
  
bash
stat -c '%a %A %n' <path>
# %a 八进制权限
# %A 字符权限(含特殊位)

判断逻辑

  • 0777 -rwxrwxrwx
     没有特殊位,问题不在 setuid。
  • 4755 -rwsr-xr-x
     是 setuid 程序,执行时拿到 owner 身份。
  • 看到大写 S:特殊位有但 x 没设,特意去 chmod u+x 才会让 s 有效。
  • 看到 drwxrwxrwt:是 sticky,目录里只有 owner 能删自己的文件。

下一步动作:进入步骤 3。

步骤 3:判断进程身份与组的匹配

目的:搞清楚“谁去访问这个文件”。同样的 7xx 权限,进程身份不一样结果不一样。

命令

  
  
  
bash
# 当前 shell
id
# 服务进程身份
ps -eo pid,user,group,comm | grep -E 'nginx|mysql|java'
cat /proc/<pid>/status | grep -E '^(Uid|Gid|Groups)'

# 复现:以服务用户去访问
sudo -u <user> -- bash -lc 'id && cat /path/to/file'

判断逻辑

  • 如果 id 看到的 uid 不等于文件 owner,且 group 不在 supplementary groups 里,就只能按 other 匹配。
  • supplementary groups
     一般从 /etc/group 派生,可查 getent group <group>
  • 某进程以 root 跑,看似什么都能做,但 SELinux 会拦截,这就是“root 也会被拒绝”的常见原因。

下一步动作:进入步骤 4。

步骤 4:ACL 与扩展属性

目的:标准 ugo 权限之外,ACL 与 xattr 会影响访问。

命令

  
  
  
bash
getfacl /path/to/file
getfattr -d /path/to/file
# 看看 mask 是否锁住了有效权限
getfacl /path/to/file | grep -E '^# (effective|mask)'

判断逻辑

  • 用户在 ACL 中单独给了权限,但 mask 是只读:实际权限被截断。
  • setfacl -m u:user:rw
     后用 chmod 600,会把 mask 同步成 600,原来的 named entry 实际可用权限变成最小值。
  • noacl
     挂载下,setfacl 会报“Operation not supported”,getfacl 只显示 classic ugo。

下一步动作:进入步骤 5。

步骤 5:挂载选项检查

目的:避开“权限位看上去对,但因为挂载选项而被拦截”的陷阱。

命令

  
  
  
bash
mount | grep $(df <path> | tail -1 | awk '{print $1}')
cat /proc/self/mounts
cat /etc/fstab | grep -E '^[^#]'
cat /proc/self/mountinfo | grep <path>

判断逻辑

  • 文件落在 ro 挂载卷上:写操作必失败(Read-only file system),权限位无关。
  • noexec
     阻止执行 / sh 脚本(脚本 #!/bin/bash 第一行也无法直接 #!)。
  • 容器内的 /tmp 是 nosuid,nodev,noexec:在 /tmp 里放脚本并 chmod +x 不能直接执行。
  • 用户空间 mount 信息受 mount_namespaces 影响,容器场景下用 cat /proc/1/mountinfo 而不是宿主。

下一步动作:进入步骤 6。

步骤 6:SELinux 检查

目的:确认问题不是 LSM 拦截。

命令

  
  
  
bash
getenforce
sestatus
ls -Z /path/to/file
ps -Z -p <pid> | head
# 触发报错
sudo -u user cat /path/to/file
# 看 log
journalctl -t setroubleshoot
ausearch -m AVC -ts recent

判断逻辑

  • 模式是 enforcing,且 audit.log 有 avc: denied { read } 相关记录,根因就在 SELinux。
  • 模式是 permissive,日志依然提示 denied:表示策略没让这个访问通过,但没真正阻止,临时把策略改成 enforcing 后才会影响业务。
  • 文件 context 是 admin_home_ttmp_t 等非预期值:context 不对,进程 domain 没权限读。

下一步动作:进入步骤 7。

步骤 7:AppArmor 检查

目的:Ubuntu / 部分发行版默认开启 AppArmor。

命令

  
  
  
bash
aa-status
cat /var/log/audit/audit.log | grep 'apparmor.*DENIED'

判断逻辑

  • DENIED
     操作出现,profile 是 enforce 模式:它就是被拒的原因。
  • profile 是 complain:仅记录不阻止,可以切到 enforce 后看是否影响。

下一步动作:进入步骤 8。

步骤 8:strace 抓内核证据

目的:所有上层检查都无明确结论时,用 strace 拿到“具体哪个 syscall 返回什么错误”。

命令

  
  
  
bash
# -f 跟踪 fork 出来的子进程
# -e 只看特定系统调用,避免刷屏
# -tt 显示时间戳相对时间
strace -f -tt -e trace=openat,access,faccessat,stat,read,write,execve -p <pid> 2>&1 | head -200
# 单次执行:
strace -f -tt -e trace=openat,access cat /path/to/file 2>&1 | tail -20

判断逻辑

  • openat("/path/to/file", O_RDONLY) = -1 EACCES (Permission denied)
    :标准权限位被拒。
  • openat(...) = -1 ENOENT (No such file or directory)
    :路径不存在或父目录 x 位缺失。
  • openat(...) = -1 EROFS (Read-only file system)
    :挂载卷只读。
  • openat(...) = -1 EACCES ...
     且 audit.log 有 AVC:SELinux 拦截,strace 也会标注 SELinux 相关 errno。
  • stat(...) = -1 EACCES
    ,并且 parent dir 没有 x 位,进程甚至没法 stat 子条目。

下一步动作:进入步骤 9。

步骤 9:审计日志

目的:用 auditd 记录系统调用级事件,能查到业务应用自身不知道的内核拒绝。

命令

  
  
  
bash
# 启动 auditd,并 audit 文件访问
auditctl -w /path/to/file -p rwxa -k watch-file
auditctl -w /path/to/dir -p rwx -k watch-dir
# 等复现,再查
ausearch -k watch-file
ausearch -k watch-dir
# 通用:最近 AVC
ausearch -m AVC -ts recent

判断逻辑

  • audit log 中带 auid= 是登录用户 ID,便于做用户画像。
  • audit log 中带 uid= 是执行进程的实际 UID。
  • 看到 path=... 与 objtype=...(如 file、socket),确认拒绝对象。

下一步动作:进入步骤 10。

步骤 10:容器内权限问题

目的:容器内外 UID 不一致、namespace 不共享时,要进容器按容器视角看。

命令

  
  
  
bash
docker exec -it <container> bash
docker exec -u <uid> <container> bash
cat /proc/1/status | grep -E '^(Uid|Gid|Groups)'
mount | grep -E '/tmp|/var'
ls -laZ <path>

判断逻辑

  • docker exec -u 33 <container> bash
     失败:Passwd: u:33 is unknown to this system,容器里没这个 UID。
  • 容器里 getcap 显示有 capability 而宿主机看不到:capability 在 user namespace 被映射。
  • 容器内看到目录 drwxr-xr-x,但写入报错:可能是 docker 的 --read-only 或者上层卷是 ro
  • 容器使用 user: "1000:1000",宿主机需要 chown -R 1000:1000 才能在绑定挂载里写入。

下一步动作:步骤 11 给出修复 + 验证。

步骤 11:修复与回滚前的决定点

走到这一步通常已经定位到根因,根据不同根因有不同的修复动作。重点是“只改一个变量再验证”,不要一次性把多种修复混合。常见修复动作:

  • chmod 644 /path/to/file
    :权限位
  • chown user:group /path/to/file
    :owner/group
  • usermod -aG group user
    :补充组
  • setfacl -m u:user:r /path/to/file
    :ACL
  • mount -o remount,rw /mount_point
    :可写挂载
  • restorecon -Rv /path/to/dir
    :SELinux context 修复
  • setenforce 0
     临时切 permissive 验证
  • AppArmor:aa-complain /usr/sbin/nginx

每修一次都重复步骤 11 的验证:复现报错动作,确认不再是 Permission denied

风险提醒

  • chmod -R 777
    :会让任何用户都能改,包括不该动的人。
  • setenforce 0
    :临时禁用 LSM,可能让攻击面扩大,验证完务必恢复。
  • setfacl -R -b
    :清空 ACL 是不可逆的(不备份的前提下)。
  • restorecon
     大范围执行:可能误改其他相关目录 context,先测试再批量。

六、常用命令

按用途归档常用命令。

1. 看权限和属性

  
  
  
bash
# 看权限
ls -la
stat
stat -c '%a %A %n'

# 看 owner
id
getent passwd user
getent group group

# 整目录递归
ls -laR
tree -pug

2. 改权限 / owner / group

  
  
  
bash
chmod 644 file
chmod u=rw,g=r,o= file
chown user:group file
chown -R user:group dir
chgrp group file

3. 特殊位

  
  
  
bash
chmod u+s file  # setuid
chmod g+s file  # setgid
chmod +t dir    # sticky

4. ACL

  
  
  
bash
getfacl file
setfacl -m u:lisi:rw file
setfacl -m g:dev:r file
setfacl -m d:u:lisi:r dir     # default ACL,新文件继承
setfacl -x u:lisi file        # 移除指定 ACL
setfacl -b file               # 清空扩展 ACL

5. Capability

  
  
  
bash
getcap /usr/bin/xxx
setcap cap_net_bind_service=+ep /usr/bin/xxx
getpcaps <pid>

6. SELinux

  
  
  
bash
getenforce
setenforce 0    # 0 = permissive, 1 = enforcing
sestatus
ls -Z file
ps -Z -p pid
sealert -a /var/log/audit/audit.log
audit2why < /var/log/audit/audit.log
audit2allow -M mynginx < /var/log/audit/audit.log
# 长期修复
semanage fcontext -a -t httpd_sys_content_t '/var/www(/.*)?'
restorecon -Rv /var/www

7. AppArmor

  
  
  
bash
aa-status
aa-complain /usr/sbin/nginx
aa-enforce /usr/sbin/nginx
aa-disable /usr/sbin/nginx
aa-logprof
# 强制重新加载
apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx

8. 进程身份与 strace

  
  
  
bash
id
ps -eo pid,user,group,command
cat /proc/<pid>/status | grep -E '^(Uid|Gid|Groups|Cap)'
strace -f -e trace=openat,access,read,write cat /path/to/file
# ltrace
ltrace -e 'getenv+puts+...+fopen' /usr/bin/xxx 2>&1

9. 挂载 / 文件系统

  
  
  
bash
mount
cat /proc/self/mounts
cat /proc/self/mountinfo
findmnt /path
findmnt -t btrfs,ext4,xfs
tune2fs -l /dev/sda1     # 单独格式化属性
xfs_info /dev/sda1

10. 容器与 audit

  
  
  
bash
docker exec -u <uid> <container> bash
nsenter -t <pid> -m -u -i -n -p -- /bin/bash
auditctl -w /path/to/file -p rwxa -k mykey
ausearch -k mykey
aureport --summary

七、配置示例

下面给几组典型场景的配置示例。

1. sudoers 标准片段

文件:/etc/sudoers.d/01-ops

  
  
  
# 不要直接改 /etc/sudoers,避免被包更新覆盖
Defaults env_keep += "PATH HOME LANG LC_ALL"
root    ALL=(ALL:ALL) ALL
%admin  ALL=(ALL) ALL
%sudo   ALL=(ALL:ALL) ALL

# 特殊授权:希望 www-data 能 mount / umount 某个文件
%sysops  ALL=(root) NOPASSWD: /bin/mount -o remount,rw /data

注意:/etc/sudoers 用 visudo 编辑(visudo -f /etc/sudoers.d/01-ops)。

2. ACL 在 NFS 上的 config

文件:/etc/exports(注意 NFS 默认 noacl

  
  
  
/data *(rw,sync,no_subtree_check,no_root_squash,crossmnt)

挂载时启用 ACL:

  
  
  
bash
mount -t nfs <server>:/data /mnt -o 'acl,vers=4.1'

NFS v4 内置 ACL,不需要 noacl,但 NFSv3 默认不支持。

3. SELinux 长期修复

文件:自定义 policy 模块(用 audit2allow 生成)

  
  
  
te
# mynginx.te
module mynginx 1.0;
require {
    type httpd_t;
    type var_log_t;
    class file { open read };
}
allow httpd_t var_log_t:file { open read };
  
  
  
bash
checkmodule -M -m -o mynginx.mod mynginx.te
semodule_package -o mynginx.pp -m mynginx.mod
semodule -i mynginx.pp

在确认根因之前,不要直接导入新策略模块。生产环境里先 setenforce 0 把模式切到 permissive,再观察一段时间,确认没有其他被拒绝的请求,最后用 audit2allow 把业务相关的都纳入同一模块。

4. systemd 服务以特定用户运行

文件:/etc/systemd/system/my-app.service

  
  
  
ini
[Unit]
Description=My App
After=network.target

[Service]
ExecStart=/opt/myapp/run.sh
WorkingDirectory=/opt/myapp
User=app
Group=app
UMask=0002

# 如需要绑定 < 1024 端口,使用 capability 而不是 root
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

# 资源限制
LimitNOFILE=65535

Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

应用:

  
  
  
bash
systemctl daemon-reload
systemctl start my-app

5. AppArmor 给一个脚本写 profile

文件:/etc/apparmor.d/usr.local.bin.myscript

  
  
  
#include <tunables/global>

/usr/local/bin/myscript {
    #include <abstractions/base>
    #include <abstractions/python>
    /usr/local/bin/myscript r,
    /etc/myapp/ r,
    /etc/myapp/** r,
    /var/log/myapp/ w,
    /var/lib/myapp/ rw,
    /run/myapp/ rw,
}

加载:

  
  
  
bash
apparmor_parser -r /etc/apparmor.d/usr.local.bin.myscript

6. udev 规则:给特定设备固定 ownership

文件:/etc/udev/rules.d/90-usb-key.rules

  
  
  
SUBSYSTEM=="block", ATTRS{idVendor}=="1234", ATTRS{idProduct}=="5678", OWNER="ops", GROUP="ops", MODE="0660"

加载:

  
  
  
bash
udevadm control --reload
udevadm trigger

7. PAM 强化

文件:/etc/pam.d/sshd 或 /etc/pam.d/system-auth

  
  
  
auth required pam_faillock.so preauth deny=5 unlock_time=300
auth required pam_faillock.so authfail deny=5 unlock_time=300
account required pam_faillock.so

文件:/etc/security/pwquality.conf

  
  
  
minlen = 12
minclass = 3
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1

八、日志与指标观察方法

权限问题不像性能问题,没法靠单一指标直接收敛到根因,仍以日志为主。

1. 系统日志

  
  
  
bash
journalctl -t kernel --since '15 minutes ago' | tail -100
journalctl -t setroubleshoot --since '15 minutes ago' | tail -50
dmesg -T | tail -50

2. SELinux 日志

  
  
  
bash
ausearch -m AVC,USER_ROLE_CHANGE -ts today
sealert -a /var/log/audit/audit.log
grep -E 'avc:|denied' /var/log/audit/audit.log | tail -50

3. AppArmor 日志

  
  
  
bash
grep -E 'apparmor.*DENIED' /var/log/audit/audit.log | tail -50
dmesg | grep -i 'apparmor'

4. 鉴权 / 登录日志

  
  
  
bash
journalctl -u sshd --since '15 minutes ago'
grep -E 'Failed password' /var/log/secure
last -f /var/log/btmp | head

5. 业务侧日志

业务侧日志会直接给出关键字:

  • java.io.FileNotFoundException: ... (Permission denied)
  • nginx: ... (13: Permission denied) while reading upstream
  • EACCES: permission denied, open '/var/log/myapp.log'
  • nginx: connect() failed (111: Connection refused)
    :注意这不是权限问题,可别混淆

6. strace 日志(手动抓)

把报错进程的 strace 输出保存下来:

  
  
  
bash
strace -f -e trace=openat,access,read,write,fstat -p <pid> -o /tmp/strace.log
# 复现后用:
grep -E 'EACCES|EPERM|EROFS' /tmp/strace.log

7. audit 监控告警建议

如果要接 audit 日志到告警:

  • 同一文件短时间内出现 > N 次 EACCES,触发告警。
  • 同一 SELinux denied 记录高频出现,意味着业务有违规访问,建议排查程序是否异常启动。
  
  
  
bash
# 一次性打 snapshot
ausearch -m AVC -ts today | grep count | tail -10

九、排查路径

把整篇文章的命令汇总成一个可执行路径树。

  
  
  
现象:业务 / 用户 / 服务报 "Permission denied"

├─ 步骤 0:明确报错上下文(who/what/where/报错原文)
│   └─ 报错原文是别的(如 EROFS / ENOENT / EACCES)
│       → 不一定是权限问题

├─ 步骤 1-2:基础属性 + 特殊位
│   ├─ 权限位对 → 步骤 3
│   └─ 权限位错 / 特殊位错 → chmod 修,复现

├─ 步骤 3:进程身份匹配
│   ├─ 用户在 owner / group 中 → 步骤 4
│   └─ 用户在 other → 是否该业务就该是 other?→ 步骤 4

├─ 步骤 4:ACL
│   ├─ getfacl 列表正常 → 步骤 5
│   └─ 有 mask / named user 异常 → setfacl 修

├─ 步骤 5:挂载选项
│   ├─ ro / noexec / nosuid 阻挡 → remount 或配置变更
│   └─ 挂载正常 → 步骤 6

├─ 步骤 6:SELinux
│   ├─ enforcing 且 audit 报 AVC → restorecon / policy 修
│   ├─ enforcing 但 audit 没有 → 步骤 7
│   └─ disabled → 步骤 7

├─ 步骤 7:AppArmor
│   ├─ DENIED → profile 调整
│   └─ 没有 DENIED → 步骤 8

├─ 步骤 8-9:strace + audit
│   ├─ 拿到明确 EACCES / EPERM + 现场调用栈 → 回到步骤 4/5/6 二次取证
│   └─ strace 无显著拒绝 → 继续排查

└─ 步骤 10:容器内权限
    ├─ 容器视角权限正常 → 宿主机 / 挂载问题
    └─ 容器视角异常 → namespace / uid mapping 调整

十、风险提醒

排查与修复时,下面这些是高风险动作,必须结合备份 / 灰度 / 回滚。

  1. chmod -R 777
    。表面解决了 A 问题,但同时让任何用户都可以随便改内容。如果是金融系统、密钥目录、配置文件目录、业务日志目录,千万别用。
  2. setenforce 0 + setenforce 1 之后没复验
    。一旦忘记切回 enforcing,下次重启 LSM 状态会回归 disbled 或维持 permissive,攻击面扩大。一定要有“开 permissive、定时窗关回 enforcing”的 SOP。
  3. restorecon -Rv /
    。会重置全盘 context,可能影响很大。务必限定到具体目录并提前 semanage fcontext -l 看现状。
  4. setfacl -b -R dir
    。清空整个目录 ACL,在备份好之前不要执行。
  5. usermod -aG group user
    -a 不要漏,否则会替换用户的 supplementary group 列表,把生效的组清理掉。
  6. mount -o remount,rw /
    。一旦业务层有 ro 挂载是因为一致性或 snapshot 原因,强行 remount 会带来数据不一致风险。
  7. apt remove apparmor / 升级后 policy 默认 deny
    。在不熟的版本上禁用 LSM,可能导致登录失败、SSH 失败、内核模块装载失败。
  8. chown -R 7777 这种 UID
    :把不属于业务的 ID 改到 root 才能识别的 UID 范围,会让一些服务(比如 ssh-agent、cron)找不到对应用户。
  9. auditctl -a never,task
    。审计策略选错,可能耗尽磁盘,要确认 rule 的 key 和 path。
  10. 容器内 docker exec 改文件
    docker exec 编辑的文件改动可能不会持久(取决于 bind-mount)。改完要确认。

十一、验证方式

不论做什么变更,验证要给出“通过 / 失败”的标准,避免“我感觉好了”。

验证 1:复现脚本

  
  
  
bash
sudo -u <user> bash -lc 'cmd'
# 预期:不再有 Permission denied

验证 2:业务侧

  • 通过业务接口验证:例如之前不能读文件,现在能读。
  • 错误日志中“Permission denied”关键字频率降到 0。

验证 3:审计 / LSM 日志无新拒绝

  
  
  
bash
ausearch -m AVC -ts recent
aa-status
journalctl -t setroubleshoot --since '1 hour ago'

验证 4:保持前后对照

  
  
  
bash
# 变更前
ls -la /path/to/file
getfacl /path/to/file
ls -Z /path/to/file
mount | grep <path>

# 变更后
ls -la /path/to/file
getfacl /path/to/file
ls -Z /path/to/file
mount | grep <path>

确认是按预期变化,而不是其他副作用。

验证 5:strace 验证

  
  
  
bash
strace -e trace=openat -p <pid> 2>&1 | grep -E 'EACCES|EPERM'

如果没有任何拒绝,且打开路径与预期一致,验证通过。

验证 6:自动化测试

如果要长期避免,回退前至少有一条端到端测试:

  
  
  
bash
sudo -u app bash -lc 'cd /var/app && ./run-stest'

十二、回滚方案

准备每个修复动作对应的回滚。

回滚 chmod / chown / setfacl

  
  
  
bash
# 写入备份
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
cd /etc && find dir -printf'%p %u:%g %m\n' > "/var/backup/perm.${TIMESTAMP}.txt" )

# 回滚
xargs -a "/var/backup/perm.${TIMESTAMP}.txt"chmod --args
# 注意:上面的命令只是示意,恢复 owner 要先腾出元数据

实践更稳的做法:把权限位打到一个文件里,恢复时按文件遍历回设。

回滚 SELinux

  
  
  
bash
setenforce 1
restorecon -Rv /path/to/dir
# 或:
semodule -r mynginx

回滚 AppArmor

  
  
  
bash
apparmor_parser -R /etc/apparmor.d/usr.sbin.nginx

回滚 group / user

  
  
  
bash
gpasswd -d user group
# 或
usermod -G <known-good-list> user

回滚 systemd unit

  
  
  
bash
systemctl revert my-app
# 或
cp -a /etc/systemd/system/my-app.service.bak.* /etc/systemd/system/my-app.service
systemctl daemon-reload
systemctl restart my-app

回滚 mount / fstab

  
  
  
bash
mount -o remount,ro /mount_point
sed -i 's/^UUID=.*\s\/\s.*$/原来行/' /etc/fstab

十三、生产环境注意事项

下面是权限问题的生产注意事项,把它沉淀成 checklist。

  1. 任何改变权限的命令都要先记录现状
    :用 stat -c 打快照到一个 /var/backup/perm/
  2. setenforce 0 是一次性开关,重启会失效
    。如果要做长期调整,走 sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
  3. restorecon 不带 -R 默认只作用于当前目录及其下文件
    -R 递归,但小心范围。
  4. NFS / Samba 挂载点要明确 aclvers
    。线上默认选项都不一定带 ACL。
  5. setuid 程序要做白名单
    /etc/audit/rules.d/audit.rules 里加上 -a always,exit -F path=/usr/bin/* -F perm=x -F auid>=1000 -F auid!=-1 -k privileged
  6. /tmp 不应该写敏感数据
    /tmp 默认 sticky,但敏感文件仍可能泄漏。
  7. 任何 umask 调整
    :进程级 umask 会影响新文件默认权限,要在 systemd unit 里用 UMask=0007 等显式指定。
  8. 日志目录归属
    :日志目录多服务共用时谨慎混用 owner,建议每服务一个目录并赋予 specific uid。
  9. 不要用 ACL 滥用
    :ACL 多了排查困难,能用 ugo 权限解决的不要上 ACL。
  10. 关键路径同步策略
    :多台主机的 /etc/passwd/etc/group/etc/shadow 用统一配置管理(Ansible / Salt),不要散乱。
  11. 容器镜像构建时尽早切非 root
    USER app 写在 Dockerfile 下游,避免生产跑 root。
  12. 千万别让 ssh-keygen 的私钥所在目录可被同机其他用户读取
    chmod 700 ~/.sshchmod 600 ~/.ssh/id_*
  13. /var/log/audit/ 占满磁盘会导致系统拒绝记录,从而丢审计日志
    。配合 max_log_file_action 配置 rotate。
  14. cap_* 用 capsh 验证
    : 测试 setcap cap_net_bind_service=+ep binary 时用 capsh --decode=<...> 看是否正确。

十四、总结

把这次主题的关键点归纳成几句:

  • 从出错的具体用户 / 命令 / 文件出发,先查标准 rwx,再查 owner / group,再查 ACL,再查 LSM
    。每一步都有可观察证据。
  • ls -la 看着对,未必真能访问
    。权限位只是第一道关;ACL、SELinux、AppArmor、挂载选项都可能阻挠。
  • 修复要分步验证
    chmod → 测 → 测下一个;不要一次性多个变量一起改。
  • 回滚路径必须先演练
    。线上工单里最容易出现的二次伤害是:乱配了 LSM 想恢复却恢复不回。

把这一套路写在团队 wiki 上,配合 auditd watch 关键文件、SELinux/AppArmor 状态监控,能把权限类工单从散乱的“凭直觉”变成“流程化处置”。

附录 A:内核 sysctl / procfs 关键开关

权限问题往往伴随系统行为边界,下面几个 procfs 路径值得知道,能避免反复被“隐藏配置”坑。

  
  
  
bash
# proc 的特殊目录
/sys/kernel/security/lsm
/proc/sys/kernel/cap_last_cap
/proc/sys/fs/protected_*
/proc/sys/fs/protected_regular
/proc/sys/fs/Protected_hardlinks
/proc/sys/fs/Protected_pipe
/proc/sys/fs/Protected_symlinks

# 用户空间进程默认权限
cat /proc/self/loginuid
cat /proc/self/uid_map

protected_* 是 hardlink / symlink 保护开关,开启后会在硬链接 / 软链接做防御:

  • kernel.yama.protected_sticky_symlinks = 1
    :限制用户跟随 world-writable 目录里的符号链接。
  • kernel.yama.protected_hardlinks = 1
    :限制非所有者创建硬链接。

这些不影响直接权限判断,但影响业务场景里“应用跟随软链执行”类逻辑。

/proc/self/uid_map 是 user namespace 的映射关系,做容器时常常要修改:

  
  
  
bash
echo"0 100000 65536" > /proc/<pid>/uid_map

附录 B:常见 pid / namespace debug

权限问题在多进程协同(systemd + 容器 + 主机)下,需要看清进程跑的 namespace:

  
  
  
bash
# 进程所在的 namespace
ls -la /proc/<pid>/ns
# 进入指定进程的 namespace
nsenter -t <pid> -m -u -i -n -p -- /bin/bash

nsenter 是处理容器内外权限差异的关键工具,能让运维临时获得容器视角的 id / mount。

附录 C:典型工单:5 个真实场景闭环

下面是我和生产团队这几年的真实工单,每条都按闭环组织。

工单 C.1:nginx 报 Permission denied while reading upstream

C.1.1 现象

  • nginx 反代 upstream 报错。
  • error.log 出现 Permission denied 在和 upstream 通信之后,紧接着 502。

C.1.2 初步判断

多数人会猜 SELinux。少数情况是反向代理接口落到了本地 socket。

C.1.3 命令检查

  
  
  
bash
ls -la /var/run/upstream.sock
ls -Z /var/run/upstream.sock
ps -Z -p $(pgrep -f nginx) | head
audit2why < /var/log/audit/audit.log | head

C.1.4 关键指标

  • /var/run/upstream.sock
     是 root:root 666
  • nginx domain 是 httpd_t
  • upstream 服务 domain 是 unconfined_t 或 initrc_t

C.1.5 根因定位

SELinux policy 不允许 httpd_t 访问 unconfined_t 的 socket。audit2why 输出大致类似 allow httpd_t unconfined_t:unix_dgram_socket read/write;,说明需要给 httpd_t 一个允许规则。

C.1.6 修复方案

  
  
  
bash
semanage fcontext -a -t 'httpd_sys_rw_t' /var/run/upstream.sock
restorecon -v /var/run/upstream.sock
# 或者改上游 socket 路径到 nginx 默认允许的 /var/cache/nginx/...

C.1.7 验证

  
  
  
bash
sudo -u nginx -- bash -lc 'cat /var/run/upstream.sock && echo ok' || true
curl --unix-socket /var/run/upstream.sock http://localhost/healthz

C.1.8 回滚预案

  
  
  
bash
semanage fcontext -d /var/run/upstream.sock
restorecon -v /var/run/upstream.sock

C.1.9 复盘

要在自己服务里建通用 socket 路径时,要确认 SELinux 是否允许当前进程 domain 反代。

工单 C.2:Docker 内应用 touch: cannot touch '/data/flag': Permission denied

C.2.1 现象

  • 容器内应用尝试写入 /data/flag,提示 Permission denied
  • host 上 /data 目录是 777

C.2.2 命令检查

  
  
  
bash
docker exec <container> ls -la /data
docker exec <container> id
docker exec <container> cat /proc/1/status | grep -E '^(Uid|Gid)'

C.2.3 关键指标

  • host 上 /data owner 是 1000。
  • container USER 不是 1000,是 0 或 33。

C.2.4 根因

UID 不对应,容器内进程不能写宿主机 1000 用户拥有的目录。

C.2.5 修复

docker-compose.yml 里加入 user: "1000:1000",或在 docker run 用 --user 1000:1000

C.2.6 验证

  
  
  
bash
docker exec -u 1000 <container> bash -lc 'touch /data/flag && echo ok'

C.2.7 回滚

docker run --user 0 ... 临时恢复回 root,但不要久用。

工单 C.3:业务报 bash: ./run.sh: Permission denied

C.3.1 现象

  • shell 脚本首行 #!/bin/bash,但报 Permission denied

C.3.2 命令检查

  
  
  
bash
ls -la run.sh
mount | grep $(pwd)
file run.sh

C.3.3 关键指标

  • 文件 mode 可能是 644(缺 x
  • 卷挂载可能包含 noexec

C.3.4 根因

通常两者之一:

  • chmod +x run.sh
     即可
  • 临时绕过:bash run.sh

C.3.5 修复

chmod +x run.sh,如果挂在 noexec,编辑 fstab 去掉。

C.3.6 验证

  
  
  
bash
./run.sh --ok-test

C.3.7 回滚

不重要,必要时 chmod 回原来权限。

工单 C.4:sudo 提示 unable to resolve host

C.4.1 现象

  • sudo
     在容器 / 临时主机里报 unable to resolve host xxx

C.4.2 命令检查

  
  
  
bash
cat /etc/hosts
hostname
cat /etc/resolv.conf

C.4.3 关键指标

  • /etc/hosts
     缺少主机名 → 127.0.0.1 行映射。

C.4.4 修复

  
  
  
bash
echo"127.0.0.1 $(hostname)" >> /etc/hosts

C.4.5 验证

sudo id 不再报错。

工单 C.5:git pull 在 NFS 卷上 Permission denied

C.5.1 现象

  • git 共享卷是 NFSv3,挂载选项默认 noacl
  • git 拉取时无法修改某些 metadata。

C.5.2 命令检查

  
  
  
bash
mount | grep <nfs-mount>
getfacl <file>
lsattr <file>

C.5.3 关键指标

  • NFSv3 不支持 ACL,但 setfacl 又在很多机器上默认启用了。

C.5.4 修复

  • 改用 NFSv4:mount 时带 vers=4.1,acl

附录 D:调试权限时的“安全命令”小抄

下面是一些安全的命令,适合贴工位上。

  
  
  
bash
# 看进程特权
getpcaps <pid>
ps -o pid,uid,gid,euid,egid,suid,sgid,command -p <pid>

# 看进程当前 cap
cat /proc/<pid>/status | grep ^Cap

# 看文件 owner 与历史 owner
ls -ln file
find dir -printf'%h/%f uid=%u gid=%g mode=%m type=%y size=%s\n'

# 看补充组
id user
groups user

# 看 secure_path
sudo -l | grep secure_path

# 看 service unit 限制
systemctl cat my-app | grep -E 'Limit|PID|UMask|Capabilit'

附录 E:常用 acl 模式速查

下面是我自己常用的 ACL 配方。

  
  
  
bash
# 给特定用户加只读
setfacl -m u:user:r file

# 给特定用户加读写
setfacl -m u:user:rw file

# 给特定组加写
setfacl -m g:dev:rw file

# 给现有文件加 default ACL(让新建文件继承)
setfacl -m d:g::rw -d /path

# 删除特定 ACL
setfacl -x u:user file

# 备份 ACL
getfacl -R dir > /tmp/acl.bak

# 恢复 ACL
setfacl --restore=/tmp/acl.bak

附录 F:selinux policy 三类常见模块

semodule -l 可以看到已装模块,挑三个常见的:

  • selinux-policy
    :基础 policy。
  • semanage
    :管理工具。
  • policycoreutils-python-utils
    :包含 audit2allowsealert
  
  
  
bash
# 列出 enabled 模块
semodule -l | head -20

# 看模块细节
semodule --info=selinux-policy

# 装载自定义模块
semodule -i mynginx.pp

# 移除
semodule -r mynginx

附录 G:profile / policy 文件结构简介

/etc/apparmor.d/ 下是 profile。/etc/selinux/targeted/policy/ 下是二进制 policy。

AppArmor profile 常见关键字:

  
  
  
network inet stream,
file,
capability,
owner,
dbus,
mount,
ptrace,
signal,
unix,

SELinux type enforcement(.te)文件常见字段:

  
  
  
te
module mypol 1.0;

require {
    class file { open read };
    role source_r;
    type src_t;
    type dst_t;
}

allow src_t dst_t:file { open read };

不要在生产上手写 .te,通常先用 audit2allow 生成。

附录 H:开源工具生态

下面几个工具是我日常会用到的:

  • setpriv
    :以指定 uid/gid/capability 跑命令,比 su 轻量。
  • runuser
    :与 su 类似,用于强制以指定 uid 启动脚本。
  • getcap / setcap
    :能力位管理。
  • nsenter
    :进入指定进程 namespace。
  • chroot / bwrap
    :轻量级 chroot/sandbox 工具。
  • smack 系列
    :另一套 LSM,但生产用得少。
  • efivars 相关
    :与 UEFI SecureBoot 相关权限。

注意:任何卸载 LSM 的操作都建议先在 dev 环境模拟,避免在生产做不可逆变更。

附录 I:跨主机一致性检查

线上多机环境里,同一台主机有时会因为 /etc/passwd/etc/group/etc/shadow/etc/sudoers/etc/dconf 等文件不一致出现“明明一台机能用,另一台不行”的情形。

用 ansible 或 puppet 统一收口是个办法:

  
  
  
bash
ansible all -m copy -a 'src=/etc/passwd dest=/etc/passwd'
ansible all -m copy -a 'src=/etc/group dest=/etc/group'
ansible all -m copy -a 'src=/etc/shadow dest=/etc/shadow mode=0600'
ansible all -m shell -a 'getent passwd {1000..1010}'

发现差异立刻拉齐。aide 或 tripwire 监控关键文件变更也很有用。

附录 J:何时考虑“重新走 LDAP / SSSD 集中账号”

如果维护多套 /etc/passwd/etc/group 已经非常痛苦,可以把账号统一到 LDAP / FreeIPA,并把客户端切到 sssd + nslcd

  • sssd
     提供本地缓存,业务离线时仍然可用。
  • nslcd
     是 LDAP / nssswitch / pam 的标准组合。

部署时注意:

  • 先在测试域打通 getent passwd <username>
  • 再迁移生产主机。

附录 K:key 文件和 SSH 权限清单

SSH 关联文件,权限是“严格”要求的:

  
  
  
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
chmod 644 ~/.ssh/known_hosts
chmod 600 ~/.ssh/config
chmod 644 ~/.ssh/authorized_keys

排查 SSH 连接被拒

  
  
  
bash
# 服务端
ssh -vvv user@host
ls -ld ~/.ssh ~/.ssh/authorized_keys
stat -c '%a %U:%G' ~/.ssh ~/.ssh/authorized_keys

有时 Linux Permission 仅是 SSH 拒绝的原因之一,更多是 AuthorizedKeysFileStrictModesPermitRootLoginPubkeyAuthentication 配置。下文给一个最小化清单:

  
  
  
# /etc/ssh/sshd_config
PubkeyAuthentication yes
PermitRootLogin prohibit-password
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes
AllowGroups ssh-users

附录 L:日志文件清理与权限

清理日志是有风险的:

  • truncate /var/log/xxx.log
     会让 logrotate 判时点错,建议用 > /var/log/xxx.log,并配合日志服务(如 rsyslog)reopen。
  • rm /var/log/xxx.log
     会让打开它的进程继续写(继续写入 unlinked inode)。
  • 用 logrotate 才是标准化方式:postrotate 触发 kill -HUP

权限经常被忽略:

  • /var/log/
     是 root:root 0755,但部分日志需要 adm 组可读,如 /var/log/auth.log

附录 M:socket / pipe / FIFO 权限

文件 socket 是一种特殊的文件,权限要按 socket 本身的 mode 来设置,客户端启动时用 unix socket 路径连。

M.1 Mysql socket

  
  
  
ls -la /var/lib/mysql/mysql.sock
# srwxrwxr-x 1 mysql mysql 0 ... mysql.sock

如果有其他用户要连(一般是错的),可调 mode,但不要 777。

M.2 Redis socket

  • 配置 unixsocket /tmp/redis.sock 和 unixsocketperm 770
  • 在外网/容器中走 TCP,而不是 socket。

M.3 Postfix socket

  • /var/spool/postfix/private/*
    :socket 共享队列。

附录 N:Capabilities 实战

N.1 最小权限运行 Web 服务

要让一个非 root 用户绑定 80 端口:

  
  
  
bash
setcap cap_net_bind_service=+ep /usr/bin/python3.11
sudo -u app python3.11 -m http.server 80

N.2 dump cap 列表

  
  
  
bash
capsh --decode=<HEX_MASK>
# 或
getpcaps <pid>

N.3 限制服务 cap

  
  
  
# systemd unit
[Service]
User=app
Group=app
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
LockPersonality=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectHome=yes
ProtectSystem=full
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=true
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

附录 O:容器权限矩阵

容器内权限可用下面 5 个维度评估:

维度
选项
影响
USER
root / non-root
默认是 root;改 non-root 时主机文件要 chown
capability
一组
默认是 Docker 默认 cap;做 K8s 时按需求关闭
seccomp
system / custom
默认 system;可以自定义 syscall 白名单
selinux / apparmor
label / profile
默认为 unconfined;启用后按策略
readonly rootfs
true / false
true 时容器内 / 没法写;做 secret mount 必要

附录 P:错误码速查

err code
errno
含义
EACCES
13
一般权限位被拒
EPERM
1
操作不允许(不是 rwx,是策略性)
EROFS
30
只读文件系统
ELOOP
40
软链循环 / 路径深度超限
ENOENT
2
路径不存在;可能父目录缺 x 位
ESTALE
116
NFS 文件过期
ETXTBSY
26
写正在执行的可执行
EFAULT
14
缓冲区越界 / 段错误
EIO
5
IO 错误,但不代表权限(注意)
ENOMEM
12
内存不足
ESRCH
3
进程不存在

附录 Q:相关 Linux 文档与 man

查 man 能找到更详细的字段:

  • man 1 chmod
  • man 1 chown
  • man 1 getfacl
  • man 5 acl
  • man 5 capabilities
  • man 8 setcap
  • man 8 getcap
  • man 8 setenforce
  • man 8 semanage
  • man 8 auditctl
  • man 8 nsenter
  • man 5 apparmor.d
  • man 5 sudoers

附录 R:写在最后

权限问题不复杂但很碎。最好用的工具依然是 流程化 + 最小变更

  • 不要一上来就 chmod 777,先查再改。
  • 改了任何权限,立刻写一份 /var/backup/perm.YYYYMMDD-HHMMSS 快照。
  • 修了 LSM(SELinux/AppArmor)相关的,遇到不确定时先 permissive / complain 而不是直接关闭。
  • 任何 rmsetfacl -b -R、整卷 chown -R 永远要二次确认。

按上面这个节奏,工单处理时间通常从“折腾两小时”降到“5 分钟内找到根因”。

附录 S:典型 bash / sh 脚本失败的 6 个坑

下面 6 个坑是写权限相关脚本时最容易踩的,做一些通用 case:

坑 1:变量没加引号,路径带空格就跪

  
  
  
bash
BACKUP="/var/log/my app.log"
ls -la $BACKUP
# 实际执行的是:ls -la /var/log/my app.log
# 正确:ls -la "$BACKUP"

解决:所有路径变量都 "$BACKUP"

坑 2:路径与目录不同步

  
  
  
bash
cd"$DIR"
# 注意:cd 之后如果 DIR 不存在,下面的命令行 $PWD 不会更新,除非重新读。

解决:每条命令检查 cd $DIR && ...

坑 3:批量删除没二次确认

  
  
  
bash
find /var/log -type f -name "*.log" -mtime +7 -delete
# 高风险操作

解决:加 -print 或 -ok

坑 4:目录递归 chmod 时没用 -X

  
  
  
bash
chmod -R u=rwX,go=rX .
# X = 只给可执行文件 / 目录加成可执行
# 不加 X 会导致普通文件变成 rw,但非文件不期望;

解决:明确 -X 或 -x

坑 5:脚本退出码没处理

  
  
  
bash
mount /dev/sda1 /mnt
# 挂在失败,但 echo 没输出,下面的程序以为成功了

解决:用 set -euo pipefail 或每条命令检查 $?

坑 6:硬编码路径在不同发行版上移位

  
  
  
bash
# CentOS
ls /etc/sysconfig/iptables
# Ubuntu
ls /etc/iptables/rules.v4

解决:判断 /etc/os-release,脚本里区分发行版。

附录 T:常见 bad case 组合 + 修复

T.1 setuid + nosuid

  
  
  
bash
mount -o remount,suid /tmp
# 临时解除 nosuid,或编辑 /etc/fstab

T.2 read-only 上写

  
  
  
bash
# 重 mount
mount -o remount,rw /path
# 然后看 /proc/mounts 是否生效

T.3 setcap 看不到

  
  
  
bash
# 容器里 setcap 不会被保留
# K8s 里,做法:
#   securityContext.capabilities.add: [NET_BIND_SERVICE]
#   securityContext.capabilities.drop: ["*"]

T.4 AppArmor + Docker

  
  
  
bash
docker run --security-opt apparmor=my-profile ...

T.5 redis 不能 bind 0.0.0.0

bind 127.0.0.1 ::1 或 protected-mode yes 时,仅允许本地。生产开 bind 0.0.0.0 要把 protected-mode no,并且防火墙开放。注意:bind 接 IPv6 前缀 - 的写法仅在部分版本支持,建议直接 bind 0.0.0.0

附录 U:监控项(用 Prometheus exporter)

权限类故障在业务指标里体现不像性能问题那样直观,但可以做以下监控:

  
  
  
bash
# 审计日志:单位时间 AVC 计数
audit_status="ausearch -m AVC -ts today | wc -l"
# /etc/lsb-release 中 SELinux 模式监控
getenforce

# AppArmor 监控
aa-status

# strace 类监控一般不上线

更重要的是把业务侧的 Permission denied 类错误做关键字告警:

  • grep -E 'Permission denied|EACCES|EPERM' /var/log/business.log
     频率

附录 V:相关参考资源

  • Kernel Documentation/admin-guide/cgroups-v2.rst
  • Kernel Documentation/admin-guide/LSM/
  • systemd.exec
     手册:详细描述所有 LimitNOFILEUserGroupCapabilitiesNoNewPrivileges 等配置项
  • Linux 文档项目 http://tldp.org
  • Red Hat Enterprise Linux SELinux 文档
  • Ubuntu AppArmor 文档
  • Man pages:上面已经列出,直接 man 即可

附录 W:练习题

为了让读者练习,列 5 个 mini 工单出来:

W.1 调试题:服务 db.service 无法访问 /var/lib/mysql/data

  • 写出 5 个排查步骤。
  • 答案:步骤 1 看 owner、步骤 2 看 mode、步骤 3 看 SELinux context、步骤 4 看 AppArmor、步骤 5 走 strace。

W.2 看图题:drwxrwx--T  4 root web 4096 /var/www/uploads

  • 问:其他人能删除 /var/www/uploads/x.png 吗?
  • 答:不能,sticky 阻止。这是“其他人只能删自己”的语义。

W.3 排错题:/opt/app/run.sh 有 x,但运行 bash: /opt/app/run.sh: No such file or directory

  • 答:通常是第一行 #!/bin/bash 指向不存在的解释器,或 #! 后必须紧跟解释器路径。

W.4 排错题:应用 EACCES 在容器内,但 host 上正常

  • 答:UID 映射不匹配、容器内进程非运行用户、host 上目录为只读。

W.5 设计题:实现一个最小化的 my-cron 服务,通过 systemd 部署且以非 root 运行

  • 答:参考附录 N 中的 systemd unit。注意:cron 调度通常用 crond 而不是自写。

附录 X:一种典型的权限问题操作流程图

  
  
  
text
[Receive ticket]
       |
       v
[Collect facts: who/what/where/报错原文]
       |
       v
[step 1-2: file info & mode]
       |
       v
[step 3: identity match?]
       |
       v
[step 4: ACL?]
       |
       v
[step 5: mount options?]
       |
       v
[step 6: SELinux?]
       |
       v
[step 7: AppArmor?]
       |
       v
[step 8: strace?]
       |
       v
[step 9: audit log?]
       |
       v
[step 10: container?]
       |
       v
[Identify root cause]
       |
       v
[Apply fix + verify]
       |
       v
[Record snapshot + rollback path ready]
       |
       v
[Close ticket with evidence]

附录 Y:生产环境变更 Checklist

  
  
  
text
[  ]  备份当前 permission: stat / getfacl / ls -lR / find -printf
[  ]  备份当前 owner: getent passwd / getent group
[  ]  备份当前 LSM 状态: getenforce / aa-status / sestatus
[  ]  备份 mount: mount / fstab
[  ]  备份 capability: getcap -r /
[  ]  备份 SELinux: semanage fcontext -l -C
[  ]  应用变更
[  ]  复现命令验证不再 Permission denied
[  ]  业务侧验证
[  ]  audit 日志确认无新拒绝
[  ]  通知变更窗口
[  ]  文档记录更新

【声明】内容源于网络
0
0
红帽Linux认证
社区致力于分享红帽Linux及其他产品最新资讯,RHCSA/RHCE/RHCA认证动态。
内容 173
粉丝 0
红帽Linux认证 社区致力于分享红帽Linux及其他产品最新资讯,RHCSA/RHCE/RHCA认证动态。
总阅读464
粉丝0
内容173