工作中难免会在操作系统时出现“手比眼快”的情况,轻则丢失自己一段时间的工作成果,重则造成生产环境直接下线。本文主要讲如何通过 Linux 中的 bash 环境变量和 sudoers 配置避免误执行类似 “rm -rf /*” 的操作。

Bash环境变量

因为生产环境中比较常见的 CentOS 默认使用的 Shell 是 Bash,而 Bash shell 在启动时会默认执行 ~/.bashrc 文件中的内容。因此对于 root 用户,可以通过在该文件中为危险命令配置别名(alias)的方式实现避免误操作。 CentOS 默认是为 rm 命令配置了防误操作的别名:

1
alias rm='rm -i'

但是 rm 命令对于传入多个参数时的处理方式是处于后边的参数效果会覆盖前面的参数,因此即使有这条 alias 设置,也无法避免 “rm -rf /*” 以完全无提示信息的方式直接执行。而 alias 并不支持为带空格的一串命令直接设置别名,只能通过引入额外的脚本的方式来处理该问题。

额外的脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
args=()
for arg in "[email protected]"
do
slash=
if [[ "${arg}" == /* ]]
then
echo "WARNING! You are deleting a directory("${arg}") starting with /, press Y to confirm, N to skip"
read -r response
case "${response}" in
[yY][eE][sS]|[yY])
args=(${args[*]} ${arg})
;;
*)
echo "Skipping "${arg}
;;
esac
else
args=(${args[*]} ${arg})
fi
done
/bin/rm -i ${args[*]}

上面脚本的主要流程如下:对传入的每个参数进行检查,当发现传入的参数以“/”开头时,显示提示信息,在用户手工确认后,才会将该参数加至用于提交的参数列表, 最后将整个参数列表传递至 rm -i 后面,执行真正的 rm 操作。副作用:当以绝对路径删除一个并非直接处于/下的文件或目录时也会触发该提示,考虑过使用计数的方法判断参中/的个数,但如果使用计数的方法则无法简单避免 rm -rf //* 的情形;使用正则匹配则需要担心 bash 的版本对正则的支持程度。

假设上面的文件保存于 /root/.srm.sh,通过 chmod u+x 添加执行权限后,配置 root 的 .bashrc 文件,将原有的

1
alias rm='rm -i'

替换成

1
alias rm='/root/.srm.sh'

重新登录或者执行 source ~/.bashrc 使变量生效。至此,root 用户的防误删除配置完毕,对根目录/下任何目录的删除操作都会触发脚本中的提示信息,需要按 y 确认后才能删除。

使用 root 作死效果:

1
2
[[email protected] ~]# rm -fr /*
WARNING! You are deleting a directory(/bin) in /, press Y to confirm, N to skip

/etc/sudoers文件的配置

很多情况下,系统中存在可以允许执行 sudo 命令的用户,而这些用户的 shell 使用的是各自的环境变量,可以依葫芦画瓢使用上面的方法为每个用户都配置 alias 使用脚本检查 rm 的参数。但 sudo 本身的配置文件 /etc/sudoers 支持对指定命令进行过滤,可以实现在一个地方统一配置拒绝通过 sudo 执行危险操作。
/etc/sudoers 文件在正常情况下权限为440,即使 root 也无法编辑,需要先通过 chmod 640 /etc/sudoers 后使用 root 身份编辑,加入以下的配置,用于指定一组危险命令,配置中的 是通配符,并不代表 本身。

1
Cmnd_Alias DANGEROUS_CMNDS=/bin/rm -rf /*, /bin/rm -fr /*

对于每个具有 sudo 执行权限的用户的配置,需要将原有的类似如下的配置:

1
username	ALL=(ALL)	ALL

修改为:

1
username	ALL=(ALL)	ALL,!DANGEROUS_CMNDS

即在最后一个参数后加上 “,!DANGEROUS_CMNDS”,表示拒绝 DANGEROUS_CMNDS 中命令的执行。

修改完毕后,需要通过 chmod 440 /etc/sudoers 将该文件权限修改回原有状态,错误的文件权限状态将导致 sudo 命令无法工作(实际测试中,修改为 640 在 CentOS 6.9 下不会引发问题,但修改为 777 会导致 sudo 找不到有效的配置文件)。

使用具有 sudo 权限的用户作死效果:

1
2
3
4
5
6
7
[[email protected] /]$ sudo touch 123 && ll 12*
-rw-r--r--. 1 root root 0 Feb 7 21:45 123
[[email protected] /]$ sudo rm -rf 12* && ll 12*
ls: cannot access 12*: No such file or directory
[[email protected] /]$ sudo rm -rf /*
[sudo] password for tony:
Sorry, user tony is not allowed to execute '/bin/rm -rf /bin /boot /dev /etc /home /lib /lib64 /lost+found /media /mnt /opt /proc /root /sbin /selinux /srv /sys /tmp /usr /var' as root on localhost.localdomain.

可以看到,在不影响用户正常使用 sudo 权限的情况下,危险的 rm -rf /* 无法执行。

测试使用 sudo 提升权限至 root ,危险操作均被脚本拦截:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[[email protected] /]$ sudo -s # sudo s参数直接提权至root
[[email protected] /]# rm -rf /123
WARNING! You are deleting a directory(/123) in /, press Y to confirm, N to skip
n
Skipping /123
[[email protected] /]# exit
[[email protected] /]$ sudo su root # 通过 su 指定root用户提权,不模拟root登录,等价于 "sudo su"
[[email protected] /]# rm -rf /123
WARNING! You are deleting a directory(/123) in /, press Y to confirm, N to skip
n
Skipping /123
[[email protected] /]# exit
[[email protected] /]$ sudo su - root # 通过 su 指定root用户提权,模拟root登录,等价于 "sudo su -"
[[email protected] ~]# rm -rf /123
WARNING! You are deleting a directory(/123) in /, press Y to confirm, N to skip
n
Skipping /123