てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] ansible-core 2.16.1 などに入った変更による assert モジュールのエラー「Conditional is marked as unsafe, and cannot be evaluated.」

はじめに

ansible-core 2.16.12.15.72.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つの条件を両方満たす時に、影響を受けると理解しています。

  1. テンプレートをネストさせた時
  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 オプションにはテンプレート書式の "{{ }}" の中だけを指定します。この場合、さらに {{ }} が含まれてしまっています。changelogNested 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_factinclude_vars モジュールの register 変数は unsafe ではないようでした(このあたりにたどり着きました)。ただ、これらのモジュールはそのモジュールのオプション内で指定した変数やファイルを読み込んで変数を定義するので、register 変数は使う機会は多くない気がします。

また、register 変数以外に以外にも、unsafe 扱いになるケースがいくつかあります。以下3つ紹介します。

変数定義に !unsafe タグを付けた場合

Playbook としての YAML では !unsafe というタグを付けることができます。この場合、文字どおり unsafe 扱いになります。(! は否定の意味ではなくYAMLのタグの書式)

https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_advanced_syntax.html#unsafe-or-raw-strings

例:

    - name: check msg
      assert:
        that: "'hello' == '{{ msg }}'"    # これはエラーになる
      vars:
        msg: !unsafe hello      # 変数 msg は unsafe 扱い

vars_promptunsafe: 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(実装)

github.com

関連PR(ドキュメント)

github.com