Will Ansible перешкоджає виконанню 'rm -rf /' у сценарії оболонки


23

Це ґрунтується на цьому підступному питанні . Описана проблема полягає в тому, щоб мати сценарій bash, який містить щось для ефекту:

rm -rf {pattern1}/{pattern2}

... що, якщо обидва шаблони включають один або більше порожніх елементів, буде розширюватися щонайменше до одного примірника rm -rf /, припускаючи, що початкова команда була переписана правильно і OP робила розширення дужок, а не розширення параметрів .

У поясненні ОП про підробку він зазначає:

Команда [...] нешкідлива, але, здається, майже ніхто її не помітив.

Інструмент Ansible запобігає цим помилкам, [...] але [...] ніхто, здавалося, не знає цього, інакше вони знають, що те, що я описав, не може статися.

Отже, якщо припустити, що у вас є сценарій оболонки, який видає rm -rf /команду через розширення дужок або розширення параметрів, чи правда, що використання Ansible не дозволить виконувати цю команду, і якщо так, як це зробити?

Чи виконання rm -rf /з привілеями root справді "нешкідливе", якщо ви використовуєте Ansible для цього?


4
Я обговорював, що робити з цим питанням, але врешті-решт я вирішив підтримати його і відповісти, щоб рухатись до останнього, вкладаючи всю цю сумну смішну безлад у минуле, куди їй належить.
Майкл Хемптон

Я думаю, що відповідь справді лежить у rmджерелі, яке я проаналізував нижче.
Аарон Холл

Відповіді:


54

У мене віртуальні машини, давайте підірвемо купу їх! Для науки.

[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з state=absent.

Я хочу сказати, що, здається, якщо ви не користуєтесь rawцим, то є якийсь захист від rmзанепаду. Не варто на це покладатися. Я швидко ознайомився з кодом Ansible, і, знайшовши попередження, не знайшов нічого, що насправді пригнічувало б виконання rmкоманди.


10
+1 для науки. Я ще +1 для імені хоста, але це було б шахрайством; p /
Journeyman Geek

Схоже, у вас може бути встановлена ​​файлова система /boot.
84104,

1
@ 84104 Смішно, що. За простим збігом обставин bootце перший запис у каталозі /. Тож жодні файли не були втрачені.
Майкл Хемптон

5
@aroth Рівно! Але, для науки, спробуйте, rm -rf {{x}}/{{y}}коли yце встановлено "*". --no-preserve-rootПеревірка корисна для того, що він є, але він не буде отримувати вас з будь-якої можливої ситуації; досить просто обійти. Ось чому це питання не було одразу сприйнято як підступ: Враховуючи погану англійську мову та явні синтаксичні помилки, це правдоподібно .
Майкл Хемптон

1
Крім того raw, поганий cronможе бути ще одним способом руйнування системи.
84104

3

Чи запобіжить виконання Ansible rm -rf /в сценарії оболонки?

Я перевірив джерело rm 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повертається до нуля /, і, отже, rm не працює.

Єдиний спосіб обійти перший блок коду (з рекурсією) - це мати, --no-preserve-rootі він не використовує змінну середовища для переопрацювання, тому його потрібно було б передати явно в rm.

Я вважаю, що це доводить, що якщо Ansible явно не перейде --no-preserve-rootдо цього rm, він цього не зробить.

Висновок

Я не вірю, що Ansible явно перешкоджає rm -rf /тому, що rmсам це перешкоджає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.