在企业运维场景中,经常需要统一清理非授权用户的 sudo 权限。
尤其是当多个账号在 /etc/sudoers 或 /etc/sudoers.d/* 中残留提权配置时,手动检查和修改既繁琐又危险。
本文介绍一种 安全、可回滚、可批量执行的自动化方案 ——
通过 Ansible + awk 列匹配,实现精准回收非白名单用户的 sudo 权限。
✅ 设计思路
按列匹配 sudo 授权格式
标准授权行一般为:
user ALL=(root) NOPASSWD:ALLawk 按列识别
$1(用户名)、$2(ALL=(root))、$3(NOPASSWD:ALL);仅当这三列完全匹配时才执行注释操作,避免误伤其他配置。
保留白名单用户
白名单用户可通过变量
whitelist定义;所有在白名单中的用户权限保留。
自动备份与回滚
修改前自动生成备份文件:
/etc/sudoers.bak_YYYYMMDDHHMMSS任何校验失败自动回滚。
语法安全校验
每次修改后使用
visudo -c验证文件语法;确保不会因配置错误导致 sudo 不可用。
🧩 完整 Playbook
文件名推荐:revoke_sudo.yml
---
- name: 批量回收非白名单 sudo 权限(awk 列匹配)
hosts: all
become: yes
gather_facts: yes
vars:
# ✅ 定义白名单(这些用户保留 sudo 权限)
whitelist: [admin, opsuser, dbmaint, system, monitor]
files_to_fix:
- /etc/sudoers
tasks:
- name: 逐个文件处理(备份 → awk 注释 → 校验 → 覆盖)
block:
- name: 备份 sudoers 文件
ansible.builtin.shell: |
cp -a "{{ item }}" "{{ item }}.bak_$(date +%Y%m%d%H%M%S)"
args:
executable: /bin/bash
loop: "{{ files_to_fix }}"
- name: 使用 awk 严格列匹配注释非白名单 sudo 行
ansible.builtin.shell: |
set -euo pipefail
tmp="$(mktemp)"
awk -v WL="{{ whitelist | join(" ") }}" '
BEGIN{
split(WL, wla, " ");
for (i in wla) wl[wla[i]] = 1;
}
/^[[:space:]]*#/ { print; next }
{
user=$1
if (($2=="ALL=(root)" || $2=="ALL=(ALL)") && $3=="NOPASSWD:ALL" && !(user in wl)) {
print "#" $0
} else {
print
}
}
' "{{ item }}" > "$tmp"
visudo -c -f "$tmp" >/dev/null && install -o root -g root -m 0440 "$tmp" "{{ item }}"
rm -f "$tmp"
args:
executable: /bin/bash
loop: "{{ files_to_fix }}"
loop_control:
label: "{{ item }}"
rescue:
- name: 回滚到最近备份(如校验失败)
ansible.builtin.shell: |
set -e
f="{{ item }}"
b="$(ls -1t ${f}.bak_* 2>/dev/null | head -n1 || true)"
if [ -n "$b" ]; then
install -o root -g root -m 0440 "$b" "$f"
fi
args:
executable: /bin/bash
loop: "{{ files_to_fix }}"
loop_control:
label: "{{ item }}"
- name: 失败提示
ansible.builtin.fail:
msg: "visudo 校验失败,已回滚最近备份,请检查 sudoers 内容与匹配逻辑。"
⚙️ 执行方法
ansible-playbook revoke_sudo.yml -i hosts执行后会:
自动备份原文件;
过滤非白名单用户的全权限 sudo 行;
添加注释符号
#;校验语法;
校验失败时自动回滚。
🧠 逻辑验证示例
假设 /etc/sudoers 内容如下:
# 保留的用户
admin ALL=(root) NOPASSWD:ALL
dbmaint ALL=(root) NOPASSWD:ALL
# 应该被回收的
testuser ALL=(root) NOPASSWD:ALL
legacyuser ALL=(ALL) NOPASSWD:ALL
执行后结果:
admin ALL=(root) NOPASSWD:ALL
dbmaint ALL=(root) NOPASSWD:ALL
#testuser ALL=(root) NOPASSWD:ALL
#legacyuser ALL=(ALL) NOPASSWD:ALL
✅ 总结
💡 扩展建议
如果你的环境中还存在 /etc/sudoers.d/ 子文件,可以在 vars 里添加参数:
files_to_fix:
- /etc/sudoers
- /etc/sudoers.d/appadmin
这样同样支持多文件批量处理。