Linux 系统管理入门:用户、权限与 sudo

掌握 Linux 用户组、文件权限和 sudo 配置,建立最小权限的系统管理基础。

技术文章约 21 分钟190 次阅读

#这篇文章解决什么问题

刚接手一台多用户 Linux 服务器时,最容易犯的错误就是「先 root 干起来再说」:所有操作都挂在 root 名下,等业务跑起来、用户也多起来,再想收紧权限,成本已经高得几乎无法承受。这篇文章把「账号、文件权限、sudo 提权」三件事从底层机制逐层推导,解决「如何让每个用户只能做被允许的事」;它背后最基础的事实只有三条:内核不认用户名,只认数字身份 UID/GID;一个文件能否被访问,由存储它的 inode 上的权限位(必要时的 ACL)决定;sudo 之所以能提权,是因为 sudo 程序本身带着 setuid 位、能以 root 身份读取 sudoers 规则再裁定放行谁。把这三条因果链想清楚,后面的每条命令都是可推导的结果,而不是要背的口诀。

图景可以概括为:身份(UID/GID)决定「你是谁」,权限位(inode mode 与 ACL)决定「你能碰什么」,sudo(setuid 程序 + sudoers 规则)决定「你能以谁的身份执行什么命令」。下面沿因果链展开,落地为一套可复现的流程与验证方法。

#原理层:身份、权限、提权三条因果链

#身份:内核只认 UID 与 GID

#用户信息落在三个文件里,但内核只用数字

Linux 把「用户」的信息分散存放,每一份都有明确理由:

  • /etc/passwd:可读的账号映射表,一行一条记录。以 deploy 用户为例:
deploy:x:1000:1000:deploy:/home/deploy:/bin/bash

字段依次是:登录名、密码占位符(x)、UID、主 GID、注释(GECOS)、家目录、登录 shell。

  • /etc/shadow:仅 root(及 shadow 组)可读的密码哈希与策略文件:
deploy:$y$j9T...:19788:0:99999:7:::

字段依次是:登录名、哈希值、最近一次改密日、密码最小使用天数、最大使用天数、警告期、宽限期、过期日、保留位。

  • /etc/group:组定义,形式与 passwd 类似:
sudo:x:27:deploy

这里最值得注意的设计是:密码哈希为什么要从 /etc/passwd 挪到 /etc/shadow。因为 id、ls -l 显示用户名、getent 查询都要读 /etc/passwd,它必须对所有人可读;而密码哈希一旦泄露,就能被离线爆破工具撞库。于是系统把哈希挪到普通用户不可读的文件里,只允许认证进程(如 login、su)在必要时读取——这就是「最小权限」思想在系统自身上的一次落地。哈希前缀也有讲究:$6$ 是 SHA-512,$y$ 是 yescrypt;较新的发行版(Debian 12、Ubuntu 22.04 起)默认用 yescrypt,更早的系统常见 SHA-512。

普通用户能不能读 /etc/shadow,取决于它的权限位(下一节详细讲)。Debian/Ubuntu 常见默认是 -rw-r-----(属主 root、属组 shadow,用户须在 shadow 组才能读);RHEL 系常见是 ----------(root:root)。无论如何,普通用户都不应该读得到。

#查询用户要走 NSS,而不是直接读文件

getent 按 /etc/nsswitch.conf 配置的顺序查询(files、ldap、sss……),不止读本地文件。所以一台接入了 LDAP/AD 的机器上,getent passwd deploy 能返回记录,而 cat /etc/passwd 看不到——判断「一个用户是否存在」要用 getent,而不是读文件。这是排查「用户明明存在却登录不上」时的第一检查点。

#进程的身份:real / effective / saved

内核创建进程时,在进程控制块里保存一组凭证(credential):

  • real UID(ruid):这个进程是谁启动的;
  • effective UID(euid):内核做权限检查时真正使用的身份(open、kill、chmod 等系统调用都按 euid 判定);
  • saved UID(suid):exec 了一个带 setuid 位的程序后,用来把身份恢复原样的保存值;
  • GID 与 effective GID、saved GID 同理;
  • supplementary groups(补充组):登录时由 initgroups 依据 /etc/group 装载,之后 id 命令显示的 groups 列表就是它。

