Linux-03 用户、权限与安全模型
前置阅读:Linux-02 文件、链接与归档压缩
权限是每天都在用、但很少有人真正搞清楚的东西。这一篇的重点不是背 chmod 755,而是三个容易理解错的地方:
- 删除文件需要的是「目录的写权限」,不是文件的写权限——这是最反直觉的一条。
x在目录上的含义完全不是「执行」。- 容器里的 uid 和宿主机是同一套,这解释了挂载卷权限问题的所有现象。
1. 身份:一切都是数字
一句话:内核只认 UID 和 GID 这两个数字,用户名是给人看的。
/etc/passwd 的作用就是「数字 ↔ 名字」的翻译表。内核在做权限检查时从来不查这张表,它只比较进程的 uid 和文件 inode 里的 i_uid。
这个认知能解释很多现象:
id
# uid=1000(lhx) gid=1000(lhx) groups=1000(lhx),27(sudo),999(docker)
# ^^^^ 内核认这个 ^^^^ 主组 ^^^^^^^^^^^^^^^^^^ 附加组
# 删掉一个用户后,它创建的文件会怎样?
ls -l /data/x
# -rw-r--r-- 1 1005 1005 0 Aug 6 20:00 /data/x
# ^^^^ ^^^^ 翻译不出名字了,直接显示数字
root 的特殊性也只是「uid == 0」。内核里大量的权限检查代码就是 if (uid == 0) return ALLOWED;。所以:
# 把一个普通用户的 uid 改成 0,他就是 root
grep "^hacker" /etc/passwd
# hacker:x:0:0::/home/hacker:/bin/bash
# ^ uid=0 -> 这就是一个后门账号
安全审计时检查「除 root 外还有谁的 uid 是 0」是必查项:
awk -F: '$3 == 0 {print $1}' /etc/passwd
# root <- 应该只有这一个
1.1 三个文件的分工
| 文件 | 存什么 | 权限 |
|---|---|---|
/etc/passwd |
用户名、uid、gid、家目录、登录 shell | 644 所有人可读 |
/etc/shadow |
密码哈希、密码有效期 | 640 或 000,只有 root 能读 |
/etc/group |
组名、gid、附加成员列表 | 644 |
为什么密码要单独拆到 shadow:/etc/passwd 必须所有人可读(因为 ls -l 要把 uid 翻译成名字),而密码哈希如果放在里面,任何用户都能拿去离线爆破。所以 1990 年代把哈希拆到了只有 root 能读的 shadow 里。这就是 passwd 第二列现在永远是个 x 的原因——那个 x 表示「密码在 shadow 里」。
head -1 /etc/passwd
# root:x:0:0:root:/root:/bin/bash
# | | | | | +- 登录 shell
# | | | | +--------- 家目录
# | | | +--------------- 描述(GECOS)
# | | +------------------ gid
# | +-------------------- uid
# +---------------------- 密码占位符
sudo head -1 /etc/shadow
# root:$6$xxxx$yyyy...:19800:0:99999:7:::
# | |
# | +- 哈希算法:$1=MD5 $5=SHA-256 $6=SHA-512 $y=yescrypt(现代默认)
# +---- 若是 ! 或 * 开头 = 该账号无法用密码登录
shadow 第二列以 ! 或 * 开头表示禁用密码登录。系统服务账号(nginx、mysql)都是这样——它们需要一个 uid 来跑进程,但绝不应该能登录。
1.2 系统账号与登录 shell
# 看看哪些账号能登录
awk -F: '$7 !~ /(nologin|false)$/ {print $1, $3, $7}' /etc/passwd
# root 0 /bin/bash
# lhx 1000 /bin/bash
# <- 其他的 shell 都是 /usr/sbin/nologin,登不进来
约定:uid < 1000 是系统账号,uid >= 1000 是普通用户(RHEL 系早期是 500 分界)。给服务创建账号时应该用 --system:
# ✅ 创建一个不能登录、没有家目录的服务账号
useradd --system --no-create-home --shell /usr/sbin/nologin myapp
# ❌ 用 root 跑业务服务
# 一旦服务被 RCE,攻击者直接拿到 root
生产建议:业务服务一律用专属的低权限系统账号运行,systemd unit 里写 User=myapp。第 14 篇会讲怎么配。
1.3 主组与附加组
id lhx
# uid=1000(lhx) gid=1000(lhx) groups=1000(lhx),27(sudo),999(docker)
- 主组(gid):写在
/etc/passwd第 4 列。你创建的文件默认属于这个组。 - 附加组:写在
/etc/group的成员列表里,用于获得额外权限。
# 加入 docker 组(能免 sudo 用 docker)
usermod -aG docker lhx
# ^^ -a 必须加!不加 -a 会【覆盖】所有附加组
坑:
usermod -G不加-a会把用户从所有未列出的附加组里踢出去。把自己踢出sudo组、又没有别的 root 途径,是能把自己锁在门外的。永远写-aG。
另一个坑:改组之后当前会话不生效。因为组成员关系是登录时写进进程凭证的,之后不再变。必须重新登录,或者:
newgrp docker # 开一个新 shell,带上新组
# 或者直接 exit 重新 ssh
「我明明加了 docker 组,为什么还是 permission denied」——答案基本都是这个。
2. 权限位:rwx 的两套语义
ls -l
# -rw-r--r-- 1 lhx dev 1024 Aug 6 20:00 file
# drwxr-xr-x 2 lhx dev 4096 Aug 6 20:00 dir
# |+++++++++
# | | | +-- other:其他所有人
# | | +----- group:属组成员
# | +-------- user:属主
# +---------- 类型:- 文件 d 目录 l 软链接 c/b 设备 s socket p 管道
关键:同样的 rwx 三个字母,在文件和目录上含义完全不同。
| 位 | 对文件 | 对目录 |
|---|---|---|
r |
读取内容 | 列出里面有哪些名字(ls) |
w |
修改内容 | 增删里面的条目(创建/删除/改名文件) |
x |
作为程序执行 | 进入、访问里面的文件(cd、open) |
2.1 最反直觉的一条:删文件看的是目录权限
mkdir d && echo hi > d/f && chmod 444 d/f # 文件设为只读
ls -l d/f
# -r--r--r-- 1 lhx lhx 3 Aug 6 20:30 d/f
rm d/f
# rm: remove write-protected regular file 'd/f'? y
# <- 只是问一下,回车就删掉了
为什么?因为「删除文件」的本质是从目录里删掉一条目录项(unlink,见第 2 篇),改的是目录的内容,不是文件的内容。所以内核检查的是目录的 w 权限,跟文件本身的权限毫无关系。
反过来也成立:
# 你对文件有全部权限,但对目录没有 w
sudo mkdir /srv/d && sudo chmod 755 /srv/d
sudo touch /srv/d/f && sudo chown lhx:lhx /srv/d/f && chmod 777 /srv/d/f
rm /srv/d/f
# rm: cannot remove '/srv/d/f': Permission denied
# <- 文件是你的、权限 777,但删不掉,因为目录 /srv/d 你没有 w
规律:能不能改文件内容看文件权限,能不能删/改名文件看目录权限。
这条规律的直接应用就是 /tmp 的 Sticky 位(§4.3)——所有人都能在 /tmp 里创建文件(目录有 w),但不能删别人的文件。
2.2 目录的 x 是「通行权」
chmod 644 dir # 有 r 无 x
ls dir
# f1 f2 <- 能列出名字
ls -l dir
# ls: cannot access 'dir/f1': Permission denied
# total 0
# -????????? ? ? ? ? ? f1 <- 拿不到元数据
cd dir
# bash: cd: dir: Permission denied
没有 x 就无法「穿过」这个目录去访问里面的任何东西,连 inode 的元数据都读不到(因为读元数据要先解析路径)。
反过来,只有 x 没有 r 是个有用的组合:
chmod 711 /home/lhx # rwx--x--x
# 别人不能 ls 你的家目录(不知道有哪些文件)
ls /home/lhx
# ls: cannot open directory: Permission denied
# 但如果知道确切路径,能访问
cat /home/lhx/public/readme.txt # ✅ 可以
这就是「知道名字才能访问」模式,Web 服务器的目录常这么配——/var/www 给 711,nginx 能穿过去读文件,但列不出目录内容。
路径上的每一层目录都需要 x。访问 /a/b/c/f 需要 /、/a、/a/b、/a/b/c 全都有 x。「明明文件权限是 777 却读不了」,答案通常是中间某层目录缺 x:
# 快速定位是哪一层挡住了
namei -l /var/www/html/index.html
# f: /var/www/html/index.html
# drwxr-xr-x root root /
# drwxr-xr-x root root var
# drwx------ root root www <- 就是这一层,other 没有 x
# drwxr-xr-x root root html
# -rw-r--r-- root root index.html
namei -l 是排查权限问题最快的命令,比一层层 ls -ld 高效得多。
2.3 八进制与符号法
r = 4 w = 2 x = 1
7 = rwx 6 = rw- 5 = r-x 4 = r-- 0 = ---
# 数字法:一次设定全部
chmod 755 script.sh # rwxr-xr-x
chmod 640 config.yaml # rw-r-----
chmod 600 ~/.ssh/id_ed25519 # rw------- 私钥必须这个权限
# 符号法:增量修改,不影响其他位
chmod +x script.sh # 给所有人加 x
chmod u+x script.sh # 只给属主加 x
chmod go-w file # 去掉组和其他人的 w
chmod a=r file # 所有人只读(= 是覆盖,+ 是追加)
chmod -R u+rwX dir/ # 大写 X:只给【目录】和【已有 x 的文件】加 x
大写 X 很实用:递归给目录加执行权限但不误伤普通文件。chmod -R 755 dir/ 会把所有 .txt 也变成可执行,而 chmod -R u+rwX,go+rX dir/ 不会。
常用的几组权限:
| 权限 | 用于 |
|---|---|
755 |
目录、可执行脚本、程序 |
644 |
普通文件、配置、静态资源 |
640 |
含敏感信息的配置(组内可读) |
600 |
私钥、密码文件 |
700 |
私有目录(~/.ssh) |
777 |
永远不要用,见下 |
777 为什么是错的:它意味着任何一个能登上这台机器的用户(包括被攻破的低权限服务账号)都能改你的文件。遇到权限问题就 chmod -R 777 是把安全边界彻底拆掉。正确做法是搞清楚需要访问的是谁,然后用属主或属组授权。
3. umask:新建文件的默认权限
创建文件时的权限不是固定的,而是**「基础权限 减去 umask」**:
文件基础权限 666(rw-rw-rw-) <- 注意:文件基础权限【没有 x】
目录基础权限 777(rwxrwxrwx)
实际权限 = 基础权限 & ~umask
umask
# 0022
touch f && mkdir d && ls -ld f d
# -rw-r--r-- 1 lhx lhx 0 Aug 6 21:00 f <- 666 - 022 = 644
# drwxr-xr-x 2 lhx lhx 4096 Aug 6 21:00 d <- 777 - 022 = 755
文件的基础权限是 666 而不是 777,这是内核的设计:新建文件默认不应该可执行,避免误把数据文件当程序运行。所以 umask 022 下新文件是 644 而不是 755。
常见的 umask 值:
| umask | 文件 | 目录 | 场景 |
|---|---|---|---|
022 |
644 | 755 | 默认,同组和其他人可读 |
027 |
640 | 750 | 推荐给生产服务,其他人完全无权限 |
077 |
600 | 700 | 最严格,只有自己能访问 |
002 |
664 | 775 | 团队协作目录(配合 SGID) |
生产建议:服务的 systemd unit 里显式设 UMask=0027。不要依赖继承来的 umask——cron、systemd、交互式 shell 的默认 umask 可能不同,导致「手动跑生成的文件权限对,定时任务生成的就不对」。
坑:
umask只影响新建的文件,对已存在的文件没有任何作用。而且它是进程属性,改了只对当前 shell 及其子进程生效。要持久化就写进/etc/profile、~/.bashrc或 systemd unit。
4. 三个特殊权限位
在 rwx 之外还有三个位,它们是权限模型里最容易被忽略、但在安全审计和团队协作中很关键的部分。
4.1 SUID:以文件属主的身份运行
只对可执行文件有效。带 SUID 的程序被执行时,进程的有效 uid 变成文件属主的 uid,而不是启动它的用户。
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 Nov 24 2022 /usr/bin/passwd
# ^ 这个 s 就是 SUID
为什么 passwd 需要 SUID:普通用户改自己的密码,要写 /etc/shadow,而那个文件只有 root 能写。SUID 让 passwd 这个程序在被任何人执行时都以 root 身份运行,从而能写 shadow——同时程序内部有逻辑保证你只能改自己的密码。
SUID 是提权漏洞的最大来源。一个有 SUID 的程序如果存在缓冲区溢出、或者能让用户指定任意命令,攻击者就直接拿到 root。所以:
# 安全审计:列出所有 SUID 程序
find / -perm -4000 -type f 2>/dev/null
# /usr/bin/passwd
# /usr/bin/sudo
# /usr/bin/su
# /usr/bin/mount
# <- 正常系统就这几个,多出来的要警惕
# 挂载用户可写的分区时禁用 SUID
mount -o nosuid,nodev,noexec /dev/vdb1 /data
nosuid 挂载选项是重要的加固手段:/tmp、/home、/data 这些用户可写的分区都应该带上,防止攻击者上传一个 SUID 程序。
4.2 SGID:继承目录的属组
对可执行文件:进程的有效 gid 变成文件的属组(用得少)。
对目录:在这个目录里新建的文件,属组自动继承目录的属组,而不是创建者的主组。这个才是实际用途。
# 团队共享目录:让所有人建的文件都属于 dev 组
mkdir /srv/share
chgrp dev /srv/share
chmod 2775 /srv/share # 2 = SGID
# ^ 或 chmod g+s /srv/share
ls -ld /srv/share
# drwxrwsr-x 2 root dev 4096 Aug 6 21:20 /srv/share
# ^ s 在 group 位
# 任何人在里面建文件
touch /srv/share/f && ls -l /srv/share/f
# -rw-rw-r-- 1 lhx dev 0 Aug 6 21:20 f
# ^^^ 属组是 dev,不是 lhx
SGID 目录 + umask 002 是团队共享目录的标准配方:SGID 保证属组统一,umask 002 保证组内可写,两者缺一不可。只配 SGID 而 umask 是 022 的话,文件属组虽然对了但组成员只能读不能写。
注意:SGID 只影响新建的文件。已经在目录里的旧文件不会自动变,需要
chgrp -R dev /srv/share手动修一次。
4.3 Sticky:只有属主能删
**只对目录有效。**设了 Sticky 的目录里,文件只能被文件属主(或目录属主、root)删除或改名,即使其他人对目录有 w 权限。
ls -ld /tmp
# drwxrwxrwt 10 root root 4096 Aug 6 21:30 /tmp
# ^ t 就是 Sticky
这正是 §2.1 那条规律的必要补丁:/tmp 权限是 777,所有人都能在里面创建文件——按「删文件看目录 w 权限」的规则,那所有人也都能删别人的文件。Sticky 位就是为了堵这个洞。
chmod 1777 /srv/upload # 1 = Sticky
# 或 chmod +t /srv/upload
任何「所有人可写的共享目录」都必须加 Sticky,否则任何用户都能删光别人的文件。
4.4 三个位的速查
| 位 | 八进制 | 显示位置 | 对文件 | 对目录 |
|---|---|---|---|---|
| SUID | 4000 | user 的 x 位 → s |
以属主身份运行 | 无效 |
| SGID | 2000 | group 的 x 位 → s |
以属组身份运行 | 新文件继承属组 |
| Sticky | 1000 | other 的 x 位 → t |
无效(历史遗留) | 只有属主能删 |
chmod 4755 f # SUID
chmod 2775 d # SGID
chmod 1777 d # Sticky
chmod 6755 f # SUID + SGID
大小写的含义:如果原本那一位有 x,特殊位显示为小写 s/t;如果原本没有 x,显示为大写 S/T。
chmod 4644 f && ls -l f
# -rwSr--r-- <- 大写 S:设了 SUID 但文件根本不可执行 = 配错了
看到大写的 S 基本就是配置错误,因为 SUID 对不可执行的文件毫无意义。
5. ACL:超越 ugo 的细粒度授权
传统的 ugo 模型只能表达「属主 / 一个组 / 其他人」三种身份。当需求是「用户 A 可读写,用户 B 只读,其他人无权限」时,ugo 就不够用了——总不能为每种组合都建一个组。
ACL(Access Control List)解决这个问题:
# 查看 ACL
getfacl report.xlsx
# user::rw-
# group::r--
# other::---
# 给单个用户额外授权
setfacl -m u:alice:rw report.xlsx
setfacl -m u:bob:r report.xlsx
setfacl -m g:audit:r report.xlsx
getfacl report.xlsx
# user::rw-
# user:alice:rw- <- 额外的用户条目
# user:bob:r--
# group::r--
# group:audit:r--
# mask::rw- <- 上限,见下
# other::---
设了 ACL 的文件,ls -l 会在权限位后面显示一个 +:
ls -l report.xlsx
# -rw-r-----+ 1 lhx dev 8192 Aug 6 21:40 report.xlsx
# ^ 有 ACL
mask 是个坑:它是所有「命名用户 / 组条目」权限的上限。chmod g-w 实际上改的是 mask,会同时削掉所有 ACL 条目的写权限,看起来像 ACL 失效了:
chmod g-w report.xlsx
getfacl report.xlsx | grep alice
# user:alice:rw- #effective:r-- <- 实际生效的降级了
目录的默认 ACL(-d)让新建文件自动继承,这才是 ACL 真正好用的地方:
setfacl -d -m u:alice:rw /srv/data # 之后在这里新建的文件 alice 都能读写
setfacl -R -m u:alice:rw /srv/data # -R 修改已有的
# 删除
setfacl -x u:bob file # 删单条
setfacl -b file # 清空所有 ACL
注意:
cp默认不保留 ACL,cp -a(或--preserve=all)才保留。tar需要--acls参数。rsync需要-A。备份脚本忘了这些参数,恢复后权限会静默丢失。
6. sudo:受控的提权
sudo 的核心价值不是「变成 root」,而是**「可审计、可限定范围的提权」**。
# 查自己能干什么
sudo -l
# User lhx may run the following commands on host:
# (ALL : ALL) ALL
# 配置必须用 visudo(它会做语法检查,防止把自己锁死)
sudo visudo
配置语法:
# 用户 主机=(可切换到的用户:组) 命令
root ALL=(ALL:ALL) ALL
%sudo ALL=(ALL:ALL) ALL # % 开头表示组
实际生产中应该做最小授权,而不是给 ALL:
# /etc/sudoers.d/deploy <- 放在 sudoers.d 下,别直接改主文件
# 部署账号只能重启这一个服务,且不需要输密码
deploy ALL=(root) NOPASSWD: /bin/systemctl restart myapp, /bin/systemctl reload myapp
# 运维组可以查看所有日志
%ops ALL=(root) NOPASSWD: /usr/bin/journalctl, /usr/bin/tail /var/log/*
坑:给用户 sudo 执行任何能「跳出去」的程序,等于给了完整 root。比如
sudo vim(:!sh直接拿 shell)、sudo find(-exec sh \;)、sudo less(!sh)、sudo tar(--to-command)。授权时要选择那些无法执行任意命令的程序。
sudo 相关的两个常见问题在第 1 篇讲过:secure_path 会重置 PATH(用绝对路径或 sudo -E env "PATH=$PATH"),sudo echo $PATH 看到的是当前用户的 PATH 而不是 sudo 的。
审计日志在这里:
grep sudo /var/log/auth.log | tail # Debian/Ubuntu
journalctl _COMM=sudo -n 20 # systemd 通用
# lhx : TTY=pts/0 ; PWD=/home/lhx ; USER=root ; COMMAND=/bin/systemctl restart nginx
7. 容器里的权限:uid 是同一套
这是后端最常撞的一类权限问题,而且现象很迷惑。
核心事实:容器没有独立的 uid 空间(除非显式开了 user namespace)。容器里的 uid 1000 和宿主机的 uid 1000 是同一个数字,内核在检查挂载卷的权限时用的就是这个数字。
# 容器里以 root(uid 0)运行,往挂载卷写文件
docker run -v /data/out:/out alpine sh -c 'echo hi > /out/f'
# 回到宿主机看
ls -l /data/out/f
# -rw-r--r-- 1 root root 3 Aug 6 22:00 f
# ^^^^ 属主是宿主机的 root,你的普通账号删不掉
反过来更常见——容器里以非 root 运行,挂载卷写不进去:
docker run -u 1000:1000 -v /data/out:/out myapp
# Error: permission denied: /out/app.log
ls -ld /data/out
# drwxr-xr-x 2 root root 4096 Aug 6 22:00 /data/out
# <- 目录属主是宿主机 root,uid 1000 没有 w
解决办法:
# ✅ 方案一:宿主机上把目录属主改成容器里的 uid
chown -R 1000:1000 /data/out
# ✅ 方案二:让容器用宿主机当前用户的 uid 运行
docker run -u "$(id -u):$(id -g)" -v /data/out:/out myapp
# ✅ 方案三(K8s):用 fsGroup,kubelet 会自动 chown 挂载卷
# K8s 里的标准写法
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000 # 挂载卷的属组会被设为 1000,且加 SGID
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
**规律:容器里的权限报错,先看宿主机上那个目录的属主 uid 和容器进程的 uid 对不对得上。**用数字比对,别看名字——容器和宿主机的 /etc/passwd 不是同一份,同一个 uid 在两边可能翻译成完全不同的名字。
为什么不要用 root 跑容器:容器的隔离靠的是 namespace 和 cgroup(第 19 篇会讲),不是虚拟机级别的隔离。容器里的 root 一旦通过内核漏洞或错误的挂载配置逃逸,拿到的就是宿主机的 root。runAsNonRoot: true 应该是默认配置。
8. 面试题
Q:一个文件权限是 444(只读),为什么我还能把它删掉?
因为删除文件改的是目录的内容,不是文件的内容。rm 底层是 unlink,它从目录里移除一条「文件名 → inode」的映射,所以内核检查的是目录的写权限,与文件自身权限无关。反过来,一个属于你、权限 777 的文件,如果它所在目录你没有 w 权限,你也删不掉它。这也是 /tmp 必须加 Sticky 位的原因——否则 777 的 /tmp 里谁都能删别人的文件。
Q:目录的 r 和 x 权限分别是什么意思?只有 r 没有 x 会怎样?
目录的 r 是「能列出里面有哪些名字」,x 是「能穿过这个目录去访问里面的东西」。只有 r 没有 x 时,ls 能列出文件名,但 ls -l 拿不到任何元数据(显示一堆 ?),cd 进不去,里面的文件也全都读不了。反过来只有 x 没有 r 是有用的配置(如 711):别人列不出目录内容,但知道确切路径就能访问,Web 目录常这么配。注意:访问 /a/b/c/f 需要路径上每一层目录都有 x,排查用 namei -l /a/b/c/f 最快。
Q:SUID 是什么?为什么 passwd 需要它?有什么风险?
SUID 让可执行文件在被任何人运行时,进程的有效 uid 变成文件属主的 uid。passwd 需要写 /etc/shadow(只有 root 可写),但普通用户要能改自己的密码,所以它被设为 SUID root。风险在于这是提权漏洞的主要入口——SUID 程序一旦有溢出、命令注入或能让用户指定任意命令的路径,攻击者直接获得 root。加固手段是定期审计 find / -perm -4000 -type f,以及给用户可写的分区加 nosuid 挂载选项。
Q:umask 022 为什么新建文件是 644 而不是 755?
因为文件的基础权限是 666,不是 777。内核规定新建文件默认不带执行权限,避免数据文件被误当作程序运行。所以文件是 666 & ~022 = 644,而目录的基础权限是 777,777 & ~022 = 755。注意:umask 只影响新建文件,对已有文件无效;它是进程属性,会被子进程继承,所以 cron 和 systemd 下的 umask 可能与交互式 shell 不同,导致「手动跑权限对、定时任务权限不对」。
Q:怎么做一个多人共享的目录,让所有人建的文件组内都能读写?
需要两件事配合:① 给目录设 SGID(chmod 2775 dir + chgrp team dir),让新建文件的属组自动继承目录的属组,而不是创建者的主组;② 把相关用户的 umask 设为 002,否则新文件是 664 中的组权限会被 umask 削掉变成只读。只做第一步的话,文件属组虽然对了但组成员只能读。如果目录还允许所有人写(如上传目录),必须再加 Sticky 位 防止互删。更细粒度的需求(用户 A 读写、B 只读)用 ACL 的默认条目:setfacl -d -m u:alice:rw dir。
Q:usermod -G docker lhx 有什么问题?
-G 不加 -a 是覆盖语义——会把用户从所有未在命令中列出的附加组里移除。如果这个用户原本在 sudo 组里,这条命令会把他踢出去,且如果没有其他 root 途径就锁死了。正确写法是 usermod -aG docker lhx。另外改组之后当前会话不生效,因为组成员关系是登录时写入进程凭证的,必须重新登录或用 newgrp 开新 shell——「加了 docker 组还是 permission denied」几乎都是这个原因。
Q:容器里以非 root 用户运行,往挂载卷写文件报 permission denied,为什么?
因为容器没有独立的 uid 空间(未开 user namespace 时),容器里的 uid 1000 就是宿主机的 uid 1000,内核用这个数字去检查宿主机目录的权限。如果挂载目录在宿主机上属主是 root、权限 755,uid 1000 自然没有写权限。解决方式:宿主机 chown 1000:1000 那个目录,或用 -u "$(id -u):$(id -g)" 让容器以宿主机当前用户运行,K8s 里用 fsGroup(kubelet 会自动调整挂载卷属组)。注意:排查时要比对 uid 数字而不是用户名,容器和宿主机的 /etc/passwd 是两份,同一个 uid 两边可能显示成不同名字。
Q:给运维同事 sudo vim /etc/nginx/nginx.conf 的权限,安全吗?
不安全,等于给了完整 root。vim 里可以 :!sh 直接开一个 root shell,或者 :w /etc/sudoers 写任意文件。同类的还有 sudo find(-exec sh \;)、sudo less/man(!sh)、sudo tar(--to-command)、sudo awk(system())。授权原则是只允许那些无法执行任意命令、无法写任意路径的程序,比如 systemctl restart 特定服务、journalctl。如果确实需要编辑配置,用 sudoedit(sudo -e)——它把文件复制到临时位置用普通权限编辑,写回时才提权,且不会让编辑器以 root 运行。
上一篇:Linux-02 文件、链接与归档压缩 | 下一篇:Linux-04 文本处理三剑客与正则
xingliuhua