Linux账号与权限管理核心解析:从文件权限到ACL与SUID实战
2026/9/24 23:04:46 网站建设 项目流程

先聊个很多人都踩过的场景。你高高兴兴把一个开发同学加进了某个用户组,跟他说“权限都给你开了”,结果他那边一执行还是Permission denied。你再一看,用户组没加错、文件属主也对、rwx 权限位看着也没毛病,就是访问不了。这种诡异问题我碰到过不下十次,最后八成都是目录中间层的x权限丢了,或者被 ACL、SELinux 这种“隐藏关卡”拦了一道。

Linux 账号和权限管理这门课,说难不难,说简单也真不简单。它既是面试里最高频的基础题,也是日常运维和开发环境里最容易出幺蛾子的环节。这篇我不打算写成一章教科书式的命令大全,而是把所有跟账号、用户组、文件权限相关的核心逻辑,从/etc/passwd到 SUID/SGID 再到 ACL,全部串起来讲清楚,再带一套可以直接照着抄的实操脚本。无论你是刚入门的新手,还是被诡异权限问题折磨过的老开发,这篇应该都能让你少走点弯路。

1. 账号管理:用户和组的底层逻辑

1.1 三个关键文件,读懂整个账号体系

Linux 里没有“用户数据库服务”,所有账号信息都落在几个文本文件上,最核心的三个就是/etc/passwd/etc/shadow/etc/group。很多教程会把重点放在命令上,但我强烈建议先从这三个文件下手——命令只是壳,文件里的字段才是魂。

先看/etc/passwd,每一行对应一个账号,典型的行是这样的:

root:x:0:0:root:/root:/bin/bash devops:x:1001:1001::/home/devops:/bin/bash

每行用冒号分成 7 个字段,第一个是用户名,第二个x不是密码本身,而是表示密码哈希被影藏到了/etc/shadow里。后面是 UID、GID、用户描述信息、家目录、登录 Shell。

这里有个知识点特别容易引起误会:不是所有用户都能“登录”,比如nobody:x:65534:65534:Unprivileged user:/nonexistent:/usr/sbin/nologin这种。把登录 Shell 指到/sbin/nologin或者/usr/sbin/nologin,这个账号就不能被用来 SSH 登录,但照样可以跑服务、作为进程身份存在。这是做最小权限设计时一个非常实用的手法。

密码真正的存放点是/etc/shadow,它每行的格式是:

devops:$6$随机盐值$哈希值:18800:0:99999:7:::

第一个字段是用户名,第二个是最关键的密码哈希。$6$开头表示 SHA-512 算法,$5$是 SHA-256,$1$是老旧的 MD5。后面依次是密码最后修改时间(从 1970 年 1 月 1 日算起的天数)、两次修改最小间隔天数、密码有效期、到期前警告天数等。这几个字段对应chage命令的调整项,后面实操部分我会展开讲。

/etc/group则是组的定义文件,格式是:

devs:x:1002:zhangsan,lisi,wangwu

从左到右是组名、组密码占位、GID、附加组中的成员列表。主组成员不在这个列表里显示,而是靠/etc/passwd里的 GID 字段来确认。

我举一个实际教训。刚学 Linux 的时候,我在一台测试机上直接vim /etc/passwd手工加账号,以为格式照葫芦画瓢就行,结果保存之后发现su切不过去,最后才发现是 shell 字段拼错了一个字符。从此之后我就养成了一个习惯:非极端情况绝不手工编辑这三个文件,而是用useraddusermodgroupadd这些标准命令。它们背后做的事情本质上也是改文件,但会做参数校验,出错了也好定位。如果非改不可,改完记得跑一遍pwckgrpck校验。

1.2 UID、GID 和用户组关系的设计要点

UID 不是随便分配的。传统约定是0root1-999是系统账号(不同发行版划分有差异,CentOS 和 Ubuntu 在这个区间上不太一样),普通用户的 UID 从1000开始。为什么这么分?因为很多服务进程需要以特定身份运行,但又不能让它们拿到管理员权限,系统账号就是干这个的。比如 nginx 的 worker 进程常用nginx这个系统账号来跑,即使被攻击者利用,也不会直接获得 root 级权限。