login、su 在登录时把进程身份设置好,之后 fork/exec 的子进程原样继承这些凭证。绝大多数时候 ruid == euid,「当前用户」就是 id 输出的 uid;但一旦 exec 了 setuid 程序,两者就会分离——这正是后面 sudo 的机制起点。sudo 执行完命令后退出,你的 shell 身份不受影响——身份变化发生在子进程里。

#文件访问:inode 上那 12 个位

#权限信息存在 inode 里,文件名只是目录项

文件系统里有个容易被忽略的事实:文件名存在目录里,文件本身(内容与元数据)存在 inode 里。ls -l 显示的权限、属主、属组、时间戳,都是从 inode 的 mode 字段读出来的。inode 的 mode 低 12 位,就是我们要说的全部:3 位特殊权限位(setuid / setgid / sticky)+ 9 位 rwx(owner / group / other 各 3 位)。

数字表示法:r=4、w=2、x=1,相加得到一段八进制数。755 就是 owner 7(rwx) + group 5(r-x) + other 5(r-x)。

#九位 rwx 的匹配顺序:先命中,后生效

内核判定「这段权限对哪个用户生效」时,按严格顺序检查:

  1. 若进程 euid == inode 属主 UID → 只看 owner 段;
  2. 否则若 egid 或任一补充组 == inode 属组 GID → 只看 group 段;
  3. 否则 → 只看 other 段。

先匹配即生效,不会三段叠加,也不会取交集取并集。 这个顺序是 ACL 和多用户环境里很多「怪现象」的根源。例如:

  • 文件 -rw-r-----(owner rw、group r、other 无)时,属主对该文件有完整 rw,group 段更严格也影响不到属主;
  • 反过来,文件 777 也不会让属主「被削弱」,other 给得再多只影响第三类人。

所以分析权限问题时,第一问永远是「这个进程现在以什么 euid/egid 在跑」,第二问才是「它对目标文件命中哪一段」。很多「明明加了组还是没权限」的报错,根因都是补充组没刷新(登录时装载,运行中的进程不会自动获得新组)。

#文件与目录的 rwx 语义不同

这是 Linux 权限里最大的坑:

  • 文件:r 读内容,w 改内容,x 可执行;
  • 目录:r 可列出目录项,w 可在此目录中创建、删除、改名条目,x 可穿过(path 解析能到达其中条目,即能 cd 进入、能 stat/打开里面的文件)。

由此推出几条关键结论:

  • 删除一个文件,看的是它所在目录的写权限,不是文件自己的写权限。文件 -rw-r--r-- 也可能被删,只要所在目录对你有 w。这解释了多用户共享目录里「我没动文件,文件却没了」的纠纷。
  • 目录至少要有 x,owner 才能进去。目录 644(rw-r--r--)意味着连属主都无法 cd 进入、使用其中文件,因为缺 x 无法做 path 解析。
  • 目录 755 = owner 完全控制 + 其他人可进、可读、不可写。对家目录和大多数共享目录,这是合理默认;
  • 目录 700 = 仅属主可进可读可写,适合存放密钥、私人脚本的目录。

#为什么 chmod 777 是坏默认

chmod 777 的问题不在于「7」这个数字,而在于它把 other 段也给了 rwx:

  • 对普通文件:意味着任何能登录这台机器的人都能改写它(如业务脚本、配置文件),这是后门级别的风险;
  • 对目录:意味着任何用户都能在共享目录里创建、删除、改名他人的条目。

普通文件的合理范围通常是 664(属主读写、属组读写、他人只读)或 644(属主读写、其余只读);目录至少 755。为什么「至少」?因为没有 x 目录根本进不去,连属主自己的正常使用都受影响。

#ACL:九位不够时的扩展

