はじめに
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(ドキュメント)