用户组的关系里有个高频考点:主组和附加组。每个用户有且只能有一个主组(primary group),同时可以加入多个附加组(secondary group)。用户创建文件时,文件的属组默认继承用户的主组,跟你当前在哪个目里没关系。如果你希望某个用户新建的文件“天然”属于某个项目组,要么把他的主组改掉,要么借助后面要讲的 SGID 特殊权限。

举个实际案例。我们公司有个项目目录/data/webapp,前端组和后端组都要访问,但两者权限不一样。如果大家的主组规划得乱七八糟,权限控制就完全没法做。正确的做法是先建两个组frontendbackend,再给每个人加对应的附加组,目录通过组权限来控制——这比把某一个人单独设成目录属主再挨个授权要清晰得多。

记住一条经验:组设计要在建号之前就想好,不要等用户建了一堆再去捣鼓。组是权限管理的最小“集合单位”,组规划得好,后面的chmodchown全都能省下大量重复劳动。

2. 文件权限体系拆解

2.1 权限位的读法:rwx 到底是谁的 rwx

ls -l看任意一个文件,第一列十个字符大概是这个样子:

-rwxr-xr-- 1 devops devs 1024 Jan 15 10:00 app.py

第一个字符是类型,d是目录、-是普通文件、l是软链接、c是字符设备、b是块设备。剩下九个字符分三组,每组三个:属主权限、属组权限、其他用户权限。这里不展开每个字符,但目录和文件的 rwx 含义是不一样的,很多人在这里理解出了偏差。

对普通文件来说:r是能读内容,w是能修改内容,x是能当程序执行。对目录来说:r是能列出目录里的文件名,w是能在目录里新增、删除、重命名文件,x是能“穿过”这个目录访问里面的具体文件。也就是说,你要进入一个目录并读取里面某个文件,必须同时拥有文件的r和目录的x,缺一不可。

这就能解释很多线上故障。比如排查问题时发现文件权限明明是644(属主可读写,其他人只读),但普通用户进去就是Permission denied。往上一查,原来中间某个父目录权限是700,其他用户根本没有x权限,整个路径被“拦腰截断”了。我后来排查这类问题直接三步走:先namei -om /完整/路径看整条路径的权限,再检查文件自身 ACL,最后看 SELinux,效率高很多。

2.2 chmod、chown 和 umask 的正确用法

chmod有两种设置方式。符号法直观,适合人看:chmod u+x file给属主加执行权限,chmod g-w,o-rwx file把属组的写、其他的读和写一起去掉。数字法是脚本里最常用的,因为格式紧凑且可预测:r=4w=2x=1,一组权限算一个数,rwxr-xr--就等于750

数字法有个小坑:它默认把没有写出来的特殊权限位全部清零。比如一个文件原本是4755(带 SUID),你执行chmod 755 file,SUID 就没了。这不算 bug,但很多人在修改权限时没留意这一点,导致原有特殊权限被静默清除。稳妥的做法是先ls -l看一眼再动手。

chown用来改属主和属组,最常见的格式是chown 用户名:组名 file,注意中间是冒号不是点。还有个有用的参数-R,递归修改目录下所有内容,但使用时要相当克制——线上环境一个大范围递归chown可能导致服务起不来,这个我后面在“常见问题”里会详细说。

umask是另一个容易被忽略的点。它决定了新文件默认权限的“减去值”。Shell 里执行umask会看到一个三位数字(比如022),这个数字的每一位分别对应属主、属组、其他用户的被屏蔽权限。新文件的默认权限计算公式:文件是666减 umask,目录是777减 umask。所以 umask 是022时,新目录是755,新文件是644。如果你希望团队协作时新文件自动让组内可写,把 umask 设成002比较合理——这算是一个提升协作效率的小技巧,但也带来安全性代价,需要自己权衡。

2.3 特殊权限:SUID、SGID、Sticky Bit

普通的 rwx 权限之外,Linux 还有三个特殊权限位。它们经常出现在面试题里,也经常在线上环境制造麻烦。

SUID(Set User ID)作用于可执行文件。文件带 SUID 时,执行它的进程会临时获得文件属主的身份,而不是执行者自己的身份。最经典的案例是passwd命令:普通用户修改自己的密码要写/etc/shadow,但这个文件只有 root 能写,没有 SUID 的话,改密码这个操作根本没法做。ls -l /usr/bin/passwd你会看到-rwsr-xr-x,那个s就是 SUID 标志。