传统九位只能表达 owner / group / other 三类,当需要一个目录同时给两个组不同权限时就不够了。Posix ACL(Access Control List)是九位的扩展,条目存在 inode 的扩展属性 system.posix_acl_access 里,用 getfacl / setfacl 操作。典型例子:

sudo setfacl -m u:alice:rwx /srv/data
sudo setfacl -m g:ops:r-x /srv/data
getfacl /srv/data

ACL 的判定顺序在九位基础上扩展为:属主 → 命名的用户条目 → 属组 / 命名的组条目 → mask → other。其中 mask 限制所有命名用户与命名组条目的最大权限,某条 ACL 即使写了 rwx,也要与 mask 求与后才能生效。两个容易踩的点:

  • setfacl 之后 ls -l 的 group 段显示的其实是 mask(例如 -rw-r----- 里的 r-- 是 mask 而不是真实属组权限),要看真相必须 getfacl;
  • 匹配顺序同样是「先命中」,属主不受自己那条 ACL 的 mask 限制。

#三个特殊位:setuid、setgid、sticky

  • setuid(4000):文件被 exec 时代进程 euid 变为文件属主 UID。典型例子:sudo、passwd、ping(早期版本)。这是本章最重要的机制,下一节展开。
  • setgid(2000):文件被 exec 时代进程 egid 变为属组;目录上设置后,新创建的文件与子目录自动继承目录的属组,对共享目录(如 /srv/www)很重要,能避免「不同用户创建的文件组归属混乱」。
  • sticky(1000):目录上设置后,只有文件属主、目录属主、root 能删除或改名其中的条目。/tmp 就是 1777:任何用户都能往里写,但谁也删不了别人的临时文件。

一个目录同时给属主完全控制、组可读写、其他人只能进、且防互删,可以表达为 2775(setgid + 775)或 1777(共享临时)。这些组合不是「魔法数字」,按位拆开就能读懂。

#符号链接与 umask

  • 符号链接的权限位对链接本身没有意义(ls -l 恒显示 lrwxrwxrwx),访问跟随目标文件。给链接 chmod 没有直接效果,要操作目标文件。
  • umask 决定新文件/目录的初始权限:创建权限 =(0666 或 0777)& ~umask。默认 022 得文件 644、目录 755;umask 077 更严格,适合隐私环境。ls -ld 看到家目录 755 就是这么来的。

#提权:sudo 是「setuid root 的裁判」,不是「万能钥匙」

#sudo 二进制本身就是 setuid root

用 ls -l /usr/bin/sudo 能看到:

-rwsr-xr-x 1 root root 232416 ... /usr/bin/sudo

owner 执行位的 s 就是 setuid 位(4755):任何用户执行它,进程 euid 立即变成 0(root)。这就是 sudo 能提权的物理基础——内核在 exec 时替它完成了身份切换,不需要任何密码。

但拿到 root 身份后,sudo 做的第一件事不是给你 shell,而是读取 /etc/sudoers(root 可读、普通用户不可读),按规则裁定:「发起者是否被允许以目标用户(默认 root)执行指定命令」。裁定通过后,sudo 才 setuid/setgid/initgroups 把身份切到目标用户,再 exec 用户请求的命令。所以 sudo 的本质可以概括为:一个 setuid root 的程序,加上一份只有 root 能改的裁决规则;提权能力来自 setuid 位,授权范围来自 sudoers。

#sudoers 规则:谁、在哪台机器、以什么身份、执行什么命令

sudoers 每一条规则表达四要素:

谁       主机=(以谁的身份)  命令列表
%ops     ALL=(ALL:ALL)     /usr/bin/systemctl, /usr/bin/journalctl
  • 谁:用户名,或 %组名(%ops 表示 ops 组所有成员);
  • 主机:单机服务器保持 ALL 即可;
  • (ALL:ALL):可以以任意用户:任意组身份执行(runas);只写 (ALL) 表示任意用户身份;
  • 命令列表:以逗号分隔的绝对路径白名单,支持通配符。

