はじめに
ansible-core 2.16.1、2.15.7、2.14.12 で、破壊的な変更が入りました。
ansible-core 2.16.1 の changelog より引用
assert - Nested templating may result in an inability for the conditional to be evaluated. See the porting guide for more information.
anisble.builtin.assert
モジュールで、これまで評価できていた条件式が、評価できなくなる可能性があるというものです。
評価できない場合、以下のようなエラーメッセージが表示されます。
CVE-2023-5764 に対する修正によるものようです。公式ドキュメントとしての詳細はそれぞれのバージョンのポーティングガイドに記載があります。
この記事では、自分で試したり調べた結果をまとめす。
- 検証環境
- ansible-core 2.16.1
- ansible-core 2.16.2 (このバージョンにも unsafe(後述) 関連の修正があったため合わせて確認
影響を受ける例
changelog の表現も借りつつ、自分なりにまとめると以下の2つの条件を両方満たす時に、影響を受けると理解しています。
- テンプレートをネストさせた時
unsafe
な値を利用する時
unsafe
な値かどうかについては後述します。
ここではどういうテンプレートの書きっぷりで影響を受けるのか、いくつかのパターンを試しました。
例1: shell モジュールの例
影響を受ける Playbook
まずは、ポーティングガイドで、don't do it this way...
とコメントが書かれていた例をそのまま試します。
- name: task with a module result (always untrusted by Ansible) shell: echo "hi mom" register: untrusted_result # don't do it this way... - name: insecure conditional with embedded template consulting untrusted data assert: that: '"hi mom" is in {{ untrusted_result.stdout }}'
うまく表現できませんが、本来 assert
モジュールの that
オプションにはテンプレート書式の "{{ }}"
の中だけを指定します。この場合、さらに {{ }}
が含まれてしまっています。changelog の Nested templating
というのは、おそらくこの状態のことを指しているのではないかと思います。
結果は以下のとおり、Conditional is marked as unsafe, and cannot be evaluated.
というエラーが表示されます。
TASK [task with a module result (always untrusted by Ansible)] ****************************************************** changed: [localhost] TASK [insecure conditional with embedded template consulting untrusted data] **************************************** fatal: [localhost]: FAILED! => {"msg": "The conditional check '\"hi mom\" is in hi mom' failed. The error was: Condit ional is marked as unsafe, and cannot be evaluated."}
対策
ポーティングガイドにある正しい書き方に直します。
- name: securely access untrusted values directly as Jinja variables instead assert: that: '"hi mom" is in untrusted_result.stdout'
以下のとおり、正しく assert できました。
TASK [securely access untrusted values directly as Jinja variables instead] ***************************************** ok: [localhost] => { "changed": false, "msg": "All assertions passed" }
例2: command モジュールの例
先程は shell
モジュールの register 変数を利用していました。今度は command モジュールを利用します。
影響を受ける Playbook
- name: task with a module result (always untrusted by Ansible) command: echo "hi mom" register: untrusted_result # don't do it this way... - name: insecure conditional with embedded template consulting untrusted data assert: that: '"hi mom" is in {{ untrusted_result.stdout }}'
結果は以下のとおりです。shell
モジュールと同じエラーです。
TASK [task with a module result (always untrusted by Ansible)] ****************************************************** changed: [localhost] TASK [insecure conditional with embedded template consulting untrusted data] **************************************** fatal: [localhost]: FAILED! => {"msg": "The conditional check '\"hi mom\" is in hi mom' failed. The error was: Condit ional is marked as unsafe, and cannot be evaluated."}
なお、対策は shell
モジュールのときとすべて同じなので省略します。
例3: file
モジュールの例
少し違うタイプのモジュールとして、file
モジュールを利用します。
影響を受ける Playbook
- name: ansible.builtin.file: path: test.txt register: untrusted_result - name: file check ansible.builtin.assert: that: '{{ untrusted_result.state }} == "file"'
エラーメッセージはこれまでと同じです。
TASK [file check] ************************************************************************************************ fatal: [localhost]: FAILED! => {"msg": "The conditional check 'file == \"file\"' failed. The error was: Conditional is marked as unsafe, and cannot be evaluated."}
対策
assert
モジュールの that
オプションの指定は、以下のように {{ }}
を外します。
- name: file check ansible.builtin.assert: that: untrusted_result.state == "file"
影響を受けない例
これまでの、影響を受ける例のテンプレートの書き方と同じく {{ }}
が含まれますが、利用する変数が unsafe
扱い出ない場合は、エラーにはなりません。
- name: assert assert: that: '"hi mom" is in "{{ out }}"' vars: out: "hi mom"
{{ }}
を利用しない、以下のパターンも正常です。
- name: assert assert: that: '"hi mom" is in out' vars: out: "hi mom"
そもそも unsafe
扱いになる例とは
前述の Playbook 例では、いくつかのモジュールと、問題のあるテンプレートの書き方を組み合わせて、影響を受けることを確認しました。
影響の有無の判断にあたって気になるのが、テンプレートの書き方の他に、どんな値が unsafe
扱いになのかということです。ただ、他のどんなモジュールの register 変数が、unsafe
扱いになるのかは調べきれませんでした。
逆に、set_fact
、include_vars
モジュールの register 変数は unsafe
ではないようでした(このあたりにたどり着きました)。ただ、これらのモジュールはそのモジュールのオプション内で指定した変数やファイルを読み込んで変数を定義するので、register 変数は使う機会は多くない気がします。
また、register 変数以外に以外にも、unsafe
扱いになるケースがいくつかあります。以下3つ紹介します。
変数定義に !unsafe
タグを付けた場合
Playbook としての YAML では !unsafe
というタグを付けることができます。この場合、文字どおり unsafe
扱いになります。(!
は否定の意味ではなくYAMLのタグの書式)
例:
- name: check msg assert: that: "'hello' == '{{ msg }}'" # これはエラーになる vars: msg: !unsafe hello # 変数 msg は unsafe 扱い
vars_prompt
で unsafe: true
を指定した場合
vars_prompt
で、unsafe
オプションを true
に指定した場合も unsafe
扱いです(デフォルトは false
)。
例:
# ...(略)... vars_prompt: - name: username private: false unsafe: true # この指定でこの変数は unsafe 扱いになる tasks: # ...(略)...
ルックアッププラグイン
試した限り、file
ルックアッププラグインで得た内容は unsafe
扱いでした。
以下の例はエラーになります。
- name: check file assert: that: "'hello' is in '{{ lookup('file', 'test.txt')' }}"
以下の例はエラーになりません。
- name: check file assert: that: "'hello' is in lookup('file', 'test.txt')"
他のルックアッププラグインがどうかまでは試せていません。
なお、ルックアッププラグイン自体のドキュメントには、allow_unsafe
オプション(デフォルトは true
)の説明があります。
By default, lookup return values are marked as unsafe for security reasons. If you trust the outside source your lookup accesses, pass allow_unsafe=True to allow Jinja2 templates to evaluate lookup values.
コレクションの integration test への影響も
各コレクションの integration test では、おそらく本影響のためであろう修正をちらほら見かけました。修正の仕方を見ると参考になります。
awx.awx
コレクション
https://github.com/ansible/awx/pull/14702/files
azure.azcollection
コレクション
https://github.com/ansible-collections/azure/pull/1354/files
amazon.aws
コレクション
https://github.com/ansible-collections/amazon.aws/pull/1891/files
おわりに
他のどんなモジュールの register 変数が、unsafe
扱いになるのかは調べきれませんでしたので、ネストしたテンプレートを書かないようにするのが良いかなと思いました。
参考
関連PR(実装)
関連PR(ドキュメント)