ansible-policyを動かしてみる

今関わっているチームでサービスのインフラの構成管理でAnsibleを利用しています。
Ansible playbookを複数人でメンテナンスしていることもあり、Playbookの書き方や制約などを設けたいと考え始めました。

何か仕組みがないか探しているところで、ansible-policy というものを見つけたのでこれを動かしてみたいと思います。
プロトタイプ段階のプロジェクトのようです。

https://github.com/ansible/ansible-policy

# 動かしてみる
READMEに従って動かしてみます。
当方の環境は、macOS Sonomaで進めます。

## 1. `opa`コマンドのインストール
OPA(Open Policy Agent)を利用するため`opa`コマンドのインストールをインストールします。

私が確認した時点では最新のバージョンの`opa`コマンドでは正常に動作しないため、過去のバージョンのものをインストールします。
```shell
curl -LO https://github.com/open-policy-agent/opa/releases/download/v0.69.0/opa_darwin_arm64_static
chmod +x opa_darwin_arm64_static
mv opa_darwin_arm64_static opa
sudo mv opa /usr/local/bin/
```
## 2. ansible-policyの取得

```shell
git clone git@github.com:ansible/ansible-policy.git
cd ansible-policy
```

## 3. `ansbile-policy`コマンドのインストール
```shell
python3 -m venv venv-ansible-policy
source ./venv-ansible-policy/bin/activate
pip install -e .
```

## 4. ポリシーブックの準備
いくつかのポリシーブックの例が`examples/check_project/policies/` に存在しています。

### 承認されたパッケージのみがインストールされているかどうかを確認
```yaml
# package-example.yml
---
- name: Check for mysql package installation
  hosts: localhost 
  vars:
    allowed_packages:
      - "mysql-server"
  policies:
    - name: Check for package name
      target: task
      condition: input["ansible.builtin.package"].name not in allowed_packages
      actions:
        - deny:
            msg: The package {{ input["ansible.builtin.package"].name }} is not allowed, allowed packages are one of {{ allowed_packages }}
      tags:
        - compliance
```

### 許可されたコレクションのみが使用されているかどうかを確認
```yaml
# package-example.yml
---
- name: Check for using collection
  hosts: localhost
  vars:
    allowed_collections:
      - ansible.builtin
      - amazon.aws
  policies:
    - name: Check for collection name
      target: task
      condition: input._agk.task.module_info.collection not in allowed_collections
      actions:
        - deny:
            msg: The collection {{ input._agk.task.module_info.collection }} is not allowed, allowed collection are one of {{ allowed_collections }}
      tags:
        - compliance
```

### become: true が使用されているかどうかを確認し、信頼できるユーザーのみが使用されているかどうかを確認
```yaml
# package-example.yml
---
- name: Check for privilage escalation
  hosts: localhost 
  vars:
    allowed_users:
      - "trusted_user"
  policies:
    - name: Check for using become in task
      target: task
      condition: 
        any:
        - input.become == true and input.become_user not in allowed_users
        - input.become == true and input lacks key become_user
      actions:
        - deny:
            msg: privilage escalation is detected. allowed users are one of {{ allowed_users }}
      tags:
        - compliance
    - name: Check for using become in play
      target: play
      condition: 
        any:
        - input.become == true and input.become_user not in allowed_users
        - input.become == true and input lacks key become_user
      actions:
        - deny:
            msg: privilage escalation is detected. allowed users are one of {{ allowed_users }}
      tags:
        - compliance
```

