Solution 1 :
J'ai des machines virtuelles, faisons-en exploser un tas ! Pour la science.
[[email protected] ~]# ansible --version
ansible 2.0.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
Première tentative :
[[email protected] ~]# cat killme.yml
---
- hosts: localhost
gather_facts: False
tasks:
- name: Die in a fire
command: "rm -rf {x}/{y}"
[[email protected] ~]# 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
OK, donc command
passe juste les littéraux, et rien ne se passe.
Que diriez-vous de notre contournement de sécurité préféré, raw
?
[[email protected] ~]# cat killme.yml
---
- hosts: localhost
gather_facts: False
tasks:
- name: Die in a fire
raw: "rm -rf {x}/{y}"
[[email protected] ~]# 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
Ne recommencez pas ! À quel point peut-il être difficile de supprimer tous vos fichiers ?
Oh, mais et s'il s'agissait de variables indéfinies ou quelque chose du genre ?
[[email protected] ~]# cat killme.yml
---
- hosts: localhost
gather_facts: False
tasks:
- name: Die in a fire
command: "rm -rf {{x}}/{{y}}"
[[email protected] ~]# 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
Eh bien, cela n'a pas fonctionné.
Mais que se passe-t-il si les variables sont définies, mais vides ?
[[email protected] ~]# cat killme.yml
---
- hosts: localhost
gather_facts: False
tasks:
- name: Die in a fire
command: "rm -rf {{x}}/{{y}}"
vars:
x: ""
y: ""
[[email protected] ~]# 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
Enfin, quelques progrès ! Mais il se plaint toujours que je n'ai pas utilisé --no-preserve-root
.
Bien sûr, cela m'avertit également que je devrais essayer d'utiliser le file
module et state=absent
. Voyons si cela fonctionne.
[[email protected] ~]# cat killme.yml
---
- hosts: localhost
gather_facts: False
tasks:
- name: Die in a fire
file: path="{{x}}/{{y}}" state=absent
vars:
x: ""
y: ""
[[email protected] ~]# 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
Bonnes nouvelles tout le monde! Ça a commencé à essayer pour supprimer tous mes fichiers ! Mais malheureusement, il s'est heurté à une erreur. Je vais laisser réparer ça et faire en sorte que le playbook détruise tout en utilisant le file
module comme exercice pour le lecteur.
N'exécutez PAS les playbooks que vous voyez au-delà de ce point ! Vous comprendrez pourquoi dans un instant.
Enfin, pour le coup de grâce ...
[[email protected] ~]# cat killme.yml
---
- hosts: localhost
gather_facts: False
tasks:
- name: Die in a fire
raw: "rm -rf {{x}}/{{y}}"
vars:
x: ""
y: "*"
[[email protected] ~]# 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
Ce VM est un ex-perroquet !
Fait intéressant, ce qui précède n'a rien fait avec command
au lieu de raw
. Il vient d'imprimer le même avertissement concernant l'utilisation de file
avec state=absent
.
Je vais dire qu'il semble que si vous n'utilisez pas raw
qu'il existe une certaine protection contre rm
devenu fou. Vous ne devriez pas vous fier à cela, cependant. J'ai jeté un coup d'œil rapide au code d'Ansible, et même si j'ai trouvé l'avertissement, je n'ai rien trouvé qui supprimerait réellement l'exécution du rm
commande.
Solution 2 :
Ansible empêchera-t-il l'exécution de rm -rf /
dans un script shell ?
J'ai inspecté la source coreutils rm, qui contient les éléments suivants :
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 ("/"));
}
La seule façon d'effacer depuis la racine est de passer ce bloc de code. À partir de cette source :
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;
}
J'interprète cela comme signifiant que la fonction get_root_dev_ino
renvoie null sur /
, et donc rm échoue.
La seule façon de contourner le premier bloc de code (avec récursivité) est d'avoir --no-preserve-root
et il n'utilise pas de variable d'environnement pour remplacer, il devrait donc être passé explicitement à rm.
Je crois que cela prouve qu'à moins qu'Ansible ne passe explicitement --no-preserve-root
à rm
, il ne le fera pas.
Conclusion
Je ne crois pas qu'Ansible empêche explicitement rm -rf /
car rm
lui-même l'en empêche.