Will Ansible会阻止在shell脚本中执行'rm -rf /'

这是基于这个骗局问题 。 所描述的问题是有一个bash脚本包含的东西的影响:

rm -rf {pattern1}/{pattern2} 

…如果两个模式都包含一个或多个空元素,则将扩展到rm -rf /至less一个实例,假定原始命令正确转录,并且OP正在进行扩展而不是参数扩展 。

他在OP 对这个恶作剧的解释中说:

这个命令是无害的,但似乎几乎没有人注意到。

Ansible工具防止这些错误,但似乎没有人知道,否则他们会知道我所描述的事情是不可能发生的。

所以假设你有一个通过扩展或参数扩展发出rm -rf /命令的shell脚本,使用Ansible将阻止这个命令被执行是真的,如果是的话,它是如何做的?

只要你使用Ansible来执行rm -rf / root权限真的是“无害”的呢?

我有虚拟机,让我们把它们一堆吹起来! 为了科学。

 [root@diaf ~]# ansible --version ansible 2.0.1.0 config file = /etc/ansible/ansible.cfg configured module search path = Default w/o overrides 

第一次尝试:

 [root@diaf ~]# cat killme.yml --- - hosts: localhost gather_facts: False tasks: - name: Die in a fire command: "rm -rf {x}/{y}" [root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml Using /etc/ansible/ansible.cfg as config file 1 plays in killme.yml PLAY *************************************************************************** TASK [Die in a fire] *********************************************************** task path: /root/killme.yml:5 ESTABLISH LOCAL CONNECTION FOR USER: root localhost EXEC /bin/sh -c '( umask 22 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374 `" )' localhost PUT /tmp/tmprogfhZ TO /root/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374/command localhost EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374/command; rm -rf "/root/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374/" > /dev/null 2>&1' changed: [localhost] => {"changed": true, "cmd": ["rm", "-rf", "{x}/{y}"], "delta": "0:00:00.001844", "end": "2016-04-20 05:06:59.601868", "invocation": {"module_args": {"_raw_params": "rm -rf {x}/{y}", "_uses_shell": false, "chdir": null, "creates": null, "executable": null, "removes": null, "warn": true}, "module_name": "command"}, "rc": 0, "start": "2016-04-20 05:06:59.600024", "stderr": "", "stdout": "", "stdout_lines": [], "warnings": ["Consider using file module with state=absent rather than running rm"]} [WARNING]: Consider using file module with state=absent rather than running rm PLAY RECAP ********************************************************************* localhost : ok=1 changed=1 unreachable=0 failed=0 

好的,所以command只是传递文字,没有任何反应。

我们最喜欢的安全搭桥raw怎么样?

 [root@diaf ~]# cat killme.yml --- - hosts: localhost gather_facts: False tasks: - name: Die in a fire raw: "rm -rf {x}/{y}" [root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml Using /etc/ansible/ansible.cfg as config file 1 plays in killme.yml PLAY *************************************************************************** TASK [Die in a fire] *********************************************************** task path: /root/killme.yml:5 ESTABLISH LOCAL CONNECTION FOR USER: root localhost EXEC rm -rf {x}/{y} ok: [localhost] => {"changed": false, "invocation": {"module_args": {"_raw_params": "rm -rf {x}/{y}"}, "module_name": "raw"}, "rc": 0, "stderr": "", "stdout": "", "stdout_lines": []} PLAY RECAP ********************************************************************* localhost : ok=1 changed=0 unreachable=0 failed=0 

不要再去! 删除所有文件有多难?

哦,但如果他们是未定义的variables或什么?

 [root@diaf ~]# cat killme.yml --- - hosts: localhost gather_facts: False tasks: - name: Die in a fire command: "rm -rf {{x}}/{{y}}" [root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml Using /etc/ansible/ansible.cfg as config file 1 plays in killme.yml PLAY *************************************************************************** TASK [Die in a fire] *********************************************************** task path: /root/killme.yml:5 fatal: [localhost]: FAILED! => {"failed": true, "msg": "'x' is undefined"} NO MORE HOSTS LEFT ************************************************************* to retry, use: --limit @killme.retry PLAY RECAP ********************************************************************* localhost : ok=0 changed=0 unreachable=0 failed=1 

那么,这是行不通的。

但是,如果variables是定义的,但是空的呢?

 [root@diaf ~]# cat killme.yml --- - hosts: localhost gather_facts: False tasks: - name: Die in a fire command: "rm -rf {{x}}/{{y}}" vars: x: "" y: "" [root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml Using /etc/ansible/ansible.cfg as config file 1 plays in killme.yml PLAY *************************************************************************** TASK [Die in a fire] *********************************************************** task path: /root/killme.yml:5 ESTABLISH LOCAL CONNECTION FOR USER: root localhost EXEC /bin/sh -c '( umask 22 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105 `" )' localhost PUT /tmp/tmp78m3WM TO /root/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105/command localhost EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105/command; rm -rf "/root/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105/" > /dev/null 2>&1' fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["rm", "-rf", "/"], "delta": "0:00:00.001740", "end": "2016-04-20 05:12:12.668616", "failed": true, "invocation": {"module_args": {"_raw_params": "rm -rf /", "_uses_shell": false, "chdir": null, "creates": null, "executable": null, "removes": null, "warn": true}, "module_name": "command"}, "rc": 1, "start": "2016-04-20 05:12:12.666876", "stderr": "rm: it is dangerous to operate recursively on '/'\nrm: use --no-preserve-root to override this failsafe", "stdout": "", "stdout_lines": [], "warnings": ["Consider using file module with state=absent rather than running rm"]} NO MORE HOSTS LEFT ************************************************************* to retry, use: --limit @killme.retry PLAY RECAP ********************************************************************* localhost : ok=0 changed=0 unreachable=0 failed=1 