关键规则是:首条命中即生效。sudo 从头到尾逐行匹配,不叠加也不合并,命中的第一条(无论是允许还是拒绝)就是最终结果。因此精确规则必须写在宽泛规则前面,否则会被前面的宽泛规则吞掉。

#为什么 sudo 比「直接给 root 密码」和「setuid 脚本」好

  • 审计:每次 sudo 执行都记入系统日志(authpriv facility),journalctl _COMM=sudo 可查;
  • 细粒度:按命令、按参数、按用户放行,最小授权可控;
  • 时间戳:默认缓存凭证 15 分钟(timestamp_timeout),sudo -k 可主动作废;
  • 回收:改规则即时生效,无需改密码。

相比之下,直接分发 root 密码意味着「知道密码 = 全部权限」,且无法审计;给脚本加 setuid 等于开一条无审计的提权通道(内核本来就忽略脚本上的 setuid 位,见「边界与坑」一节)。这也是「真需要提权就好好用 sudo,按命令白名单给」这句经验背后的全部理由。

#操作层:完整可复现的实施流程

环境声明:以下命令以 Ubuntu 22.04 LTS 与 Debian 12(bookworm) 上的验证为准;RHEL 9 / Rocky / AlmaLinux 的差异单独标注。前置条件:拥有 root 权限,或当前账号已在 sudo 组;先运行 sudo -l 确认能列出至少一条可执行规则,否则下面的命令同样不可用。

#1. 创建用户:useradd 不只是创建账号

创建用户时,推荐这样写:

sudo useradd --create-home --shell /bin/bash --groups sudo deploy
sudo passwd deploy

逐参数说明:

  • --create-home:生成 /home/deploy 并复制 /etc/skel 骨架文件(.bashrc、.profile 等)。Debian/Ubuntu 的 useradd 默认不建家目录,漏掉它之后,很多软件默认往家目录写配置时会直接报错(SSH 密钥目录、~/.bashrc、定时任务日志等都会出问题);
  • --shell /bin/bash:设定登录 shell,避免落到默认 sh 的交互体验;
  • --groups sudo:把 deploy 加入 sudo 组(作为补充组)。Debian 系安装 sudo 包时,/etc/sudoers 默认自带 %sudo ALL=(ALL:ALL) ALL,加进这个组就等于拿到 sudo 白名单基础权限;RHEL 系对应的提权组是 wheel,且默认在 sudoers 里是注释状态,需要 root 手工放开;
  • 随后 passwd deploy 交互式设置密码。若要走纯密钥登录,可以跳过密码,用 sudo passwd -l deploy 直接锁定密码,见第 5 节自检清单。

useradd 默认的 umask 是 022,因此家目录创建出来是 755(drwxr-xr-x,别人能进入但写不了)——这是合理的默认值:deploy 自己的文件默认 644/755,同机其他用户能读不能改。别手贱改成 777,否则其他用户可以直接改写 deploy 的代码和配置。

RHEL 系差异:useradd 默认创建家目录,但提权组是 wheel;用户已存在且漏加组时不必重建,用 usermod 补:

sudo usermod -aG sudo deploy   # Debian 系补 sudo 组
sudo usermod -aG wheel deploy  # RHEL 系补 wheel 组

如果某天发现用户登录后没有家目录、提示 Could not chdir to home directory,多半是当初漏了 --create-home。Debian 系可以直接用 pam 提供的 helper 补上:

sudo mkhomedir_helper deploy

#2. 落盘检查:创建之后先确认

创建之后用这几条命令确认落盘情况:

id deploy                 # uid/gid/所属组
getent passwd deploy      # 来自 NSS 的记录
ls -ld /home/deploy       # 预期 drwxr-xr-x

预期输出:

uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo)
deploy:x:1000:1000:deploy:/home/deploy:/bin/bash
drwxr-xr-x 2 deploy deploy 4096 ...

id 输出里能同时看到主组(Debian/Ubuntu 默认创建与用户同名的私有组)与补充组 27(sudo)。若 groups 里没有 sudo,说明加组失败或需要重新登录——补充组在登录时装载,现有会话不会自动获得新组,验证时记得重新登录。

