Ansibleはシェルスクリプトで 'rm -rf /'の実行を防止します


23

これは、このデマの質問に基づいています。説明されている問題は、次の効果をもたらすものを含むbashスクリプトを持っていることです。

rm -rf {pattern1}/{pattern2}

...両方のパターンに1つ以上の空の要素が含まれる場合rm -rf /、元のコマンドが正しく転写され、OPがパラメーター展開ではなくブレース展開を行っていると仮定して、少なくとも1つのインスタンスに展開されます

OPによるデマ説明では、彼は次のように述べています。

コマンド[...]は無害ですが、ほとんど誰も気付いていないようです。

Ansibleツールはこれらのエラーを防止しますが[...]しかし[...]誰もそれを知らないようでした。さもなければ彼らは私が説明したことが起こらないことを知っているでしょう。

したがってrm -rf /、ブレース展開またはパラメーター展開のいずれかを介してコマンドを発行するシェルスクリプトがあると仮定すると、Ansibleを使用するとそのコマンドの実行が妨げられるというのは本当ですか?

rm -rf /あなたがそれを行うためにAnsibleを使用している限り、root権限で実行することは本当に「無害」ですか?


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

OK、それで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

このVMは元オウムです!

興味深いことに、上記ではのcommand代わりに何もできませんでしたraw。での使用について同じ警告を出力fileしましたstate=absent

私は、あなたがそれを使用rawしていない場合、それがrmなくなってしまうことからの保護があるように見えると言うつもりです。ただし、これに頼るべきではありません。Ansibleのコードをざっと見てみましたが、警告を見つけましたが、実際にrmコマンドの実行を抑制するものは見つかりませんでした。


10
科学の場合は+1。ホスト名をさらに+1しますが、詐欺になります; p /
ジャーニーマンオタク

にファイルシステムがマウントされているようです/boot
84104

1
@ 84104おかしい、それ。まったくの偶然により、はのboot最初のディレクトリエントリです/。したがって、ファイルは失われませんでした。
マイケルハンプトン

5
@arothまさに!ただし、科学のrm -rf {{x}}/{{y}}場合、yがに設定されて"*"いる場合に試してください。この--no-preserve-rootチェックはそれが何であるかには役立ちますが、考えられるあらゆる状況から抜け出すことはできません。バイパスするのは簡単です。だからこそ、その質問はデマとしてすぐには見つかりませんでした。悪い英語と明らかな構文エラーを考慮すると、それはもっともらしいです。
マイケルハンプトン

1
それにrawcronシステムを破壊する別の方法は悪いかもしれません。
84104

3

Ansibleの実行を防止します rm -rf /はシェルスクリプトでますか?

coreutils rm sourceを調べましたが、これには次のものがあります。

  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が失敗ます。

最初のコードブロックを(再帰を使用して)バイパスする唯一の方法は、 --no-preserve-root使用、環境変数を使用してオーバーライドしないことです。そのため、明示的にrmに渡す必要があります。

これは、Ansibleが明示的に合格しない限り、 --no-preserve-rootrm、それがこれを行うことはありません。

結論

Ansibleがそれを防ぐrm -rf /ので、Ansibleが明示的に防ぐとは思わないrm

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.