SUID 是一把双刃剑,一个不小心就把自己给背刺了。如果某个 root 所有的脚本或程序带了 SUID,而且里面存在漏洞,攻击者就能借助它提升权限。更难受的是,SUID 不会在解释器脚本(比如.sh.py文件)上生效,这是内核刻意为之的安全决策。我的经验是:不到万不得已不上 SUID,能用 sudo 解决就优先用 sudo。

SGID(Set Group ID)有两个作用。作用于文件时,效果类似 SUID,但身份换成了文件的属组。作用于目录时,它能让该目录下新建的文件自动继承目录的属组,而不是继承创建者的主组。这一点在项目协作中太重要了。比如/data/webapp属组是webteam,给目录加上 SGID 之后,不管谁来创建文件,文件属组都是webteam,省去了反复chown的麻烦。

Sticky Bit(粘滞位)主要用来保护目录。最典型的就是/tmp,它允许任何用户往里面写文件,但不允许用户删除“不属于自己”的文件——即使目录权限写的是777ls -ld /tmp会看到drwxrwxrwt的结尾t。这个场景可以类比成一个公共储物间,谁都能往里存东西,但只能取走自己存的那一份。

特殊权限的设置在数字法里对应第 4 个前缀数字:4是 SUID,2是 SGID,1是 Sticky Bit,还可以组合。比如chmod 1777 /data/share就是给/data/share设置粘滞位且权限是777。符号法也能设:chmod u+schmod g+schmod o+t。检查时可以看ls -l的输出,但要注意:如果文件原本没有执行权限,特殊权限位会显示为大写ST,表示这个位虽然设了但不生效——这种“半吊子”状态经常让人看走眼,我就在这里吃过亏。

3. 实操:从新建用户到权限落地

3.1 用 useradd 创建标准用户,并设置密码策略

手动改文件加用户这种事我早就不干了,标准做法是用useradd。假设我们要创建一个部署账号deploy,把它加入ops附加组,并指定登录 Shell 和家目录:

useradd -m -d /home/deploy -s /bin/bash -G ops deploy

参数解释一下:-m是如果家目录不存在就自动创建,-d指定家目录路径,-s指定登录 Shell,-G指定附加组。如果不加-m,很多发行版不会自动建家目录,后面切换到该用户时会发现cd ~路径都不对。

创建完用户后,密码处理也有讲究。交互式执行passwd deploy最安全,但脚本化部署时我们需要非交互方式:

echo '临时密码123!' | chpasswd

这条命令会从标准输入读取用户名:密码,适合批量初始化。生产环境我建议紧接着做两件事:强制首次登录改密码,以及给密码设置生命周期。

强制首次登录改密码的核心是设置chage

chage -d 0 deploy

-d 0表示把密码最后修改时间设为 1970-01-01,这样用户最近一次登录后必须马上修改密码。给密码设有效期则是:

chage -M 90 -m 7 -W 14 deploy

-M 90表示密码 90 天后过期,-m 7表示两次修改密码的最小间隔是 7 天,-W 14表示过期前 14 天提醒用户。这几个参数是很多公司账号合规审计的硬性要求,提前设置好能省去后期补台账的麻烦。

新手常犯的一个错误是只用useradd忘了passwd,结果用户建好了,密码压根没设。这时候用户既不能登录,也看不出什么问题,排查起来很迷惑。我的建议是每次建号后立刻用一条命令验证账号状态:

id deploy chage -l deploy

id看用户与组信息,chage -l查看密码策略明细,两个都正常再宣布建号成功。

批量创建用户的场景下,我更推荐newusers配合chpasswd。先把用户信息写成一个格式与/etc/passwd一致的文件,newusers < users.txt批量导入,然后再用chpasswd < passwords.txt统一设置密码。只要数据文件格式没问题,几十个账号几秒钟就建完了,效率比手工useradd高出好几个档次。

3.2 项目目录权限规划:一个可以直接照抄的案例

接下来用一个完整案例演示权限落地的全过程。假设场景是:服务器上有个项目目录/data/webapp,有两个组——dev(开发组,可读写)和ops(运维组,可读写也可执行一些部署脚本),还有一个人zhangsan(外包同事,只允许读取)。

第一步,创建组并把人加进去:

groupadd dev groupadd ops usermod -aG dev zhangsan usermod -aG dev,ops lisi