#3. 配置 sudoers:白名单比 ALL 好得多

编辑 /etc/sudoers 要用 visudo(负责加锁并做语法校验,防止写坏文件把自己锁在门外):

sudo visudo

更推荐的做法是把自定义规则写进 /etc/sudoers.d/ 片段,避免动主文件(要求 /etc/sudoers 末尾有 #includedir /etc/sudoers.d,Debian/Ubuntu 默认有;片段权限须 0440 且属主 root):

sudo visudo -f /etc/sudoers.d/ops

给 ops 组的最小规则示例:

%ops ALL=(ALL:ALL) /usr/bin/systemctl, /usr/bin/journalctl, /bin/systemctl
%ops ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart blog-app

逐项解释:

  • %ops:组前缀;不带 % 的裸词是用户名;
  • 第一个 ALL:任意主机(单机服务器保持);
  • (ALL:ALL):可以以任意用户:任意组身份执行;(ALL) 只写一侧表示任意用户身份;
  • 命令列表:以逗号分隔的绝对路径白名单。Debian/Ubuntu 在 usrmerge 之后 /bin 与 /usr/bin 是同一目录的硬链接/符号链接,所以 systemctl 的两条路径都写上,避免「没有那个文件或目录」;
  • NOPASSWD:执行该命令时不要求再输密码。只给确无风险的命令。注意 systemctl restart blog-app 是精确匹配:参数不允许换成别的服务;若确实要放行任意服务的重启,写 systemctl restart * 而不是 ALL;
  • 千万不要写 %ops ALL=(ALL) ALL——那不是授权,是把 root 分发出去。给一组命令就够了(重启服务、查日志),这就是运维人员日常 90% 的需求。

规则多了以后,用命令别名组织更清晰:

Cmnd_Alias SERVICE = /usr/bin/systemctl restart *, /usr/bin/systemctl stop *, /usr/bin/systemctl start *
%ops ALL=(ALL) SERVICE

写规则时记住三点:首条命中、精确规则在前;命令一律写绝对路径(secure_path 的原因见「边界与坑」);NOPASSWD 只给确无风险的命令,给出去容易、收回来难,该收就收。

#4. 验证 sudo 规则:分两步,缺一不可

改完 sudoers,验证分两步:

sudo visudo -c                    # 语法检查全部 sudoers 文件
sudo -u deploy sudo -l            # 以 deploy 身份列出可执行命令
  • sudo visudo -c:输出 ... parsed OK 才算通过,语法错误会阻止后续加载;
  • sudo -u deploy sudo -l:以 deploy 身份列出它可执行的命令,逐条核对白名单是否与预期一致;也可以用 sudo -l -U deploy(需要你有足够权限)查看指定用户的规则;sudo -u deploy sudo -l -l 看长格式。

验证之后,真实执行一条被允许的命令做冒烟测试:

sudo -u deploy sudo /usr/bin/systemctl status blog-app
sudo -u deploy sudo /usr/bin/rm /etc/passwd   # 预期:拒绝

第一条应成功、第二条应拒绝,才说明「该放的放、不该放的不放」两条都成立。

#5. 关闭密码登录前的自检清单

计划用密钥登录、关闭 SSH 密码登录之前按下列顺序做,顺序本身是逃生设计,不要跳步:

  1. 保持当前会话不关,另开一个终端会话用新密钥登录,确认能进:
ssh -i ~/.ssh/deploy_ed25519 deploy@server

能拿到 shell 才算第一关通过;当前会话留着当逃生通道。

  1. 确认新账号 sudo 能执行所需命令:sudo -l 的输出逐条核对,并真实执行一次关键动作(如重启服务、查日志)。

  2. 确认 /etc/shadow 对普通用户不可读:

ls -l /etc/shadow
namei -l /etc/shadow

判定标准是 other 段没有 r。发行版默认值不同:Debian/Ubuntu 常见 -rw-r-----(属主 root、属组 shadow),RHEL 系常见 ----------(root:root);原稿经验值是 -rw-------(仅 root 可读),更严格。无论哪种,普通用户都不应读得到。namei -l 逐级列出路径每层的属主与权限,精确回答「哪一层目录挡住了访问」。

  1. 确认无误后再改 sshd_config,先备份、再检查、最后 reload:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F)
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sshd -t
sudo systemctl reload ssh       # Debian/Ubuntu;RHEL 系服务名是 sshd