最后,一些进展! 但是它仍然抱怨我没有使用--no-preserve-root

当然,它也警告我,我应该尝试使用file模块和state=absent 。 让我们看看是否有效。

 [root@diaf ~]# cat killme.yml --- - hosts: localhost gather_facts: False tasks: - name: Die in a fire file: path="{{x}}/{{y}}" state=absent vars: x: "" y: "" [root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml Using /etc/ansible/ansible.cfg as config file 1 plays in killme.yml PLAY *************************************************************************** TASK [Die in a fire] *********************************************************** task path: /root/killme.yml:5 ESTABLISH LOCAL CONNECTION FOR USER: root localhost EXEC /bin/sh -c '( umask 22 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388 `" )' localhost PUT /tmp/tmpUqLzyd TO /root/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388/file localhost EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388/file; rm -rf "/root/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388/" > /dev/null 2>&1' fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "invocation": {"module_args": {"backup": null, "content": null, "delimiter": null, "diff_peek": null, "directory_mode": null, "follow": false, "force": false, "group": null, "mode": null, "original_basename": null, "owner": null, "path": "/", "recurse": false, "regexp": null, "remote_src": null, "selevel": null, "serole": null, "setype": null, "seuser": null, "src": null, "state": "absent", "validate": null}, "module_name": "file"}, "msg": "rmtree failed: [Errno 16] Device or resource busy: '/boot'"} NO MORE HOSTS LEFT ************************************************************* to retry, use: --limit @killme.retry PLAY RECAP ********************************************************************* localhost : ok=0 changed=0 unreachable=0 failed=1 

好消息,大家! 它开始试图删除所有的文件! 但不幸的是,它遇到了一个错误。 我将会修复这个问题,并且让剧本将所有使用file模块的东西摧毁,作为读者的练习。


不要运行你看到超越这个点的任何剧本! 你会明白为什么一会儿。

最后,对于发动政变

 [root@diaf ~]# cat killme.yml --- - hosts: localhost gather_facts: False tasks: - name: Die in a fire raw: "rm -rf {{x}}/{{y}}" vars: x: "" y: "*" [root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml Using /etc/ansible/ansible.cfg as config file 1 plays in killme.yml PLAY *************************************************************************** TASK [Die in a fire] *********************************************************** task path: /root/killme.yml:5 ESTABLISH LOCAL CONNECTION FOR USER: root localhost EXEC rm -rf /* Traceback (most recent call last): File "/usr/lib/python2.7/site-packages/ansible/executor/process/result.py", line 102, in run File "/usr/lib/python2.7/site-packages/ansible/executor/process/result.py", line 76, in _read_worker_result File "/usr/lib64/python2.7/multiprocessing/queues.py", line 117, in get ImportError: No module named task_result 

这个虚拟机是一个前鹦鹉 !

有趣的是,上面没有做任何与command而不是raw 。 它只是打印与使用state=absent file相同的警告。

我要说的是,如果你不使用raw ,那么rm已经有了一些保护。 不过,你不应该依赖这个。 我通过Ansible的代码快速浏览了一下,当我发现这个警告的时候,我没有发现任何会阻止运行rm命令的东西。

Will Ansible会阻止在shell脚本中执行rm -rf /

我确实检查了coreutils的源代码 ,它具有以下内容:

  if (x.recursive && preserve_root) { static struct dev_ino dev_ino_buf; x.root_dev_ino = get_root_dev_ino (&dev_ino_buf); if (x.root_dev_ino == NULL) error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), quoteaf ("/")); } 

从根目录擦除的唯一方法是通过这个代码块。 从这个来源 :

 struct dev_ino * get_root_dev_ino (struct dev_ino *root_d_i) { struct stat statbuf; if (lstat ("/", &statbuf)) return NULL; root_d_i->st_ino = statbuf.st_ino; root_d_i->st_dev = statbuf.st_dev; return root_d_i; } 

我认为这意味着函数get_root_dev_ino/上返回null,因此rm失败。

绕过第一个代码块(recursion)的唯一方法是使用--no-preserve-root并且它没有使用环境variables来覆盖,所以它必须被明确地传递给rm。

我相信这certificate除非Ansible显式传递--no-preserve-rootrm ,否则不会这样做。

结论

我不相信Ansible明确地阻止rm -rf /因为rm本身阻止它。