外观
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; pwdtext
root
app
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/app/.local/bin:/home/app/bin
/home/appsu 和 su -
su app 切换后 PATH、HOME 可能还是 root 的,导致"我明明装了命令却找不到"。脚本里一律用 su - app -c '...'。
二、sudo 基本用法
bash
sudo -ltext
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) ALLbash
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/appini
# /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 20text
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.shsu 在某些精简镜像里没有,runuser 在 util-linux 里,更常见。
八、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
sudo: java: command not found | secure_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 |