sshd -t 是配置语法检查,无输出即通过;通过后才 reload。reload 只重载配置、不中断现有连接,所以第 1 步保留的会话依然可用。

「先备份再改、验证再 reload、保留逃生会话」这三条习惯,是这条链路里最有价值的部分:所有把生产环境「锁死在外面」的事故,都是跳过了其中一条。

#验证层:怎么确认我做对了

把「看起来对」变成「确实对」,用命令与预期输出说话。

身份验证:

id deploy                # uid/gid/补充组,groups 里应有 sudo
getent passwd deploy     # NSS 视角的记录,应返回一行完整记录
getent group sudo        # 确认 sudo 组存在
groups deploy            # 组归属

文件权限验证:

ls -ld /home/deploy                    # 预期 drwxr-xr-x
stat -c '%a %U %G %n' /home/deploy     # 预期 755 deploy deploy
namei -l /etc/shadow                   # 逐级列出 owner 与权限,定位"哪层挡路"
getfacl /srv/data                      # ACL 全貌:mask 与命名条目

对「某个用户能否读/写某文件」这类问题,最可靠的办法是切到该用户身份直接试,而不是靠肉眼数位:

sudo -u deploy test -r /var/log/journal && echo readable
sudo -u deploy cat /etc/sudoers 2>&1 | head -1   # 预期出现 Permission denied

sudo 验证:

sudo visudo -c                            # 预期 parsed OK
sudo -l -U deploy                         # 列出 deploy 全部规则,逐条核对
sudo -k                                   # 清空时间戳缓存,验证下次执行需要密码
sudo -u deploy sudo /usr/bin/systemctl status blog-app   # 白名单内:成功
sudo -u deploy sudo /usr/bin/rm /etc/passwd 2>&1         # 白名单外:拒绝

全局检查 setuid/setgid 文件(排查意外的提权通道):

find / -xdev -type f -perm /6000 -ls

预期输出是一份你能解释来历的清单(sudo、mount、su、passwd、ping 等系统程序);多出陌生条目就是告警信号。

把判定标准汇总成一句话:身份看 id/getent,权限看 stat/getfacl/namei,sudo 看 visudo -c + sudo -l + 真实试运行。任何一项与预期不符,先回到原理层定位是「哪一段身份匹配错了」还是「哪条规则命中了」,而不是盲目改权限。

需要审计 sudo 历史时:

journalctl _COMM=sudo --since today

每次 sudo 成功或失败都会留下记录,是多用户服务器追责与复盘的第一手材料。