## 5. ポリシー評価を実行する
今回ポリシーを評価するPlaybookがこちらです。
```yaml
---
- name: Provision EC2 instance and set up MySQL
  hosts: localhost
  gather_facts: false
  become: True
  vars:
    region: "your_aws_region"
    instance_type: "t2.micro"
    ami_id: "your_ami_id"
    key_name: "your_key_name"
    security_group: "your_security_group_id"
    subnet_id: "your_subnet_id"
    mysql_root_password: "your_mysql_root_password"
    package_list:
    - unauthorized-app
  tasks:
    - name: Create EC2 instance
      amazon.aws.ec2_instance:
        region: "{{ region }}"
        key_name: "{{ key_name }}"
        instance_type: "{{ instance_type }}"
        image_id: "{{ ami_id }}"
        security_group: "{{ security_group }}"
        subnet_id: "{{ subnet_id }}"
        assign_public_ip: true
        wait: yes
        count: 1
        instance_tags:
          Name: "MySQLInstance"
      register: ec2

    - name: Install Unauthorized App
      become: true
      ansible.builtin.package:
        name: "{{ package_list }}"
        state: present

    - name: Set MySQL root password [using unauthorized collection]
      community.mysql.mysql_user:
        name: root
        password: "{{ mysql_root_password }}"
        host: "{{ item }}"
        login_unix_socket: yes
      with_items: ["localhost", "127.0.0.1", "::1"]
```

実行します、そうすると3つのポリシー違反が表示されました。

```shell
$ ansible-policy -p examples/check_project/playbook.yml --policy-dir examples/check_project/policies
PLAY [Provision EC2 instance and set up MySQL] examples/check_project/playbook.yml L2-45 **********************************************************************************************************************************************
... Check_for_using_become_in_play Not Validated
    privilage escalation is detected. allowed users are one of ["trusted_user"]

TASK [Install Unauthorized App] examples/check_project/playbook.yml L32-37 ************************************************************************************************************************************************************
... Check_for_using_become_in_task Not Validated
    privilage escalation is detected. allowed users are one of ["trusted_user"]

TASK [Set MySQL root password [using unauthorized collection]] examples/check_project/playbook.yml L38-45 *****************************************************************************************************************************
... Check_for_collection_name Not Validated
    The collection community.mysql is not allowed, allowed collection are one of ["ansible.builtin", "amazon.aws"]

... Check_for_package_name Not Validated
    The package unauthorized-app is not allowed, allowed packages are one of ["mysql-server"]

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SUMMARY
... Total files: 1, Validated: 0, Not Validated: 1

Violations are detected! in 1 play, 2 tasks

```

ポリシー違反を元に修正をします。
PlayとTaskにbecome_userを指定、許可されたパッケージ名を指定して修正。
```shell
cp examples/check_project/playbook.yml examples/check_project/playbook.yml.org
```
```diff
$ diff examples/check_project/playbook.yml.org examples/check_project/playbook.yml
5a6
>   become_user: "trusted_user"
15c16
<     - unauthorized-app
---
>     - mysql-server
33a35
>       become_user: "trusted_user"
```

再度実行をします、1つのポリシー違反が表示されました。
community.mysqlコレクションが使えるようにポリシーを修正します。
```shell
$ ansible-policy -p examples/check_project/playbook.yml --policy-dir examples/check_project/policies
TASK [Set MySQL root password [using unauthorized collection]] examples/check_project/playbook.yml L40-47 *****************************************************************************************************************************
... Check_for_collection_name Not Validated
    The collection community.mysql is not allowed, allowed collection are one of ["ansible.builtin", "amazon.aws"]

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SUMMARY
... Total files: 1, Validated: 0, Not Validated: 1

Violations are detected! in 1 task

```

```shell
cp examples/check_project/policies/check_collection.yml examples/check_project/policies/check_collection.yml.org
```

```diff
$ diff examples/check_project/policies/check_collection.yml examples/check_project/policies/check_collection.yml.org
9d8
<       - community.mysql
```

再度実行します、ポリシー違反がなくなりました。
```shell
$ ansible-policy -p examples/check_project/playbook.yml --policy-dir examples/check_project/policies
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SUMMARY
... Total files: 1, Validated: 1, Not Validated: 0

No violations are detected

```



# 感想
yamlでポリシーを定義できるのはいいなと思いました、Rego言語を習得するハードルが少し高いと感じたので親しみのあるyamlで記述できるのは良いと感じました。

今後チームで、セキュリティやアクセス制限などクリティカルな部分を操作するAnsible taskなどでこの仕組みを用いて制約をかけるトライをやってみる予定です。

今回はスタンダードな使い方を紹介しましたが、他の機能もあるため一通りプロトタイプを触ってみたいと思います。