Linux

Ansible 會阻止在 shell 腳本中執行“rm -rf /”嗎

  • April 21, 2016

這是基於這裡的這個惡作劇問題。所描述的問題是有一個 bash 腳本,其中包含以下內容:

rm -rf {pattern1}/{pattern2}

rm -rf /…如果兩種模式都包含一個或多個空元素,則假設原始命令已正確轉錄並且 OP 正在執行大括號擴展而不是參數擴展,則該模式將擴展為 的至少一個實例。

在 OP對騙局的解釋中,他指出:

命令

$$ … $$是無害的,但似乎幾乎沒有人注意到。 Ansible 工具可以防止這些錯誤,

$$ … $$但$$ … $$似乎沒有人知道這一點,否則他們會知道我所描述的不可能發生。

rm -rf /因此,假設您有一個通過大括號擴展或參數擴展發出命令的 shell 腳本,使用Ansible是否會阻止該命令被執行,如果是,它是如何做到的?

rm -rf /只要您使用 Ansible 來執行,以 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

別再去了!刪除所有文件有多難?

哦,但是如果它們是未定義的變數或其他東西怎麼辦?

[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

好吧,那沒有用。

但是,如果變數已定義但為空怎麼辦?

[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. file它只是列印了關於使用with的相同警告state=absent

我要說的是,如果你不使用它,似乎raw有一些保護可以防止rm失控。**但是,您不應該依賴於此。**我快速瀏覽了 Ansible 的程式碼,雖然我發現了警告,但我沒有發現任何實際上會抑制執行該rm命令的內容。

引用自:https://serverfault.com/questions/771598