#边界与坑

  1. chmod -R 修权限是最贵的一课。 我见过最疼的一次事故:有人为了「省事」把 /etc 整个 chmod -R 777,结果 sudo 直接罢工。原因要从机制上讲:sudoers 有硬性权限要求——属主 root、仅 root 可写(常见 0440),一旦 /etc/sudoers 变成「任何人可写」,sudo 宁可不工作也不冒险在不可信配置下提权。最后只能进单用户模式(或通过云控制台 VNC/救援模式)恢复属主与权限。所以:永远不要用 chmod -R 去修权限问题;权限问题要先定位「是谁(哪个用户/组)、在哪一段(owner/group/other 还是 ACL)、为什么被拒」,再改最小范围。

  2. 对脚本用 setuid(4755)几乎总是坏主意。 原因分两层。第一层是机制:Linux 内核忽略 shebang 脚本上的 setuid 位——脚本由解释器(如 /bin/bash)执行,解释器的运行身份由内核决定,脚本文件上的 s 位并不会被应用,这条路从内核层面就不通。第二层是即便用编译好的二进制或包装器强行实现「以属主身份运行」,也等于给所有能执行它的人开一条无审计的提权通道,而且没有 sudoers 那样的白名单与审计。真需要提权就好好用 sudo,按命令白名单给。

  3. 删除文件是目录权限问题,不是文件权限问题。 ls -l 显示 -rw-r--r-- 的文件也可能被删——只要所在目录对你有 w。多用户共享目录里互相删文件,解决办法是目录设 1777(sticky,如 /tmp)或收紧目录属组,而不是抠文件自己的权限位。

  4. 目录 755 与 700 的选择。 755 是合理默认(别人能进不能写);但家目录里放密钥、私人脚本时,改成 700 更符合最小权限。判断标准不是「数字好不好看」,而是「还有谁需要访问这里」:需要被 web 服务或其他用户读取的目录给 r-x,完全私有的给 700。

  5. NOPASSWD 给出去容易,收回来难。 用户会形成「重启服务不用密码」的习惯,一旦安全策略收紧就会抱怨——但该收就收:从 sudoers 删掉对应行,reload 后即时生效。给 NOPASSWD 前先自问两个问题:这条命令万一被滥用,后果是否可接受?被滥用的面有多宽(任意参数?任意服务?)?两个答案有一个不放心,就要求输密码。

  6. PATH 劫持与相对路径。 sudoers 里写命令必须用绝对路径;同时 sudoers 默认的 secure_path(Ubuntu 上是 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin)保证在受限 PATH 下解析命令,防止攻击者在当前目录放同名程序劫持 root 执行。自己写脚本调用系统命令时同理:一律写绝对路径,或用 env -i 起干净环境。查看当前 secure_path:

sudo grep -ri secure_path /etc/sudoers /etc/sudoers.d/
  1. sudoers 首条命中带来的误配。 规则顺序错了,宽泛规则先命中,后面的精确规则永不生效;拒绝规则也可以写在前头做「默认拒绝」再逐条放行。改完必须 sudo visudo -c + sudo -l + 真实试运行三道验证,缺一不可。

  2. 符号链接与 setgid 目录。 给符号链接 chmod 没有效果,要操作目标文件;共享目录(如 /srv/www、/srv/data)设置 setgid(2775),让新建文件自动继承属组,避免不同用户创建的文件组归属混乱。挂载点可按需加 nosuid、nodev、noexec,抑制提权位、设备节点与程序执行(如 /home、/tmp 的绑定挂载)。

  3. 忘记保留逃生通道。 关闭密码登录前必须另开会话验证密钥;改 sudoers 前先确认当前用户之外还有 root 逃生路径(另一个 root 会话、或能 sudo 的管理员账号)。「验证后再上线」不是流程套话——最贵的事故,都是「已在生产环境、却把自己锁在外面」。

#收尾

最小权限不是一次配置,而是每次变更时的习惯:默认拒绝,按需放行,验证后再上线。这篇文章从「身份由 UID/GID 决定、访问由 inode 权限位与 ACL 决定、提权由 setuid 与 sudoers 规则决定」三条因果链出发,把账号创建、权限设置、sudo 白名单和关闭密码登录串成了同一件事——理解了机制,命令只是表达机制的语言。

可以继续做:启用并定期 review sudo 审计日志(journalctl _COMM=sudo);给 sudo 加第二因子(pam 模块、硬件密钥);SSH 禁用 root 登录(PermitRootLogin no)、只允许密钥;给 /home、/tmp 等挂载点加 nosuid,nodev。也要知道什么时候不该用 sudo:能给最小权限就别给 sudo 组;能锁 root 就锁 root;能用只读、专用命令解决的场景,不要开交互式 shell。最小权限的终点不是「配置好看」,而是「每个账号只拿得到它做事所需的那一点,越权有迹可查」。

相关阅读

Related

留言讨论

0 条

还没有留言,来说点什么吧。

Linux 系统管理入门:用户、权限与 sudo · LXH·BLOG