
1. 账号不是用户名而是三个数字从/etc/passwd与/etc/shadow看本质很多人一开始接触Linux账号管理以为账号就是登录时敲的那串字符。这个理解不能说错但会掩盖真正的机制。Linux内部真正识别身份的从来不是用户名而是两个整数UID用户ID和GID组ID。用户名只是给人类看的“标签”内核在做权限判断时只认数字。你可以做一个简单验证随便把一个普通用户的UID改成另一个用户的UID再登录你会发现自己直接变成了另一个人。文件属主、进程归属、能访问什么全部跟着UID走用户名没起任何作用。这个特性在账号管理里既是便利也是雷区后面会展开讲。1.1 /etc/passwd每行七个字段到底在说什么先从最基础的说起。执行cat /etc/passwd每一行代表一个账号用冒号分成7段我拆开给你看root:x:0:0:root:/root:/bin/bash字段示例值含义用户名root登录名仅用于展示和登录匹配密码占位符x真正的密码哈希存在/etc/shadow里这里只是占位UID0用户ID内核只认这个GID0初始组ID用户创建文件时默认带上的组描述信息root通常是姓名或备注GECOS字段可随意填家目录/root登录后进入的目录很多服务依赖它登录Shell/bin/bash用户登录后运行的Shell可设为/sbin/nologin禁止登录有几个点值得注意。第一密码字段是x而不是哈希是因为早期Linux把密码哈希放在passwd文件里但该文件需要被所有用户读取导致密码哈希暴露暴力破解就有了目标。后来把哈希挪到只有root能读的shadow文件中才堵上这个洞。这是权限设计上“最小暴露面”的经典案例。第二UID不是连续的。传统Linux约定UID 0是root1-999是系统账号也叫伪用户1000以上是普通用户。系统账号服务于服务进程比如nginx可能有个nginx用户sshd有sshd用户它们通常没有家目录、不能登录只用来限制进程权限。第三同UID不等于同账号。如果你不小心在两个用户名上配了相同UID系统会认为它们是同一个账号登录哪个用户名看到的内容完全一样。这往往发生在手动指定UID时是实际运维中很隐蔽的坑。1.2 /etc/shadow与密码策略的管理边界/etc/shadow权限默认是000也就是除了root谁都不能读里面每行同样9个字段root:$6$xyz...:19000:0:99999:7:::依次是用户名、密码哈希、最后一次修改密码的日期从1970年1月1日算起的天数、修改密码的最小间隔天数、密码最大有效期、过期前多少天提醒、密码失效宽限期、账号失效日期、保留字段。实际管理中密码策略往往是安全审计的重点。以下场景很常见某安全团队要求你实现“90天必须改一次密码过期前7天提醒10天内不修改就锁定”。对应命令是chage -M 90 -m 0 -W 7 -I 10 usernamechage -l username可以验证策略是否生效。这里有个经验别直接手改shadow文件格式错了会导致用户无法登录且不报任何清晰错误用chage是最稳妥的方式。实测中我见过有人用sed改日期导致哈希被破坏整个账号直接废掉只能重置密码。1.3 系统账号的“锁”与“解锁”不该疏忽的基础操作很多人管理账号只知道删和建忽略了锁定这个中间态。账号锁定不等于删除它有几种实现方式usermod -L username # 传统方式在密码哈希前加! usermod -U username # 解锁 passwd -l username # 等价于usermod -L passwd -u username # 解锁但注意锁定账号并不会禁止root切换过去也不影响已经登录的会话。真正要限制登录需要配合修改Shell或使用/etc/nologin机制。我见过不少同学以为usermod -L之后用户就进不来了结果人家早就有终端会话照样在里面待着安全排查时被审计揪出来。提示批量查看哪些账号是锁定的可以执行awk -F: $2 ~ /^!/ {print $1} /etc/shadow这个命令在生产环境做账号盘点时很好用。2. 权限位的三重解读读、写、执行在文件和目录上完全是两回事账号定完了接着就是权限。Linux权限模型用一句话概括一个文件对三类人属主、属组、其他人分别设定是否可读、可写、可执行。但这里最容易被忽略的是同样的rwx字母作用在文件和目录上含义截然不同。这也是一堆权限问题从诡异到合理的根源。2.1 九个权限位的符号表示与数字换算执行ls -l输出的第一列形如-rwxr-xr--一共10个字符。第一个字符表示文件类型-普通文件、d目录、l符号链接、b块设备、c字符设备后面9个字符每三个一组第1组属主u权限第2组属组g权限第3组其他人o权限数字表示法就是把每组字母换算成二进制对应的十进制r4、w2、x1。举个例子-rwxr-xr--换算后就是755属主7rwx、属组5r-x、其他人4r--。我在实际项目里发现一个规律不熟悉数字法的人容易写出777这种全开权限。后面会细说为什么不该这么做但先记住一个认知755是目录的标准配置644是文件的标准配置600是私钥文件的标准配置700是家目录的标准配置。这几个数字背下来能解决日常80%的权限问题。2.2 目录的x权限为什么目录不可执行就进不去这是最容易误解的地方。文件的r是读取内容w是修改内容x是执行内容直觉比较顺。但目录的r是列出目录里的文件名w是在目录里创建/删除文件注意不需要对文件本身有写权限x是进入目录并访问其中文件的能力。真正的坑在这里如果你对一个目录只给r不给x你能ls看到文件名但访问任何文件都会报Permission denied。因为读取文件内容需要路径上每一级目录都有x权限。我踩过一次很经典的坑某个上传目录权限设成了744而不是755Web程序能显示文件名列表但用户下载文件全部403排查到怀疑人生。反过来只给x不给r的目录也有用处你能通过已知文件名访问文件但不能列出目录内容。这在某些限流场景比如网盘的分享链接目录是可以利用的技巧。2.3 umask为什么新建文件总是缺一些权限每个Shell会话都有一个当前掩码umask它决定新建文件/目录的默认权限。计算方式是默认完整权限减去掩码。文件完整是666因为默认不给执行位避免创建一堆可执行垃圾文件目录完整是777然后用掩码抵消。比如系统默认umask是022那么新建普通文件666 - 022 644属主可读写其他人只读新建目录777 - 022 755属主可读写执行其他人只读执行注意这里用的是算术减法不是按位与偶尔会有人在这里算错。如果umask设为027文件就是640目录就是750常用于团队项目目录隔离。要永久修改某个用户的umask写入其家目录的~/.bashrc或/etc/profile。实测中团队协作时统一umask能避免大量“怎么你的文件别人读不了/写不了”的扯皮尤其是多人共管同一批代码目录。2.4 chown与chmod的边界改归属和改权限不要混在一起权限设置的基本动作就两个chown改归属谁的文件、chmod改权限谁能碰。看起来简单但生产环境里混用导致的低级事故非常多。chown user:group /path/to/file # 同时改属主和属组 chown user: /path/to/file # 只改属主组不动 chown :group /path/to/file # 只改组经验法则先确定归属再设置权限。很多同学一上来就chmod 777其实应该先思考这个文件该属于哪个用户和哪个组。比如一个Web目录正确做法是把目录属主设为web用户属组设为web组权限755或750而不是一刀切777。777的口子开久了总有人往里放奇怪的东西安全审计迟早找上门。3. 特殊权限位SUID、SGID与粘滞位三个“保守治疗”的利器常规的rwx只能解决“用户身份”维度的权限但实际场景里有几个绕不开的需求普通用户要改密码需要写/etc/shadow、团队目录里所有人都能往里面放文件但不能互相删、临时目录里公共文件不能被别人随便清理。这就轮到三个特殊权限位登场了。3.1 SUID为什么普通用户能改密码先看一个现象/etc/shadow权限是000普通用户理应对它无能为力但普通用户执行passwd命令却能修改自己的密码哈希。秘密就在于passwd命令设置了SUID位ls -l /usr/bin/passwd -rwsr-xr-x. 1 root root ... /usr/bin/passwd你注意到那个s了吗正常情况下属主权限位是rwx中的x但这里显示为s。它的意思是当任何用户执行这个程序时进程的有效身份临时切换为文件属主root。所以普通用户执行passwd时进程瞬间变成root身份去写shadow写完后恢复。这就是SUIDSet User ID。设置方法chmod us /path/to/file。但我要非常严肃地提醒不要轻易给脚本或解释器加SUID。因为如果给bash加了SUID那么任何用户都能执行bash获取root Shell。Python、Perl同理。生产环境里这类事故屡见不鲜。安全加固检查单里find / -perm -4000扫描SUID文件是必查项。3.2 SGID让团队目录自动继承组身份SGIDSet Group ID作用在目录上时有一个非常有用的特性在该目录下新建文件和子目录它们的属组会自动继承目录的属组而不是创建者的主组。这正是团队协作目录的福音。假设项目组共用/data/project属主是某管理员属组是teamgroupSGID位一设无论组内谁往里面建文件文件属组都是teamgroup天然满足“组内可写”的预期。设置方式chown admin:teamgroup /data/project chmod 2770 /data/project那个2就是SGID的数字表示。后续新建的子目录如果想自动继承SGID需要单独设置但文件属性组继承是自动的。我实际用过这个方案管理共享目录配好之后真的不用天天盯着chgrp了。相比用ACL后面讲SGID对老系统的兼容性更好逻辑也更简单。缺点是如果目录下的程序不认组权限比如FTP服务没开组写权限也会白搭需要配合服务配置。3.3 Sticky Bit/tmp为什么不会“礼崩乐坏”/tmp目录人人可写理论上是安全重灾区。但你看它的权限ls -ld /tmp drwxrwxrwt. 20 root root ... /tmp最后那个t就是Sticky Bit粘滞位数字是1。它规定在一个设置粘滞位的目录里你可以创建和修改自己的文件但删除文件时只有文件的属主、目录的属主或root才有资格其他人哪怕对目录有写权限也删不了别人的文件。没有这个位的话任何能写/tmp的用户都能随手删掉别人的临时文件那就乱套了。设置方法是chmod t /path/to/dir或chmod 1777 /path/to/dir。注意三个特殊位叠加在权限数字的最前面按SUID4、SGID2、Sticky1的顺序排列。比如chmod 4755是SUID7552770是SGID7701777是粘滞位777。4. 账号生命周期管理实战从创建到清理的标准动作权限机制搞清楚之后回到账号本身。账号管理不只是useradd一条命令的事它涉及创建、授权、配置Shell环境、锁定、清理全流程。每个环节都有“标准动作之外”的坑。4.1 useradd的隐藏参数目录、Shell、优选项一次配齐一个典型的业务账号创建场景是这样的某同事要做数据分析需要Linux账号、家目录、bash环境、加入teamgroup。useradd -m -d /home/zhangsan -s /bin/bash -g teamgroup -c Zhang San zhangsan echo TemporaryPass_2024 | passwd --stdin zhangsan参数含义-m创建家目录-d指定家目录路径-s指定登录Shell-g指定主组-c备注。最后一行用--stdin从管道读入密码适合脚本批量建用户。这里有几个细节容易被新手忽略。第一-m和-d配合使用才有意义只用-d不指定-m时系统不会自动创建目录。第二主组-g只能指定一个附加组要用-G比如-G docker,wheel多个组用逗号分隔。第三-r可以创建系统账号UID自动落在系统范围内适合为服务进程建账号。我习惯在创建普通账号时把所有参数写全而不是创建后再补usermod。别图省事用默认值默认家目录路径和Shell可能不符合团队规范在几十台服务器上再统一改一遍目录结构成本极高。4.2 批量创建与批量清理三个脚本级技巧实际工作中最常见的是批量操作。比如某项目上线需要给5个研发各建一个账号并加入devgroupfor u in dev01 dev02 dev03 dev04 dev05; do useradd -m -s /bin/bash -G devgroup $u echo Initial_$u | passwd --stdin $u done批量删除时注意先确认有谁正在登录再动手who | awk {print $1} | sort -u # 查看当前登录用户 for u in dev01 dev02; do userdel -r $u; done-r会同时删除家目录和邮件池。我提醒一句删除账号前先备份其家目录里有价值的文件。生产环境里我见过不止一次账号删完发现里面有未提交的代码、数据库备份、配置文件模板。删了等于全没了。另一个容易被忽略的点是userdel不会自动清理该用户曾经拥有的文件。如果他的UID之后被新账号复用新用户就会“继承”这些历史文件的属主关系又污染又危险。规范做法是删除后执行find / -user 旧UID把散落的文件归置掉。4.3 用户组的管理边界主组、附加组与gpasswd组管理是账号管理里最容易“差不多就行”的环节但权限体系的稳定往往靠组撑起来。核心命令groupadd teamgroup # 创建组 gpasswd -a zhangsan teamgroup # 把用户加入组 gpasswd -d zhangsan teamgroup # 把用户移出组 groupdel teamgroup # 删除组组内不能有主组用户注意主组和附加组的区别用户登录后创建的进程默认GID是主组而附加组的作用是“额外获得的权限上下文”。也就是说文件权限判断里“用户属于哪个组”看的其实是他的所有组成员的并集。你id username看到的就是完整的组列表。一个常见案例给某用户添加docker组权限后他必须重新登录或执行newgrp docker才能生效原因是当前会话的组ID列表在登录时就固定了。很多人改完组权限但用户反馈没生效多数是没重新登录。这个细节十有八九能排查掉一类“权限改了没用”的灵异事件。4.4 密码过期策略让员工入职离职的账期自动流转团队管理人员变动频繁时密码策略不能全靠手动催。Linux早就提供了chage工具可以把整个密码生命周期自动化chage -M 90 -m 5 -W 7 -I 15 zhangsan含义密码最长90天、5天内不能改、到期前7天提醒、过期15天强制锁定。结合pam的pam_unix模块还可以实现登录时密码复杂度校验。经验是在新员工入职日当天设置“首次登录必须改密”chage -d 0 zhangsan这样他第一次登录就会收到密码过期提示被迫设置新密码。批量开账号时这条命令几乎每次都要跟上不然初始密码会被一直沿用跨部门流转后就成了安全隐患。5. sudo权限最小授权的边界设计从visudo说起账号建好了、权限位理解了还有一块绕不开怎么让普通用户在必要时执行管理命令。直接给root口令是懒办法一出事就是全责事故。正确做法是用sudo划定一个受控的“救援通道”。5.1 sudo与su的本质差异su -是切换用户需要目标用户的密码sudo command是“用指定用户身份执行单条命令”只需当前用户的密码默认。两者最大的区别在于sudo可以做到精确定到某条命令。比如研发小王需要重启nginx但不该有改系统配置的权限。你可以这样配visudo进入编辑器后加一行zhangsan ALL(root) /usr/bin/systemctl restart nginx这行的四段含义分别是用户、从哪些主机登录ALL表示任意、以谁的身份执行root、允许执行哪些命令。命令必须写绝对路径防止用户借助环境变量执行同名恶意程序。这个细节是sudoers最常见的坑写成systemctl而不是/usr/bin/systemctl匹配不上用户sh就被拒了。5.2 组授权与命令别名十分钟配好一个运维小团队团队场景下逐人配置不现实更推荐按组授权%opsgroup ALL(ALL) ALL%前缀表示组。但ALL(ALL) ALL意味着组内成员能执行任何命令权限给得很大通常只给高度可信的核心运维。一般团队应缩小到日常命令集合%opsgroup ALL(ALL) /usr/bin/systemctl, /usr/sbin/nginx, /usr/bin/tail还可以用别名整理方便维护Cmnd_Alias SERVICE_CMD /usr/bin/systemctl, /usr/sbin/nginx %opsgroup ALL(ALL) SERVICE_CMDvisudo的威力在于保存退出之前它会做语法检查语法错误会阻止你退出并提示修正大大降低配错导致sudo全线崩坏的风险。我有一次手滑在sudoers里多写了一个冒号还好visudo拦住并让我修好否则所有sudo都会立刻失效那就真的叫天天不应了。5.3 sudo日志审计需求下的“最后一根救命稻草”生产环境中“谁在什么时间执行了什么命令”不是可选项是审计要求。默认sudo会通过syslog记录日志但你还可以配置专属日志Defaults logfile/var/log/sudo.log Defaults log_input, log_outputlog_input和log_output会记录终端交互的输入输出适合在操作堡垒机或跳板机的场景下使用等于给每个特权操作录了像。我强烈建议给所有能sudo的用户配置独立的日志文件轮转和权限限制别让普通用户能随便读否则相当于特权操作的“证据链”直接暴露。日志文件的权限应该类似600属主root。5.4 sudo的一个隐蔽大坑环境变量与PATHsudo执行时默认会重置环境变量很多用户第一次遇到“明明有某个路径的命令sudo下却找不到”就是这个原因。secure_path控制着sudo执行的PATHDefaults secure_path /sbin:/bin:/usr/sbin:/usr/bin而环境变量传递也有开关不要随意放开env_keep尤其是LD_PRELOAD这类变量一旦被注入sudo执行时会加载用户指定的动态库想干什么都行。安全基线检查通常直接把这条列成高危项。6. 一次“有权限却无法访问”的完整排查链路fACL、隐藏属性与挂载选项理论与命令都过完一遍最后用一个真实案例把整个权限体系串起来。这个案例很有代表性报错是Permission denied但ls -l显示权限明明够于是排查了很久。6.1 现象与第一轮排查别只盯着ls -l某次同事反馈应用账号appuser访问/data/shared/report.txt被拒绝但ls -l显示该文件是-rw-r--r-- 1 root root按道理所有用户都能读。第一轮排查要看三件事id appuser ls -ld /data /data/shared /data/shared/report.txt getfacl /data/shared/report.txt结果第一个命令显示appuser在某个辅助组里目录一路都是755没问题文件权限也正常。但getfacl却多了一行user:opsrunner:r-- mask::r--问题找到了ACL访问控制列表里有一条单独的授权规则与系统权限位叠加而mask又限制了有效权限。虽然普通权限位是644但ACL的mask把有效权限压成了r--而ls -l显示的是“粗粒度权限”ACL才是精确判断的最终依据。6.2 ACL的正确启用与使用什么时候用、怎么不滥用ACL本质是突破“3类人×3种权”的限制允许给具体某个用户或某个组单独授权。启用很简单setfacl -m u:appuser:r /data/shared/report.txt也可以用递归处理目录setfacl -R -m g:devgroup:rwx /data/teamdir设定默认ACL让新文件自动继承setfacl -d -m g:devgroup:rwx /data/teamdirACL很有用但要克制。ACL条目越多权限审计越复杂排错越难。我见过一个目录挂了十几个ACL规则后来连创建人都说不清谁能访问。ACL适合解决“临时性的单点授权”长期性的团队协作建议优先用组SGID方案。6.3 第二层排查chattr隐藏属性与挂载选项ACL查完以为完事了但同样的报错在另一个目录又出现。这次getfacl干净得很ls -l权限也没问题。那就要往下看两个地方lsattr /data/shared/secure.txt mount | grep /datalsattr查隐藏属性如果文件带i不可变属性就算你是root也动不了文件更别说普通用户读chattr -i /data/shared/secure.txt # 解除不可变属性另一个容易被忽略的是挂载选项。比如/data挂载时带了noexec目录里所有可执行文件都跑不起来带了nosuidSUID位全部失效甚至nodev、ro都会影响行为。用mount命令确认挂载选项是权限排查里很靠后但经常一锤定音的一步。6.4 排查顺序总结一份可直接复制的“权限拒绝”诊断清单踩过几轮坑之后我给自己总结了一张排查顺序表基本覆盖了90%的“有权限却无权限”场景步骤命令查什么1id 用户名用户到底在哪些组里组集合对不对2namei -l /data/shared/report.txt路径上每级目录的遍历权限3ls -ld 路径/文件常规权限位与特殊权限位4getfacl 文件ACL精确规则与mask5lsattr 文件隐藏属性是否挡路6mount | grep 挂载点挂载选项是否限制了能力其中namei是个冷门但好用的命令它能列出路径中每一层目录的权限状态快速定位“哪一级目录没有x权限导致整个路径走不通”。这个命令在排查嵌套结构时比手动逐层ls好使太多。7. 写在最后的几个个人习惯账号和权限管理这个领域真不是“会几条命令”就能过日子的。我在实际工作中逐渐养成了几个固定习惯分享给你参考。第一所有涉及权限的批量操作先在测试机上跑一遍再上生产。特别是chown -R和chmod -R这种递归命令参数写反或范围写错整台机器的文件归属就全乱了恢复成本极高。第二权限配置尽量用“可解释、可回看”的方式留痕。比如在/etc/sudoers.d/下放独立配置文件文件名按团队或项目命名里面写清楚授权对象和原因。这样半年后有人问起“为什么给某用户sudo权限”你能从文件本身得到答案而不是靠记忆。第三定期扫描特殊权限位和失控的SUID文件。一行命令能扫find / -perm -4000 -type f 2/dev/null拿到结果跟基线比对多出来的东西要么解释、要么移除。这个动作成本很低但对安全的收益最直接。第四密码策略、sudo配置、账号清理这三件事建议纳入月度巡检清单。不是每次都能发现问题但至少能保证出问题时历史信息可追溯、权限边界可审计。很多公司在复盘事故时发现问题早就出现过只是没人定期看一眼。Linux的权限体系越往里挖越觉得设计精巧但它同时也是生产事故的高发区。把根因逻辑吃透、把排查顺序固化比背一百条命令更有用。希望这篇经验能帮你少踩一些我踩过的坑。