权限这玩意儿平时不出事的时候没人会想起它。真正把你从椅子上拽起来的往往是这么几种场面某个服务重启之后一堆Permission denied日志里只有一行看不懂的提示新同事入职第二天备份目录少了小半文件追下去发现是有人顺手把自己的账号塞进了不该进的组接手一台跑了三五年的旧机器ls -l一拉满屏 777还夹着几个带s位的可执行文件谁都不敢动。这篇是 Linux 账号和权限管理的第二篇第一篇我们把用户是什么、组是什么、文件权限的九位怎么看这些基础铺开了这一篇往下钻一层账号数据到底躺在哪几个文件里、每加一个参数会动到哪些字段、sudo的授权怎么写才不至于变成全开放、ACL在什么场景下比改属组更划算、以及一个Permission denied摆在面前时按什么顺序往下挖。关键词锁定在Linux 账号与权限管理、用户组、sudo 提权、ACL 访问控制列表、SUID/SGID/Sticky、umask这几个点上。适合已经能在命令行里chmod、chown、useradd敲得出来但一到真实环境就心里没底的人看纯新手也能看懂因为关键的几个概念我会用生活里的东西类比过去。1. 先搞清楚账号数据到底存在哪几个文件里很多人用了一两年 Linuxuseradd、passwd敲得飞起但被问一句新建用户之后系统到底改了哪些文件就答不上来。这个问题答不上来后面所有的排错都是靠猜。账号体系其实不复杂核心就四个文本文件加一个目录。1.1 /etc/passwd 里那七个字段该逐位读/etc/passwd是账号的户口本全局可读因为很多程序需要根据 UID 反查用户名比如ls -l显示属主的时候。它每行七个字段用冒号隔开alice:x:1001:1001:Alice Wang:/home/alice:/bin/bash拆开看位置字段说明常见坑1用户名登录名大小写敏感别用大写开头2密码占位通常是x写成x表示密码在 shadow 里3UID用户数字 ID0 是 root改这里等于造超级用户4GID主组 ID必须对应 /etc/group 里真实存在的组5描述GECOS备注信息可空finger类命令会读6家目录登录后的落脚点可以和实际目录不一致不报错7登录 shell登录后跑的程序写/sbin/nologin就是禁止登录第二个字段这里有个细节值得单独说如果它写的是一个真实的密码哈希而不是x意味着任何能读/etc/passwd的进程都能拿到哈希去做离线爆破。老系统上偶尔还能见到这种配置属于要立刻修掉的历史遗留。现代发行版全部改成x把哈希挪到只有 root 可读的/etc/shadow这个设计本身就是权限分层的典型案例。第三个字段 UID 是最容易被玩坏的地方。有些教程为了图省事直接把普通用户的 UID 改成 0说这样就有 root 权限了。这确实有效但后果是系统里出现两个 UID 为 0 的账号审计日志里全是root出了事根本分不清是谁干的。真要给权限走sudo或者加组别动 UID。1.2 /etc/shadow 的九个字段与日期算法/etc/shadow的权限通常是000或者400属主 root属组 shadow。为什么是 000因为连这个文件存在、有多大都不想让普通用户知道读它必须靠特权程序passwd、login以 setuid 方式运行。九个字段alice:$6$xxxx:19800:0:99999:7:::从第三个字段开始全是日期而且是从 1970-01-01 起算的天数不是常见的年月日。这一点在实际操作里非常容易算错。比如你要设置某账号 2024 年 12 月 31 日过期不能直接填日期的字符串得换算# 算出目标日期距 1970-01-01 的天数 echo $(( ($(date -d 2024-12-31 %s)) / 86400 ))得到 20088 这样的数字才能填进去。好在有chage -E 2024-12-31 alice这样的封装命令帮你换算日常操作优先用它手改 shadow 是下下策改错一个字段账号就直接登不上。第四到第八个字段是密码策略相关最小间隔两次改密码之间至少隔几天、最大间隔密码多少天后必须换、警告天数过期前几天开始提醒、不活动天数密码过期后再过几天账号彻底失效、账号过期日期。这几个字段和后面的chage章节直接对应先记住位置。密码字段本身也值得看一眼。以$6$开头是 SHA-512$5$是 SHA-256$y$是 yescrypt$2b$是 bcrypt。如果看到!!或者!开头说明这个账号密码被锁了无法用密码登录但仍然可能通过密钥登录。这是排障时一个非常关键的分歧点后面第 8 章会展开。1.3 /etc/group 与 /etc/gshadow 的成员列表分工/etc/group四段developers:x:1002:alice,bob,carol组名、密码占位、GID、成员列表。这里有个非常容易绕晕的点成员列表里只列附加成员不列主组成员。如果 alice 的主组就是 developers/etc/passwd第四字段是 1002那么在/etc/group的 developers 行里可能压根看不到 alice 的名字但她确实属于这个组。我用id alice验证一下就很清楚$ id alice uid1001(alice) gid1002(developers) groups1002(developers),1003(docker)gid后面是主组groups后面是全部主组 附加组。当年我第一次看到/etc/group里没有某人的名字却判断他不在这个组就是栽在这个细节上。/etc/gshadow对应组的密码和组管理员日常几乎用不到除非用newgrp切组。可以简单理解成组版本的 shadow权限同样是 root 专属。这四个文件之外还有一个目录要提一句/etc/skel。新建用户时这个目录里的内容会被复制到新用户家目录.bashrc、.profile、.bash_logout都从这里来。想统一给所有新账号预置环境变量或者命令别名改这里比事后一个个改家目录靠谱得多。2. useradd、usermod、userdel 背后到底动了什么这三个命令看着简单但每一个都有一堆默认值在背后起作用。不搞清楚默认值从哪来就会出现我在 A 机器上useradd bob之后能登录在 B 机器上同样操作却登录不了这种莫名其妙的差异。2.1 默认值来自 /etc/default/useradd 和 /etc/login.defsuseradd不带参数时家目录建不建、shell 是什么、UID 从哪开始全由两个文件决定文件管什么典型配置/etc/default/useraddshell、家目录父路径、是否建家目录、默认组行为SHELL/bin/bashCREATE_MAIL_SPOOLyes/etc/login.defsUID/GID 范围、密码有效期、umaskUID_MIN 1000PASS_MAX_DAYS 99999UMASK 022/etc/default/useradd可以直接用useradd -D查看和修改。有时候在精简版系统上执行useradd zhangsan登录进去发现 shell 是/bin/sh甚至家目录压根不存在就是因为这两个文件的默认值和你的预期不一致。我的习惯是新建账号时所有关键项全部显式写出来不依赖默认值useradd -m -d /home/zhangsan -s /bin/bash -u 1500 -g developers -G docker,wheel -c Zhang San zhangsan多敲几个字符省掉后面一堆为什么他登录不了的排查。这条经验在自动化脚本里更重要因为脚本跑在什么环境下你未必清楚。-m和-M是一对-m强制建家目录并从/etc/skel复制-M明确不建。某些发行版默认不建家目录如果你漏了-m账号建好了但用户登录后cd ~报错体验极差。2.2 usermod 里几个改一下就可能把人锁在门外的参数usermod是这类命令里最危险的一个因为它直接改已经存在的账号。几个高频参数-aG追加附加组。必须带-a只写-G会覆盖原有的附加组列表。-g改主组。改完之后用户此前在家目录里创建的文件属组还是老的不会自动跟着变。-d /new/home -m改家目录并移动文件。不带-m只改字段不搬文件用户登录后会看到一个空目录而原文件还在老地方。-l newname改用户名。只改/etc/passwd里的名字家目录名、邮件池、sudoers里按用户名写的规则全都不会跟着改。-L/-U锁定 / 解锁。-L是在密码哈希前加一个!。-aG这个坑我见过太多次。有人在脚本里写usermod -G docker appuser跑完之后 appuser 本来在的developers、logreaders组全没了服务立刻起不来。加组永远用-aG这条几乎可以当成肌肉记忆。改用户名之后的连锁反应更隐蔽。sudoers里如果写的是alice ALL(ALL) ALL你把 alice 改名成 alice2这条规则立刻失效因为匹配的是字符串。同理crontab、/etc/fstab里按名字写的路径、某些应用配置文件里的用户名字段都得手动跟进。所以改用户名这事能新建一个删掉旧的就别改名。2.3 userdel 之前必须先想清楚的三件事userdel不加-r只删/etc/passwd、/etc/shadow、/etc/group里的条目家目录、邮件池、以及这个用户跑过的所有文件全部保留。这看起来安全实际留了个更大的雷文件还在属主的 UID 变成了孤儿下次新建账号如果分到同一个 UID新用户直接继承这些文件的所有权。所以删账号之前我一般按这个顺序走一遍# 1. 找到这个人拥有的所有文件全盘扫描慢但必要 find / -xdev -uid 1500 -print 2/dev/null # 2. 看看有没有正在跑的进程 ps -u zhangsan # 3. 检查有没有计划任务 crontab -l -u zhangsan ls -l /var/spool/cron/ 2/dev/null # 4. 确认之后再删 userdel -r zhangsanfind / -xdev里的-xdev是不跨文件系统否则挂载的网络存储、容器卷都会被扫进去慢到怀疑人生。扫出来的文件要么改属主转给别人要么明确归档后删除别留着。还有一种情况是账号删了但进程还在跑比如某个服务用这个账号启动一直没停。这时候 UID 在ps里会直接显示成数字文件也一样。必须先停服务再删账号顺序反了就是一堆残留。3. 权限位不止 rwxSUID、SGID、Sticky 三个特殊位九位权限大家都熟但ls -l里属主执行位上出现的那个s还有其他人执行位上的t很多人是见过但不知道干嘛的。这三个位在实际系统里用得非常广不理解它们看到/usr/bin/passwd是-rwsr-xr-x会一头雾水。3.1 SUID以文件属主的身份运行SUIDSet User ID只对可执行文件有意义。一个可执行文件带上 SUID 之后任何用户执行它进程的有效 UID 都是文件属主而不是执行者本人。最经典的就是/usr/bin/passwd$ ls -l /usr/bin/passwd -rwsr-xr-x. 1 root root 27832 ... /usr/bin/passwd普通用户改自己的密码本质是要往/etc/shadow里写东西而这个文件只有 root 能写。passwd带着 SUID 位运行时临时获得 root 身份改完立刻降权。整个系统里这类程序就那么一小撮每一个都是攻击面所以现代发行版对 SUID 程序的数量控制得很严。查找系统上所有 SUID 文件是安全巡检的固定动作find / -xdev -perm -4000 -type f -print 2/dev/null这个列表如果出现了你不认识的东西尤其是在/tmp、/home这种用户可写的目录下那基本可以判定有问题了。同理查 SGIDfind / -xdev -perm -2000 -type f -print 2/dev/null3.2 SGID 在目录上的行为新建文件继承属组SGID 用在可执行文件上时效果是以文件属组的身份运行但用在目录上时行为完全不同而且这个用法远比前者实用在这个目录里新建的任何文件或子目录属组自动继承该目录的属组而不是创建者的主组。设想一个协作场景三名开发者共同维护/srv/app每个人有自己的主组但都需要对方能改自己提交的文件。做法是# 建一个公共组 groupadd appdev usermod -aG appdev dev1 usermod -aG appdev dev2 usermod -aG appdev dev3 # 目录属组设为 appdev并打上 SGID chgrp appdev /srv/app chmod 2775 /srv/app2775里的2就是 SGID。设置完成后dev1 在/srv/app下创建的文件属组自动是appdev属组权限是rwxdev2、dev3 都能改。如果目录上没这个 SGID 位dev1 建的文件属组会是 dev1 的主组dev2 只能干瞪眼。ls -ld看目录时的表现是drwxrwsr-x属组执行位变成了s。注意一个小细节如果属组执行位原本是关的比如2745显示出来会是大写S意思是SGID 设了但执行位没开SGID 实际不生效。这个大小写的区别是排查为什么 SGID 没起作用时的第一眼线索。3.3 Sticky 位只保护删除这一个动作Sticky 位用在目录上效果是目录里的文件只有属主、目录属主、或者 root 能删除或改名其他人即使对目录有写权限也删不了别人的文件。/tmp是标准例子$ ls -ld /tmp drwxrwxrwt. 20 root root 4096 ... /tmp最后那个t就是 Sticky。/tmp权限是1777所有人可写但没人能删别人的临时文件。如果没有t任何一个用户都能把别人正在用的临时文件删掉整个系统立刻乱套。同理一个多人共享的上传目录也可以这么配mkdir -p /srv/upload chmod 1777 /srv/upload三个特殊位的数字表示法汇总一下位数字作用对象效果SUID4000可执行文件以属主身份运行SGID2000可执行文件以属组身份运行SGID2000目录新建文件继承目录属组Sticky1000目录只有属主/目录属主/root 可删设置方式两种数字法和符号法。数字法就是在常规三位前面加一位比如chmod 4755 file、chmod 2775 dir、chmod 1777 dir。符号法更直观一点chmod us file、chmod gs dir、chmod ot dir。清理的时候用u-s、g-s、o-t。提醒一句SUID 是提权漏洞的高发区。如果某个业务程序是自己编译的确需 SUID务必确认它会正确处理参数、不会把环境变量直接传给子 shell。历史上大量的本地提权问题就出在这类自己写的 SUID 小工具上。4. umask为什么你新建的文件权限总是 644umask这个东西平时存在感极低但它决定了你每天新建的所有文件的初始权限。很多人遇到过明明chmod 777了目录新建出来的文件还是不能执行或者为什么我建的文件别人改不了答案大多在 umask 里。4.1 umask 的计算方式与减法的本质umask的值是一组要屏蔽掉的权限位。默认常见值是022新建目录的基准权限是777减去022得到755drwxr-xr-x新建普通文件的基准权限是666减去022得到644-rw-r--r--这里有个关键点必须讲清楚普通文件的基准是 666 而不是 777。因为系统默认不给新建文件执行权限这是安全设计——你复制一个文本文件过来它不应该自动变成可执行脚本。所以哪怕 umask 是000新建文件也最多是666永远不会是777。总有人问我 umask 都设成 000 了为什么文件还是不能执行原因就在这。计算过程用二进制看更清楚但日常用位相减就够了注意是按位做减不是数值相减umask目录结果文件结果022755644002775664077700600027750640002是很多协作场景想要的同组用户可写077是私密场景别人完全看不到。安全合规要求高的机器上常常直接在/etc/profile里统一设成027。4.2 umask 设在哪一层影响谁umask 是进程属性会被子进程继承。设置位置不同影响范围差异很大# 只在当前 shell 生效退出即失效 umask 027 # 对登录 shell 生效写在用户自己的配置文件里 echo umask 027 ~/.bashrc # 对全体登录用户生效 echo umask 027 /etc/profile但注意/etc/profile只对登录 shell生效。通过systemd启动的服务、cron跑的任务、ssh执行的单条命令往往不走这个流程。这时候要去别的地方设systemd 服务在 unit 文件里写UMask0027cron 任务在 crontab 开头写UMASK 027特定服务在它的启动脚本或者环境变量文件里设这个分层是排查为什么服务生成的文件权限不对的核心。我遇到过一件事一批由定时任务生成的报表文件权限是644全局可读而合规要求是640。查了半天/etc/profile明明设了027最后发现是 cron 不走登录流程得单独在 crontab 里加一行。这种问题不踩过一次很难想到。4.3 数字权限与符号权限的换算技巧chmod的两种写法各有适用场景。数字法适合一次性定死比如chmod 640 config.ini符号法适合增量调整比如chmod gw file只加组写权限其他位不动。换算规则不难但容易在小地方翻车r4、w2、x1三位相加目录的x位含义特殊没有x就进不去即使有r也只能看到文件名打不开里面的文件chmod -R递归时对文件和目录应用同一套权限通常不是你想要的结果——目录需要x普通文件不需要递归改权限的正确姿势是分开处理# 目录统一 755 find /srv/app -type d -exec chmod 755 {} \; # 文件统一 644 find /srv/app -type f -exec chmod 644 {} \;或者用大写的X注意是大写它的含义是仅当对象是目录或者原本就有执行位时才加执行位chmod -R urwX,gorX /srv/appX这个用法知道的人不多但在批量修权限又要保留脚本可执行的场景里非常好用。5. sudo 授权别把用户往 wheel 组一塞就完事sudo是日常运维里用得最多的提权方式也是最容易配错的地方。配得太松等于人人都是 root配得太紧运维效率直接崩。中间那个刚刚好的位置靠的是sudoers里的规则粒度。5.1 为什么必须用 visudo 而不是直接编辑/etc/sudoers明文可读但绝对不能用vim直接改。原因是这个文件如果出现语法错误sudo会拒绝加载全部规则结果是所有人都提不了权包括 root 自己——因为 root 通过sudo提权也走这套解析虽然 root 有兜底但很多系统一旦 sudoers 坏了恢复起来很麻烦。visudo做的事很简单编辑完保存时做一次语法检查有错就提示你要不要重试、放弃、或者强制保存。这个检查救过无数人。同理/etc/sudoers.d/下的文件用visudo -f /etc/sudoers.d/xxx编辑。规则的基本语法谁 在哪台机器 (以谁的身份) 能执行什么命令四个部分alice ALL (ALL) ALL意思是 alice 在任意主机上可以以任意用户身份执行任意命令。这是最宽的授权等价于给了 root。实际生产里我一般这么分层授权层级写法适用对象只读排查Cmnd_Alias READONLY /bin/systemctl status *, /bin/journalctl, /usr/bin/tail一线值班限定服务重启Cmnd_Alias SVC /bin/systemctl restart app*, /bin/systemctl reload nginx应用运维全量但需密码%ops ALL(ALL) ALL核心运维免密特定命令%deploy ALL(root) NOPASSWD: /usr/bin/systemctl restart app自动化部署账号5.2 用 Cmnd_Alias 把权限切碎直接写一长串命令路径会很难维护Cmnd_Alias就是给一组命令起别名Cmnd_Alias APP_CTL /bin/systemctl start app, \ /bin/systemctl stop app, \ /bin/systemctl restart app, \ /bin/systemctl status app Cmnd_Alias LOG_VIEW /usr/bin/journalctl -u app, \ /usr/bin/tail -f /var/log/app/*.log %appteam ALL(root) APP_CTL, LOG_VIEW几个必须注意的地方命令必须写绝对路径sudo不做 PATH 查找。写systemctl会不生效。参数匹配是精确的。/bin/systemctl restart app只允许这一条systemctl restart nginx会被拒。但如果你写成/bin/systemctl restart *那个*是通配符能匹配任意参数风险立刻放大——有人会用systemctl restart ../../恶意路径这种方式绕。能用参数限死就限死尤其是那些能执行任意命令的工具比如vi、less、find、awk、tar。这些程序本身都能逃逸出一个 shell给它们的 sudo 权限等于给了完整 root。关于最后这一条值得多说两句。很多人以为sudo /usr/bin/vi file是安全的实际在vi里敲:!bash就能拿到一个 root shell。类似的还有less!sh、find-exec sh \;、man、python、perl。凡是能执行子命令或者能读写任意文件并借此修改配置的工具都不能随便放进 sudoers。这是 sudo 配置里最反直觉、也最容易出事的一条。5.3 sudo 日志与审计sudo的日志默认进系统认证日志位置随发行版不同Debian 系在/var/log/auth.logRHEL 系在/var/log/secure用 systemd 的机器上journalctl -u sudo或者journalctl _COMMsudo也能看。想单独留一份 sudo 日志在 sudoers 里加Defaults logfile/var/log/sudo.log Defaults log_input, log_output Defaults iolog_dir/var/log/sudo-iolog_input和log_output会把用户敲的每个按键、屏幕输出都录下来事后再现当时到底干了什么。这个功能有争议涉及操作记录存储量和个人隐私但在需要严格审计的环境里是硬需求。开启前记得规划好日志轮转别把磁盘撑满。还有两个实用参数Defaults timestamp_timeout5 Defaults env_reset Defaults secure_path/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bintimestamp_timeout控制免密有效期分钟默认 5 分钟设成 0 就是每次都问密码。env_reset清掉用户环境变量防止有人通过LD_PRELOAD这类手段注入。secure_path保证 sudo 下执行的命令走可信路径而不是用户自己的 PATH。6. ACL当一个文件只属于一个组不够用的时候传统权限模型有个硬限制一个文件只能有一个属主、一个属组。但现实里的需求经常是这三个人能读写那两个人只读还有一个外部审计账号只能读某几个文件。硬凑的话只能建一堆组组一多就很乱。ACL 就是解决这个的。6.1 setfacl 与 getfacl 的常用组合给单个用户加权限# 给 alice 读写权限 setfacl -m u:alice:rw /srv/report/2024.csv # 给组 auditors 只读 setfacl -m g:auditors:r /srv/report/2024.csv # 查看 getfacl /srv/report/2024.csvgetfacl的输出大概长这样# file: srv/report/2024.csv # owner: root # group: root user::rw- user:alice:rw- group::r-- group:auditors:r-- mask::rw- other::r--ls -l看这个文件权限位的最后会多一个号-rw-rw-r--。这个是有 ACL 规则的标记看到它就知道不能只看九位权限了得用getfacl。删除规则# 删掉某一条 setfacl -x u:alice /srv/report/2024.csv # 清空全部 ACL setfacl -b /srv/report/2024.csv6.2 mask 是 ACL 里最容易踩的一个坑getfacl输出里那个mask::rw-是很多人第一次用 ACL 时设置了却不生效的元凶。mask 是 ACL 条目的上限。所有命名用户、命名组、以及属组的权限都会和 mask 取交集。也就是说setfacl -m u:alice:rwx /srv/data/file getfacl /srv/data/file | grep mask # mask::rw-alice 明明设了rwx但 mask 是rw-实际生效的是rw-没有执行权限。改 masksetfacl -m m::rwx /srv/data/file或者更省事的做法设置具体条目时让setfacl自动重算 mask默认行为就是自动算除非你显式指定-n。所以大部分情况下 mask 不是问题只有在你手动指定了 mask、或者从别处继承过来一个旧 mask 时才会踩。排查 ACL 不生效getfacl里先看 mask这是第一顺位。6.3 默认 ACL让目录里的新文件自动继承setfacl -d设置的是默认 ACL只对目录有效作用是在这个目录里新建的文件和子目录自动继承这套规则。这是 ACL 相比普通权限最有价值的地方之一。# 在项目目录上设置默认 ACL setfacl -d -m u:alice:rwx /srv/project setfacl -d -m g:auditors:r-x /srv/project setfacl -d -m o::- /srv/project设置完之后任何人包括 root在/srv/project下新建的文件都会自动带上这些 ACL 条目。不需要每次建完文件再手动setfacl。注意两个细节默认 ACL 只影响新建的文件/目录对已经存在的文件无效。要同时处理历史文件得再跑一遍非默认的-m。子目录会继承父目录的默认 ACL也就是说这套规则会往下传。如果某层子目录不需要得单独用-k清掉默认 ACL。ACL 需要文件系统支持。现代 ext4、xfs 默认都开了一般不用管。极老的系统或者特殊挂载选项下可能需要mount -o acl。查一下就行mount | grep /srv 如果输出里没有acl字样挂载时补上。一个实践经验ACL 条目多了之后getfacl的输出会很长权限关系变得难以一眼看懂。我的做法是能用组解决的就用组ACL 只用来处理少量例外。一个文件上超过五六条 ACL就该考虑重新设计目录结构和组划分了。7. 一个 Permission denied 该怎么顺着链路往下挖前面讲的都是配这一章讲查。Permission denied是 Linux 上最高频的报错之一但它的原因可能出在七八个不同的层面。掌握一套固定的排查顺序比记住一百个命令有用。7.1 第一步永远是确认谁在访问同样是访问/srv/data你手动cat是成功的服务去读就失败——因为身份不一样。所以排查的第一个动作是搞清楚失败的那个进程是以什么身份跑的。# 找到进程的 PID ps -ef | grep app-server # 看它的实际用户和组 ps -o pid,user,group,cmd -p 12345 # 或者直接看 /proc cat /proc/12345/status | grep -E Uid|Gid|Groups/proc/PID/status里的Uid有四个数字分别是真实、有效、保存的、文件系统 UID。绝大多数权限判断用的是有效 UID。如果进程是 systemd 拉起来的去看 unit 文件里的User和Groupsystemctl cat app-server | grep -E User|Group这一步做完你就知道到底是谁被拒了。很多人跳过这步直接去chmod 777结果发现还是不行因为问题压根不在文件权限上。7.2 namei -l一次看清整条路径的权限Linux 的目录权限是逐级校验的。要访问/srv/app/data/file.txt需要对/、/srv、/srv/app、/srv/app/data每一级都有x权限最后才对file.txt本身校验读写。中间任何一级缺x直接拒绝而且报错信息不会告诉你卡在哪一级。namei -l就是干这个的$ namei -l /srv/app/data/file.txt f: /srv/app/data/file.txt drwxr-xr-x root root / drwxr-xr-x root root srv drwxr-x--- root app app drwxr-xr-x app app data -rw-r----- app app file.txt一眼就能看出/srv/app是750属组是app。如果访问者不在app组里到这一级就被挡住了压根轮不到file.txt。这个命令是排查路径权限的效率神器比手工一层层ls -ld快太多。顺带说一个非常隐蔽的坑父目录的x位缺失表现却不一定是 Permission denied。有时候表现为文件明明存在但ls看不到或者find扫不出来。因为r管的是列目录x管的是进入和访问。只有r没有x的目录能看到文件名列表但打不开任何一个。7.3 权限之外ACL、扩展属性、SELinux、挂载选项基础权限查完还没解决往下还有四层第一层ACL。ls -l带号就getfacl看一眼重点检查 mask。第二层扩展属性。chattr i会把文件设成不可修改连 root 都改不了要先chattr -i。这个属性在ls -l里完全看不出来lsattr /srv/app/data/file.txt # ----i---------e---- /srv/app/data/file.txt那个i就是不可变。这种情况下的报错往往是Operation not permitted而不是Permission denied但两者容易被混为一谈。第三层强制访问控制。SELinuxRHEL 系和 AppArmorDebian 系。SELinux 开着的时候即使传统权限全对安全上下文不匹配照样拒绝getenforce ls -Z /srv/app/data/file.txt # -rw-r----- app app unconfined_u:object_r:var_t:s0 file.txt如果要放到 Web 目录下给 Web 服务读上下文得是httpd_sys_content_t否则怎么改权限都没用。这是无数人权限全开了还是 403的真正原因。第四层挂载选项。分区被ro挂载只读或者挂了noexec禁止执行表现也各不相同mount | grep -E ro,|noexecnoexec尤其隐蔽——脚本权限755shebang 也对一执行就报Permission denied因为文件系统层面就禁止执行。很多人会去反复检查chmod其实是挂载参数的问题。把这四层串起来排查顺序就清晰了顺序检查项命令典型现象1访问者身份ps -o user,group -p PID服务用户和预期不符2路径逐级权限namei -l /path中间某级缺 x3ACLgetfacl /pathmask 卡住了权限4扩展属性lsattr /path有 i 或 a 标记5强制访问控制getenforce、ls -Z上下文类型不对6挂载选项mount | grep 挂载点ro 或 noexec这个表我一般直接贴在运维文档里新人照着走一遍八成的权限问题能自己解决不用来问人。8. 账号生命周期的收尾锁定、过期、密码策略账号建起来只是开始真正麻烦的是人走了账号还在、外包人员离职三个月了还能登这类事。这一章讲怎么把账号的生命周期管起来。8.1 chage把密码有效期真正管起来chage直接操作/etc/shadow里那几个日期字段但用的是人类可读的格式比手改文件安全得多。# 查看当前策略 chage -l alice # 密码最长 90 天必须换最短间隔 1 天过期前 7 天开始提醒 chage -M 90 -m 1 -W 7 alice # 密码过期后 30 天内不登录就彻底失效 chage -I 30 alice # 账号本身在指定日期过期不是密码是整个账号 chage -E 2025-06-30 alice # 强制下次登录必须改密码 chage -d 0 alice最后这条-d 0特别实用。新建账号时给一个临时密码同时把最后修改日期设成 0用户下次登录会被强制要求改密码临时密码就失去了长期有效性。这是交付账号时的标准动作。-E和-M的区别要说清楚-M管的是密码过期密码过期后用户还能登录但必须先改密码-E管的是账号过期到期后直接登不上改密码也没用。给外包、实习生设账号用-E更彻底。/etc/login.defs里的PASS_MAX_DAYS等参数只影响新建账号已经存在的账号不受影响。所以改了全局配置之后老账号得用chage批量刷一遍# 给 UID 大于等于 1000 的普通用户统一设置 awk -F: $31000 $365534 {print $1} /etc/passwd | \ xargs -I{} chage -M 90 -W 7 {}8.2 锁定账号的几种方式效果差别很大锁定账号这个动作至少有四种做法效果完全不同用错了会留下后门。方式命令效果能否密钥登录恢复方式密码前加!passwd -l alice密码登录失效能passwd -u alice改 shell 为 nologinusermod -s /sbin/nologin alice交互登录被拒不能usermod -s /bin/bash alice账号过期chage -E 0 alice完全无法登录不能chage -E -1 alice改密码字段为*手改 shadow密码登录失效能重设密码最容易出事的是第一种。passwd -l只是在密码哈希前加了个!SSH 密钥登录完全不受影响。如果这个账号配置了authorized_keys锁了等于没锁。我见过一次安全审计报告里写着已锁定 12 个离职账号实际抽查发现其中 7 个还能用密钥登进去。真正的锁死应该组合拳改 nologin shell 账号过期 清理authorized_keys 停掉相关进程。usermod -s /usr/sbin/nologin alice chage -E 0 alice rm -f /home/alice/.ssh/authorized_keyspasswd -S alice可以看账号状态输出第二列是Llocked、Pusable password、NPno password之一一眼判断。8.3 定期巡检脚本该盯哪几个点账号管理最怕的是以为管了其实没管。我一般会留一个巡检脚本定期跑一遍输出一份清单人工过目。核心检查项#!/bin/bash echo UID 为 0 的账号应该只有 root awk -F: $30 {print $1} /etc/passwd echo 空密码账号 awk -F: $2 {print $1} /etc/shadow echo 可登录 shell 的账号 awk -F: $7!~/(nologin|false|sync|shutdown|halt)$/ {print $1:$7} /etc/passwd echo 长期未登录的账号lastlog 超过 90 天 lastlog -b 90 echo sudo 权限清单 grep -rE ^[^#].*ALL /etc/sudoers /etc/sudoers.d/ 2/dev/null echo 无属主文件孤儿文件 find / -xdev \( -nouser -o -nogroup \) -print 2/dev/null | head -50这几项覆盖了最常见的几类风险多出来的 root 账号、空密码、不该有 shell 的账号、僵尸账号、过宽的 sudo 授权、以及离职人员遗留的文件。lastlog -b 90是列出 90 天没登录过的账号这个命令在很多新发行版上被标记为废弃因为wtmp机制的变化如果不可用用lastlog全量输出后自己筛也行。脚本跑出来的结果不必每次都处理但一定要有人看。巡检的价值不在于自动修复而在于让账号数量在悄悄增长这件事变得可见。我自己接手过一台机器/etc/passwd里有四十多个账号实际在用的不到十个剩下的全是几年间临时开的、没人清理的。这种事只能靠定期翻账本发现。最后分享一个我在实际操作中养成的小习惯任何账号创建或权限变更都在变更记录里写清楚给谁、为什么、有效期到什么时候。光靠系统里的配置三个月后你自己都想不起来当初为什么给某个账号开了那个 sudo 规则更别说接手的人了。权限收紧的时候最怕的就是这个不敢删万一有人在用——有记录就能果断处理没记录就只能一直留着越留越多。