-aG-a特别重要,它表示 append(追加)。如果不加-ausermod -G会把用户原有的附加组全部清掉,只保留命令行里指定的组。这个操作我在生产环境误用过一次,当时差点把用户的权限全部清空,想想都后怕。

第二步,创建目录并设置属主、属组和基础权限:

mkdir -p /data/webapp chown root:dev /data/webapp chmod 2770 /data/webapp

这里chmod 2770里的2是 SGID,目的是让目录下新建的文件自动继承dev组,这是整个协作机制的核心。770表示属主root和属组dev都有完整的读写执行权限,其他用户无权访问。

第三步,给ops组和zhangsan单独授权。传统 rwx 权限无法实现“主组可写,另一个组只读,某个人只读”这样细粒度的区分,这时就要上 ACL 了。之前写过一遍 ACL 命令,这里再展开一层。

先给ops组加读和进入目录的权限:

setfacl -m g:ops:rx /data/webapp

再给zhangsan加只读:

setfacl -m u:zhangsan:r-x /data/webapp

注意,ACL 规则里的x对目录来说是必需的,没有它zhangsan连目录都进不去,更别提看文件了。很多新手只给r权限,结果对方一ls又报错,就是这个原因。

要想让zhangsan之后在该目录下新建的文件依然保持只读,可以设默认 ACL:

setfacl -m d:u:zhangsan:r-x /data/webapp

d:前缀表示 default,作用于目录后会影响未来在该目录下新建的文件/子目录。这是继承机制,不是实时生效,所以要提前设,而不是等文件建了再补。

最后用getfacl检查:

getfacl /data/webapp

输出里能看到 user、group、mask、default 等条目。有一个mask字段值得单独解释:它相当于所有命名用户、命名组和属组权限的“上限”。即使某条 ACL 写了r--,如果 mask 是r--,实际生效的就是r--。这个 mask 会在执行chmod时被自动重新计算,所以如果你在设置了 ACL 的目录上跑了一次chmod g-w,搞不好后脚就把人家组权限给削了。遇到“ACL 明明写了但权限没生效”的怪事,首先查 mask。

3.3 从删除账号到回收权限:离职流程同样重要

权限管理不仅管“给”,也要管“收”。常见场景是员工离职或转岗,账号需要禁用或删除。

标准做法不是直接userdel -r deploy一刀切,而是分两步。第一步先把密码锁定,让账号无法立即登录:

usermod -L deploy

或者更彻底一点,把 shell 改成nologin

usermod -s /sbin/nologin deploy

第二步才是根据审计需求决定是保留数据还是删除账号。如果需要彻底删除账号、家目录和邮件池:

userdel -r deploy

如果暂时不想删,但希望保留家目录中的数据,就把userdel -r分成两步:先注释掉/etc/passwd里的账号(或者锁密码),把家目录打包归档,过了审计期再彻底清除。

这里我要补一个容易忽略的点:删除用户不会自动删除这个用户在其他地方留下的文件。比如某个用户是/data下大量文件的属主,userdel之后这些文件的属主会显示为一串数字 UID。所以删账号前,先全局扫一遍该用户拥有的文件:

find / -user deploy -ls 2>/dev/null

尤其是 web 目录、数据目录、定时任务脚本,都得检查一遍。有次我删掉一个账号后,当天夜里一个 cron 脚本因为属主消失而执行失败,日志里全是 “user not found”,排查了很久才意识到是账号删早了。后来我养成了习惯:锁账号后至少观察一周,确认定时任务和进程都不依赖这个账号,再执行最终删除。

4. 常见问题与排查技巧

4.1 文件删除不了,问题可能在父目录而不是文件本身

很多新手会纠结于文件自身的权限,但文件能不能被删除,规则取决于它所在的目录。删除一个文件,本质上是修改目录里“文件名到 inode 的映射”,所以看目录有没有写权限就够了。

举个具体例子。用户zhangsan想删一个文件,文件的权限明明是666(所有人都可读写),但rm依旧报Operation not permitted。这时候去看它所在目录的权限,很可能目录是755,其他用户没有写权限,因此zhangsan根本没有删除文件的资格。

另外还要检查两类特殊标志。一类是目录上的粘滞位,比如/tmp和公共上传目录,即使其他用户有写权限,也不能删别人的文件。另一类是文件上的隐藏属性,用lsattr查看:

lsattr 文件名

