Verhindert Ansible die Ausführung von 'rm -rf /' in einem Shell-Skript?

23

Dies basiert auf dieser Falschmeldung hier. Das beschriebene Problem besteht darin, ein Bash-Skript zu haben, das Folgendes enthält:

rm -rf {pattern1}/{pattern2}

... das, wenn beide Muster ein oder mehrere leere Elemente enthalten, auf mindestens eine Instanz von erweitert wird rm -rf /, vorausgesetzt, der ursprüngliche Befehl wurde korrekt transkribiert und das OP führte eine Klammererweiterung statt einer Parametererweiterung durch .

In der Erklärung des OP zum Hoax heißt es:

Der Befehl ist harmlos, aber es scheint, dass fast niemand bemerkt hat.

Das Ansible-Tool verhindert diese Fehler, [...] aber niemand schien das zu wissen, sonst würden sie wissen, dass das, was ich beschrieben habe, nicht passieren könnte.

Angenommen, Sie haben ein Shell-Skript, das einen rm -rf /Befehl entweder durch Klammer- oder Parametererweiterung ausgibt. Stimmt es, dass die Verwendung von Ansible die Ausführung dieses Befehls verhindert? Und wenn ja, wie funktioniert das?

Ist das Ausführen rm -rf /mit Root-Rechten wirklich "harmlos", solange Sie Ansible dafür verwenden?

aroth
quelle
4
Ich habe überlegt, was ich mit dieser Frage anfangen soll, aber letztendlich habe ich mich entschlossen, sie zu beantworten, um endlich dieses ganze lächerliche Chaos in die Vergangenheit zu bringen, wo es hingehört.
Michael Hampton
Ich denke, die Antwort liegt wirklich in der rmQuelle, die ich unten analysiert habe.
Aaron Hall

Antworten:

54

Ich habe virtuelle Maschinen, lasst uns ein paar davon in die Luft jagen! Für die Wissenschaft.

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

Erster Versuch:

[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

OK, gibt commandnur die Literale weiter und es passiert nichts.

Wie wäre es mit unserer bevorzugten Sicherheitsumgehung 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

Nein, geh schon wieder! Wie schwer kann es möglicherweise sein, alle Ihre Dateien zu löschen?

Oh, aber was wäre, wenn sie undefinierte Variablen wären oder so?

[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

Nun, das hat nicht funktioniert.

Was aber, wenn die Variablen definiert, aber leer sind?

[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

Endlich ein paar Fortschritte! Aber es klagt immer noch, dass ich nicht benutzt habe --no-preserve-root.

Natürlich warnt es mich auch, dass ich versuchen sollte, das fileModul und zu verwenden state=absent. Mal sehen, ob das funktioniert.

[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

Gute Nachrichten, Leute! Es wurde versucht , alle meine Dateien zu löschen! Aber leider ist es auf einen Fehler gestoßen. Ich überlasse es, das zu reparieren und das Spielbuch dazu zu bringen, alles zu zerstören, indem ich das fileModul als Übung für den Leser benutze .


Führen Sie KEINE Playbooks aus, die Sie über diesen Punkt hinaus sehen! Du wirst gleich sehen warum.

Schließlich für den Coup de Grâce ...

[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

Diese VM ist ein Ex-Papagei !

Interessanterweise hat das oben Gesagte nichts dagegen commandunternommen raw. Es wurde nur die gleiche Warnung zur Verwendung von filemit ausgegeben state=absent.

Ich werde sagen, rawdass es einen gewissen Schutz vor rmAmok gibt , wenn Sie es nicht benutzen . Darauf sollten Sie sich jedoch nicht verlassen. Ich habe den Code von Ansible kurz durchgesehen, und als ich die Warnung gefunden habe, habe ich nichts gefunden, was die Ausführung des rmBefehls tatsächlich unterdrücken würde .

Michael Hampton
quelle
10
+1 für die Wissenschaft. Ich würde +1 mehr für den Hostnamen geben, aber es wäre Betrug; p /
Journeyman Geek
Sieht so aus, als hättest du ein Dateisystem gemountet /boot.
84104
1
@ 84104 Komisch, dass. Zufällig bootist der erste Verzeichniseintrag in /. Es gingen also keine Dateien verloren.
Michael Hampton
5
@aroth Genau! Aber für die Wissenschaft, versuchen Sie, rm -rf {{x}}/{{y}}wann eingestellt yist "*". Die --no-preserve-rootÜberprüfung ist nützlich für das, was es ist, aber sie wird Sie nicht aus jeder möglichen Situation herausholen. es ist leicht genug, umzugehen. Aus diesem Grund wurde diese Frage nicht sofort als Scherz aufgefasst: Unter Berücksichtigung des schlechten Englisch und der offensichtlichen Syntaxfehler ist dies plausibel .
Michael Hampton
1
Außerdem könnte rawein Bad croneine andere Möglichkeit sein, ein System zu zerstören.
84104
3

Verhindert Ansible die Ausführung rm -rf /in einem Shell-Skript?

Ich habe die coreutils rm-Quelle untersucht , die Folgendes enthält:

  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 ("/"));
    }

Die einzige Möglichkeit, das Root- Verzeichnis zu löschen, besteht darin, diesen Codeblock zu umgehen. Aus dieser Quelle :

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;
}

Ich interpretiere dies so, dass die Funktion get_root_dev_inonull zurückgibt /und somit rm ausfällt.

Die einzige Möglichkeit, den ersten Codeblock (mit Rekursion) zu umgehen, ist have, --no-preserve-rootund es wird keine Umgebungsvariable zum Überschreiben verwendet. Daher muss sie explizit an rm übergeben werden.

Ich glaube , das beweist , dass es sei denn , ansible ausdrücklich geht --no-preserve-rootauf rm, es wird dies nicht tun.

Fazit

Ich glaube nicht, dass Ansible das explizit verhindert, rm -rf /weil es es rmselbst verhindert.

Aaron Hall
quelle