十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 id 命令剖析 Linux 权限问题:uid、gid、有效身份与组管理

用 id 命令剖析 Linux 权限问题:uid、gid、有效身份与组管理 遇到Permission denied先敲whoami发现自己明明有 root 权限还被挡在门外把用户加进了 docker 组重启终端后却依然没法用 docker两条命令跑同一个文件一条成功一条报错。这些场景我估计很多人实际都撞见过而它们背后共同的线索都能用一条命令解释清楚——id。id最基础的功能是查询用户身份信息但真正用好的时候它是你排查权限问题的第一把扳手。这篇文章我就以id为主线从输出字段、参数组合、有效身份、脚本判断到用户管理事故把它在真实环境里的用法一次讲透。无论你是刚开始学 Linux、准备面试还是已经有几年维护经验想补一补底层概念下面这些内容应该都有参考价值。1. 裸敲 id 到底打印了什么uid、gid 与 groups 的字段拆解很多教程会把id一句话带过查看用户 id 信息。真到用的时候输出长这样$ id uid1000(zhangsan) gid1000(zhangsan) groups1000(zhangsan),4(adm),27(sudo),131(docker)如果系统启用了 SELinux末尾还会多一个context...字段大致是这样的$ id uid1000(zhangsan) gid1000(zhangsan) groups1000(zhangsan),4(adm),27(sudo),131(docker) contextunconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023context是 SELinux 的安全上下文不是每个发行版都有。默认情况下 Ubuntu、Debian 没有CentOS、RHEL 上有。这部分很多人直接忽略但在排查 SELinux 拦截问题时id里的context反而是第一个要看的字段。1.1 三个关键字段的含义拆开看uid1000(zhangsan)当前用户的有效用户 ID 是 1000对应的用户名是 zhangsan。这里有个容易混淆的点你看到的 uid在没有特殊程序介入时既代表真实 ID也代表有效 ID两者一致。如果不一致id会把它们分开显示后面讲 setuid 时细说。gid1000(zhangsan)基本组的组 ID 和组名。它来自/etc/passwd中该用户记录的第 4 个字段。你去看/etc/passwd里用户记录的第 4 个字段就是 gid不是用户名。这个基本组决定了你在系统里新建文件时默认的属组。groups1000(zhangsan),4(adm),27(sudo),131(docker)当前用户所属的全部组列表包括基本组和补充组。括号里是组名前面的数字是组 ID。很多新手以为基本组不在这个 groups 列表里其实groups列表第一个成员就是基本组后面列的是补充组。命令后面跟用户名可以查任意用户$ id daemon uid1(daemon) gid1(daemon) groups1(daemon)有些系统上 daemon 还有一个补充组bin不同发行版定义不完全一样这说明不要凭记忆猜直接用 id 查才是最可靠的做法。1.2 基本组和补充组管的事情不一样基本组和补充组都参与权限判断但分工不太一样。基本组最直观的影响是普通用户创建新文件或目录时默认的属组就是基本组除非写了chgrp或者目录上设置了 setgid 位。举个例子公司内部有个/srv/team目录里面需要多人协作。如果你把用户的补充组加进 team 组他确实能访问已有文件但如果目录允许创建新文件新文件默认属组却是他的基本组而不是 team 组这就容易导致我能进来但新建的文件别人改不了。这时候常见解法是在/srv/team上设置 setgid让新文件属组自动继承目录属组。那id在这里能帮上什么忙当然能——改完配置后用id确认用户到底属不属于这个组以及他的基本组是哪个可以防止你把 setgid 都设好了结果用户压根没进组。补充组的作用是额外放行。系统在内核里做权限检查时如果访问请求与文件属主匹配就走 owner 权限位如果不匹配再看请求的组是不是进程的基本组或补充组之一是就走 group 权限位。这也是为什么把用户加到某个组通常立刻能解锁对这个组有权限的目录不需要改任何文件属性。1.3 真实、有效、保存三组 ID别只记一个这是面试里最容易被问到的点Linux 进程其实有三个用户 ID——真实用户 IDreal UID、有效用户 IDeffective UID、保存的用户 IDsaved UID组 ID 也同理还有对应的补充组列表。真实用户 ID启动这个进程的实际登录账户常规情况下不会变。有效用户 ID内核做权限检查时使用的身份。大部分时间它等于真实 ID但遇到 setuid 程序或者用 su、sudo 切换身份时会不同。保存用户 ID用于程序在有效 ID 和保存 ID 之间来回切换。它让你能降下去再升回来不至于一次放弃后就回不来。id默认显示的是有效身份这也是为什么问题排查时你不能只靠whoami。whoami在很多实现里读的也是有效 UID但输出的是字符串容易受环境变量或别名干扰而id直接查系统用户数据库和进程凭证结果更可靠。如果要专门看真实 ID用id -r。后面讲 sudo 和 setuid 的章节会专门展开这里先记住id输出的 uid/gid默认偏向有效身份不是登录账户。2. 参数看着少组合起来能顶半个身份管理工具id的参数不多核心就那几个但组合起来很实用。直接给一张对照表命令作用示例输出id -u当前用户的有效用户 ID纯数字1000id -u zhangsan指定用户的 UID1000id -g基本组 ID1000id -G所有组 ID空格分隔1000 4 27 131id -n把数字转成名称需要和-u、-g、-G搭配zhangsanid -r显示真实 ID而不是有效 ID1000id -un当前用户名zhangsanid -Gn当前用户所有组名zhangsan adm sudo dockerid -u -n daemon指定用户的名字daemon2.1 核心参数的使用边界-n本身不输出任何东西它只是一个格式修饰符只有跟-u、-g、-G之一搭配才有意义。常见的错误写法是直接id -n结果什么也没显示然后就以为命令坏了。实际上id -n是显示名称这个修饰指令但不告诉 id 要显示哪个对象的名称自然空手而归。-r也有类似的搭配限制它必须和-u、-g、-G结合否则没效果。因为真实 ID是相对于有效 ID而言的你总得指定是要真实用户身份还是真实组身份。2.2 我平时最常用的几种组合脚本判断当前用户[ $(id -u) -eq 0 ]返回 0 说明是 root。这个我在运维脚本里用了无数回比whoami更靠谱因为whoami输出的是字符串遇到大小写、空格、换行容易出问题纯数字比较最稳。查看当前用户的所有组名id -Gn。这个比敲groups更标准groups在不同系统上格式略有差异而id -Gn始终是空格分隔的组名列表方便用 shell 的 for 循环处理。同时查多个用户的组for u in zhangsan lisi wangwu; do echo $u: $(id -Gn $u); done批量盘点服务账号是否有对应权限。取某个用户的 UID而不是靠自己换算id -u www-data返回结果因发行版而异有的系统是 33有的可能是别的。这符合一个原则不要硬记 UID让系统告诉你。注意id -G输出的是数字 ID排列顺序和/etc/group里出现的顺序不一定一致千万不要在脚本里用id -G的字符顺序去比较。要比较就用数值或换行后逐项比较后面第 4 部分我会给一个不会踩坑的例子。3. 权限变更没生效的真相有效身份和会话残留这一节是我自己踩过不少坑、也帮同事排过最多问题的地方你把用户加进某个组他明明在组里但依然没权限。表现非常迷惑因为id zhangsan清清楚楚显示用户已经在 docker 组里可用户自己在终端一执行 docker 就报Permission denied。原因一句话当前会话里已经存在的进程其凭证不会因为账号数据库的变化而自动更新。3.1 加了组为什么还得重新登录Linux 用户的补充组列表在用户登录时被读取并写入登录进程的凭证里之后这个登录进程派生的所有子进程都继承同一份凭证。你在这之后修改/etc/group数据库变了但那些早已创建的子进程手里的凭证没变所以它们仍然不认新组。验证方法很直接让用户开一个新终端看看id输出有没有变化。如果新终端里已经有了新组那么旧终端里那套进程环境必须彻底退出重进。不是敲一遍su - 用户名那么简单而是要退出整个会话或者用exec su - 用户名替换当前 shell 进程这样才能拿到新的组凭证。3.2 newgrp 立竿见影但有副作用如果不方便重新登录还有一条临时路newgrp docker。这个命令会启动一个子 shell把当前用户的基本组临时切换成指定组并重新初始化组列表。这个切换是临时的退出这个子 shell 就恢复原状。$ newgrp docker $ id uid1000(zhangsan) gid131(docker) groups1000(zhangsan),4(adm),27(sudo),131(docker) $ exit # 回到原shell注意细节执行newgrp docker后 gid 变成了 131也就是说基本组从 zhangsan 组换成了 docker 组。这带来的影响是在这个子 shell 里新建文件默认属组会变成 docker。在协作目录里使用时要特别小心最好提前设置好目录 setgid否则新建文件可能意外归到 docker 组。另外如果用户本来就是 docker 组成员newgrp这条路是通的如果用户压根不在组里系统会要求输入该组的组密码没有组密码就换不过去。这也侧面说明纯粹为了排查权限问题不如直接让用户重新登录来得干净。3.3 sudo、setuid 和 id 输出里的 euid 字段讲完会话残留再往前推一步。有些程序文件本身设置了 setuid 位比如/usr/bin/passwd$ ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 68208 ... /usr/bin/passwd所有者那段出现了一个s这就是 setuid 位。zhangsan 运行 passwd 时内核会把进程的有效 UID 临时置成文件属主 root这样 passwd 程序才有权限去写/etc/shadow。此时在程序内部查看身份会看到真实 UID 和有效 UID 不一样。如果是在外层 shell 里因为 shell 进程本身没有 setuid你看到的就是普通身份。想要亲眼看到这种差异可以看 sudo 的表现$ sudo id uid0(root) gid0(root) groups0(root)如果 sudo 是通过设置保留凭证的方式运行你可能在某些场景下看到uid1000(zhangsan) euid0(root)这种双字段输出。理解这一层能帮你解释很多明明是 root 怎么还被拒绝的怪问题——可能你看到的 root 只是登录用户名的 root进程的有效身份还没切换过去或者程序在切换后没正确恢复。面试官问起euid 是什么你如果能接着说出id 在真实和有效 ID 不同时会同时打印两个字段这一题基本就稳了。4. 真正派上用场的地方脚本判断和权限审计id如果不是为了写脚本和排查权限问题确实就是个查询命令。但一旦放进自动化场景它的价值立刻不一样。以下三个套路是我在线上和面试里都高频使用的。4.1 判断当前用户是不是 root几乎每个安装脚本、初始化脚本都有这一步。最稳的写法if [ $(id -u) -eq 0 ]; then echo 是root继续执行 else echo 必须以root身份运行 2 exit 1 fi为什么不写[ $USER root ]因为USER环境变量可以被伪造也可能在 su 切换时不更新。为什么不直接用[ $EUID -eq 0 ]因为EUID不是 POSIX 标准环境变量大部分现代 bash 里存在但在某些精简环境、busybox 或别的 sh 里可能是空的。id -u直接向系统取数字不依赖环境变量这是最底层的可信信息。按我多年的实操体验这条写法在所有发行版上都通用放到容器里也成立。4.2 精确判断用户属于某个组别让 grep -w 坑了你想判断 zhangsan 是否在 docker 组第一反应可能是id -nG zhangsan | grep -qw docker echo 在docker组大多数情况下能用但有一个边界问题grep -w匹配的是单词它会把docker-admin这种组也算进去。因为-w只要求匹配位置前后各有一个非单词边界字符docker-admin里的-正好是非单词字符于是docker被当成独立单词匹配上了。这个坑特别隐蔽尤其你系统里恰好有docker-admin这类组的时候。要真正精确匹配用逐项循环或把组列表拆行后逐行精确比对found0 for g in $(id -nG zhangsan); do if [ $g docker ]; then found1 break fi done或者更简洁的写法if id -nG zhangsan | tr \n | grep -qx docker; then echo 精确匹配docker组 figrep -x要求整行完全相等配合按空格拆行后就避开了-w的边界问题。这也是我在写组权限检查脚本时固定的写法毕竟wheel、docker、sudo这类短组名特别容易遇到相似前缀的兄弟组。4.3 和 stat、find 配合快速定位文件归属异常还有一类常见问题从备份或另一台机器迁移过来的文件ls -l里不显示用户名只显示数字比如1000 1000。这是因为这台机器上不存在 UID 1000 对应的账号或者当前系统里 1000 不是原来那个用户。此时id能快速帮你把数字对应成名字$ stat -c %u %g %n /data/oldfile 1000 1000 /data/oldfile $ getent passwd 1000 zhangsan:x:1000:1000:zhangsan:/home/zhangsan:/bin/bash如果getent passwd 1000没输出说明系统里没有 UID 1000 的账号。反过来id zhangsan也告诉你这个用户的 UID 是多少。两边一对照就能判断是不是因为账号体系不一致导致文件归属错乱。在 NFS 共享目录、容器挂载卷这些场景里这种UID 漂移问题非常常见排查时用id加stat基本一锤定音。5. 用户管理组合拳几个差点出事的典型事故复盘我把这章放在最后因为它比前面更贴近直接用命令改系统的场景。id本身不改东西但它是用户和组管理最直观的验证工具。不带它做复核你可能改完组都不知道自己已经把用户踢出了 sudo 组。5.1 事故一usermod 忘加 -a把补充组整个覆盖了这是用户管理里最高频的翻车现场# 想把zhangsan加到docker组 sudo usermod -G docker zhangsan用户确实进了 docker 组但如果他原来在 sudo 组或者 adm 组里这些组全部被替换掉了。也就是说他不仅进了 docker 组还失去了 sudo 权限。更麻烦的是有时候重新登录后才会发现而你已经没法用 sudo 修复了只能让另一个管理员帮忙。正确做法是追加sudo usermod -aG docker zhangsan-a就是 append只有带上它才是在现有补充组基础上追加。这条规则我记了无数次每次给别人写操作手册都会加粗usermod加组务必用-aG除非你明确知道自己在重置整个补组列表。改完用id zhangsan确认输出里既有 sudo 又有 docker再让用户新开终端验证。5.2 事故二useradd 的主组策略不同发行版不一样在一台 CentOS 上创建用户默认会建立一个和用户名同名的组但在某些环境或老系统上默认主组可能是users或者其他策略。如果你写脚本时假设新用户的主组必然是用户名很可能在系统间迁移时踩坑。验证同样靠idsudo useradd -m -s /bin/bash zhangsan id zhangsan出来后 gid 如果和 uid 一样说明该发行版默认用户私有组策略如果 gid 是 100 或其他公共组说明策略不同。更稳妥的创建方法是显式指定sudo useradd -m -s /bin/bash -U zhangsan-U要求同时创建一个同名组作为主组这样在两个发行版上行为一致。创建完还是用id验证。5.3 事故三把 nobody 当成可登录账号经常有人图省事把定时任务、web 服务进程直接设成nobody理由是反正它不需要权限。但id nobody会告诉你真相$ id nobody uid65534(nobody) gid65534(nogroup) groups65534(nogroup)nobody 在多数系统里 UID 是 65534壳是/usr/sbin/nologin或/bin/false不能登录。把它用作服务账号时文件所有权和日志归属也会变成一个尴尬的共享身份所有以 nobody 身份运行的进程共享同一个 UID它们之间无法通过文件属主区分。真想让服务之间隔离更合理的做法是给每个服务单独建系统账号和组比如useradd -r -s /usr/sbin/nologin myservice然后用同样的id命令确认账号已经建立再配合 chown 给对应目录。5.4 一个关于内核视图的提醒最后补充一个容易被忽略但面试很喜欢问的点你执行id看到的 uid/gid 是系统用户数据库/etc/passwd、/etc/group里的定义但内核真正做权限判断用的是进程凭证里的 uid/gid。两者在大多数情况一致却也存在短暂不一致的场景比如用户正在登录过程中、或者 NSS 配置刚改完还没刷新、或者你在容器里看到宿主机 UID 和容器内 UID 映射不一致。以后再遇到id 显示有权限但还是报错先别怀疑命令用id -u和cat /proc/$$/status里的 Uid、Gid 行对照一下看看真实 ID、有效 ID 到底落在什么值上。我个人现在排查用户权限问题的标准动作通常是四步先id 用户确认身份和组集合然后stat 目标文件看属主和权限位再ps -o user,group,comm -p 进程号看进程实际身份最后实在不行再上strace。其中id永远是第一步因为它能把我以为他是谁和他到底是谁这两件事快速拉齐。如果你也被权限问题折磨过下次不妨先敲一下它。
返回列表