如果看到i(immutable)或a(append-only)标志,那么即使是 root 也删不掉。i表示文件不可修改、不可删除、不可重命名,a表示只能追加内容。这些属性需要用chattr来设置或解除,比如chattr -i 文件名。我曾经遇到一种情况:安全加固脚本给某些日志文件加了a属性,结果运维轮转日志时发现删除不了,查了一圈才找到原因,直接把锅甩给了安全策略。

4.2 权限看着没问题,但就是访问不了?从这几层排查

遇到“权限神秘失效”,我推荐按下面这个顺序排查,效率最高。

第一层,检查整条路径的目录权限。用namei -om /data/webapp/config/app.yml,它会列出路径中每一层的属主、权限和 ACL,一目了然。九成的问题在这一层就能定位。

第二层,检查 ACL 的 mask。执行getfacl /data/webapp,重点关注mask是不是把权限下限抬高了。mask 被chmod意外改小的案例非常多,我建议在脚本里一旦执行过chmod,紧接着就getfacl复核一次。

第三层,看 SELinux。执行getenforce,如果输出是Enforcing,那可能就是 SELinux 在拦截。再执行ls -Z看文件的安全上下文,跟同目录下能正常访问的文件做对比。临时放行可以用chconrestorecon,但长期建议认真排规则。这个场景真的非常普遍:根目录下迁移过来的一组文件,SELinux 上下文变成了默认的conf_t而不是httpd_sys_content_t,nginx 死活读不了,日志里又没有明显的权限报错。排查权限问题时,sealert/var/log/audit/audit.log都能给出明确线索。

第四层,确认 sudo 环境中的 PATH 问题。这个问题不在文件权限,但挺有迷惑性。sudo执行命令时默认使用 secure_path,可能跟你当前 Shell 的 PATH 不一致。这会导致你在自己终端里能敲nginx -t,但sudo nginx -t却说命令找不到。遇到这种,用sudo /usr/sbin/nginx -t或者编辑/etc/sudoers里的secure_path都能解决。

4.3 sudo 授权:用最小权限防住最大事故

账号权限做完后,管理员常常面临另一个需求:给开发或运维人员一个“临时 root”能力,但不能把 root 密码交出去。解决思路是 sudo。

sudo的配置文件是/etc/sudoers,强烈建议不要直接vim去改,而是用visudo。它自带的语法检查能防止你把自己锁在门外——如果你写坏了 sudoers,visudo会在保存时报错并拒绝写入,而vim直接写坏的后果可能是所有人包括 root 都没办法用 sudo 提权了。

一个实用授权示例如下:

dev ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx ops ALL=(ALL) /usr/bin/systemctl restart *, /bin/systemctl stop *

第一行表示dev用户可以在所有主机上,不需要输入密码就执行 nginx 重启命令。第二行表示ops组可以重启或停止所有 systemd 服务,但要求输入自己的密码。NOPASSWD要谨慎使用——方便归方便,但一旦账号被入侵,攻击者连密码都不用猜就能执行授权命令。

关于 sudo 授权,我的建议是:能细分到命令就直接写命令,尽量少给ALL=(ALL) ALL这种“等于送 root 外壳”的权限。线上环境真有“给个 root 权限方便一点”的想法,往往就是事故的开端。配好之后,让用户执行sudo -l检查自己的授权列表,一眼能看到自己到底能提权做什么。这是验证 sudoers 是否生效最快的方式。

5. 最后再分享几点经验

账号和权限管理的核心,说穿了就是“最小权限”四个字。在建号、授权、设置目录权限之前,先问自己一句:这个人、这个进程、这个脚本,真的需要这么多权限吗?不需要的权限,越少越好,这样即使出问题,影响面也小。

实际操作中我会额外坚持几个习惯。第一,每建一个账号、每做一次权限变更,都在变更记录里写一笔,包括变更人、时间、授权依据,方便后期审计。第二,定期扫一遍系统中长期不登录的账号和异常的 UID 0 账号——一个安全的系统,根本不应该出现多个 UID 0 的“平行 root”。第三,每次执行批量chmodchown之前,先备份 ACL 和属主信息:

getfacl -R /data/webapp > /tmp/webapp.acl.bak

真出问题时恢复成本极低,这条小命令帮我省掉了不少运维事故。这套账号和权限管理的体系,越早想明白,你后面踩的坑就越少。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询