Skip to content

sudo 与权限边界 ​

生产上不该长期用 root 登录。正确姿势是:普通用户 + sudo + 精确到命令的授权。

一、四个容易混的命令 ​

命令做什么环境
su app切成 app 用户保留当前环境变量
su - app登录式切换加载 app 的完整登录环境(PATH/HOME 都对)
sudo -i以目标用户身份启动登录 shell类似 su -,但走 sudo 授权与日志
sudo -s以目标用户启动非登录 shell保留当前环境,容易踩 PATH 坑
bash
whoami
su - app
whoami; echo $PATH; pwd
text
root
app
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/app/.local/bin:/home/app/bin
/home/app

su 和 su -

su app 切换后 PATH、HOME 可能还是 root 的,导致"我明明装了命令却找不到"。脚本里一律用 su - app -c '...'。

二、sudo 基本用法 ​

bash
sudo -l
text
User app may run the following commands on kafka-1:
    (root) NOPASSWD: /bin/systemctl restart order-api
    (root) NOPASSWD: /usr/bin/journalctl -u order-api
    (ALL : ALL) ALL
bash
sudo systemctl restart order-api
sudo -u app /opt/app/bin/healthcheck.sh
sudo -l -U zhangsan           # 看别人有什么权限

sudo -l 是最重要的命令:授权前先确认自己到底能干什么。

三、sudoers ​

永远用 visudo 编辑,它会做语法检查;直接 vi 改错会导致所有 sudo 失效。

bash
visudo -f /etc/sudoers.d/app
ini
# /etc/sudoers.d/app
Defaults:app  env_keep += "JAVA_HOME"
Defaults:app  secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

app ALL=(root) NOPASSWD: /bin/systemctl restart order-api, \
                          /bin/systemctl status order-api
app ALL=(root) NOPASSWD: /usr/bin/journalctl -u order-api *
app ALL=(ALL) /usr/bin/less /var/log/*

格式:

text
用户  主机=(目标用户)  标签: 命令
app   ALL =(root)    NOPASSWD: /bin/systemctl restart order-api
部分说明
ALL(主机)允许在哪些主机上执行
(root)以谁的身份执行
NOPASSWD:免密码
命令路径必须写绝对路径

文件权限:

bash
chmod 440 /etc/sudoers.d/app

四、最小授权的写法 ​

ini
# 只允许重启一个服务
app ALL=(root) NOPASSWD: /bin/systemctl restart order-api

# 允许看日志(注意 less 可以执行 shell,见下方 warning)
app ALL=(root) NOPASSWD: /usr/bin/journalctl -u order-api

这些授权等于给了 root

  • NOPASSWD: /usr/bin/vi /etc/sudoers —— 能改 sudoers。
  • NOPASSWD: /usr/bin/less /var/log/* —— less 里可以 !sh 起 shell。
  • NOPASSWD: /bin/systemctl —— 能重启任何服务,包括 sshd。
  • NOPASSWD: /bin/sh、/usr/bin/python3、/usr/bin/find —— 都能间接起 shell。

通配符更危险:

ini
app ALL=(root) NOPASSWD: /bin/systemctl restart *

* 允许重启任何服务,等于完全控制。授权要精确到完整命令和参数。

五、环境变量:secure_path 陷阱 ​

sudo 默认会重置 PATH 为 secure_path(在 /etc/sudoers 里),导致:

bash
which java
# /usr/local/jdk-17/bin/java

sudo java -version
# sudo: java: command not found

三种解法:

ini
# 1. 把路径加进 secure_path
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/local/jdk-17/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# 2. 保留特定变量
Defaults env_keep += "JAVA_HOME PATH"

# 3. 命令里写绝对路径(最稳)
bash
sudo /usr/local/jdk-17/bin/java -version

六、日志 ​

bash
tail -f /var/log/secure | grep sudo
journalctl -t sudo -n 20
text
Oct 27 23:50:11 kafka-1 sudo[41200]:      app : TTY=pts/0 ; PWD=/opt/app ; USER=root ; COMMAND=/bin/systemctl restart order-api

记录了谁、从哪个终端、在哪个目录、执行了什么。这是审计的基础。

七、runuser ​

runuser 是专给 root 用的切换命令,不需要密码,且不受 sudoers 限制:

bash
runuser -u app -- /opt/app/bin/start.sh
runuser -u app --login -- env | grep PATH

适合在 systemd 或启动脚本里以其他用户身份执行:

ini
ExecStartPre=/usr/sbin/runuser -u app -- /opt/app/bin/prepare.sh

su 在某些精简镜像里没有,runuser 在 util-linux 里,更常见。

八、踩过的坑 ​

现象根因处置
sudo: java: command not foundsecure_path 不含 JDK 路径写绝对路径或改 secure_path
sudo: sorry, you must have a tty to run sudo脚本/管道里没有 TTY用 ssh -t,或去掉 requiretty
改了 sudoers 后所有人 sudo 都用不了语法错误用 visudo;已锁死则用单用户模式
sudo 后环境变量丢了默认重置环境env_keep 或 sudo -E
NOPASSWD 不生效规则顺序/位置被后面的覆盖sudo -l 看最终生效规则
su app 后命令找不到没加载登录环境用 su - app
通配符授权被利用restart * 等于 root精确到完整命令
sudo 提示 user is not in the sudoers file没授权加进 wheel 组或 sudoers.d