Cisco MerakiをAnsibleから操作する(②Firewallポリシー操作)

# はじめに
[前回の記事](https://qiita.com/tech_kitara/private/154dd6d9bb16d1ffa152)でCisco Merakiのデバイス情報取得、MSシリーズのスイッチポート操作をAnsibleから行いました。
今回は、MXシリーズ（SD-WANとセキュリティアプライアンス）のレイヤー3 Fiwarallポリシーの取得と設定変更をAnsibleの`meraki_mx_l3_firewall`を使って実施してみました。

環境セットアップやInventoryファイルは前回同様ですので割愛します。

# 1. Firewallポリシー取得
まず始めに、現状のFirewallポリシーを取得します。

## 1-1. ダッシュボード画面
デフォルト状態では、全許可設定×1行だけだったため、その前に拒否設定×2行を追加しています。
![無題1222_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/dff6afb3-ffc7-0f1c-8c0c-892678a77df8.png)

## 1-2. Playbook
タスク1で、`meraki_mx_l3_firewall`モジュールを使って、Firewallポリシーを取得したい組織名・ネットワーク名を指定します。情報取得のため、`state`は`query`を指定します。続いてタスク2で結果を表示しています。

```yaml:playbook_meraki_mx_l3_firewall.yml
---

- hosts: cisco
  gather_facts: no
  connection: local

  tasks:
    - name: Query firewall rules
      meraki_mx_l3_firewall:
        auth_key: "{{ api_key }}"
        org_name: "{{ org_name }}"
        net_name: "{{ net_name }}"
        state: query
      register: result

    - name: display output data
      debug:
        msg: "{{ result.data }}"
```

## 1-3. 実行結果
「1-1. ダッシュボード画面」の結果と同じ3行が表示されました。

```
$ ansible-playbook -i inventory_meraki.ini playbook_meraki_mx_l3_firewall.yml

PLAY [cisco] **********************************************************************************************************

TASK [Query firewall rules] *******************************************************************************************
ok: [meraki]

TASK [display output data] ********************************************************************************************
ok: [meraki] => {
    "msg": [
        {
            "comment": "Block specific HTTPS traffic",
            "dest_cidr": "192.0.12.10/32",
            "dest_port": "443",
            "policy": "deny",
            "protocol": "tcp",
            "src_cidr": "192.168.128.10/32",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Allow traffic to group of servers",
            "dest_cidr": "192.0.12.0/24",
            "dest_port": "Any",
            "policy": "deny",
            "protocol": "any",
            "src_cidr": "192.168.128.0/24",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Default rule",
            "dest_cidr": "Any",
            "dest_port": "Any",
            "policy": "allow",
            "protocol": "Any",
            "src_cidr": "Any",
            "src_port": "Any",
            "syslog_enabled": false
        }
    ]
}

PLAY RECAP ************************************************************************************************************
meraki                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

# 2. Fiwarwallポリシー設定変更（リプレイス）
続いて、ACL×2行を設定してみます。

## 2-1. Playbook
1-2.と同じモジュールを使っています。ただし、`state`は`present`を指定し、`rules`オプションでACL設定を2つ定義しています。

```yaml:playbook_meraki_mx_l3_firewall2.yml
---

- hosts: cisco
  gather_facts: no
  connection: local

  tasks:
    - name: Set two firewall rules
      meraki_mx_l3_firewall:
        auth_key: "{{ api_key }}"
        org_name: "{{ org_name }}"
        net_name: "{{ net_name }}"
        state: present
        rules:
          - comment: Block specific HTTPS traffic
            src_cidr: 192.168.128.2/32
            src_port: any
            dest_cidr: 192.0.2.2/32
            dest_port: "443"
            protocol: tcp
            policy: deny
          - comment: Allow traffic to group of servers
            src_cidr: 192.168.128.0/24
            src_port: any
            dest_cidr: 192.0.2.0/24
            dest_port: any
            protocol: any
            policy: allow
      register: result

    - name: display output data
      debug:
        msg: "{{ result.data }}"
```

## 2-2. 実行結果
結果を見ると、全許可設定の前にあった2行が、今回設定した2行にリプレイスされてしまいました:fearful:

```
$ ansible-playbook -i inventory_meraki.ini playbook_meraki_mx_l3_firewall2.yml

PLAY [cisco] **********************************************************************************************************

TASK [Set two firewall rules] *****************************************************************************************
changed: [meraki]

TASK [display output data] ********************************************************************************************
ok: [meraki] => {
    "msg": [
        {
            "comment": "Block specific HTTPS traffic",
            "dest_cidr": "192.0.2.2/32",
            "dest_port": "443",
            "policy": "deny",
            "protocol": "tcp",
            "src_cidr": "192.168.128.2/32",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Allow traffic to group of servers",
            "dest_cidr": "192.0.2.0/24",
            "dest_port": "Any",
            "policy": "allow",
            "protocol": "any",
            "src_cidr": "192.168.128.0/24",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Default rule",
            "dest_cidr": "Any",
            "dest_port": "Any",
            "policy": "allow",
            "protocol": "Any",
            "src_cidr": "Any",
            "src_port": "Any",
            "syslog_enabled": false
        }
    ]
}

PLAY RECAP ************************************************************************************************************
meraki                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
```

## 2-3. ダッシュボード画面
![無題1222_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/db193ad0-082c-b098-f0f4-e5830fdec232.png)


# 3. Firewallポリシー設定変更（マージ）
Ansible公式ドキュメントを確認したところ、既存設定にACLを追加したい場合は、[Cisco Meraki Guide - Merging Existing and New Data](https://docs.ansible.com/ansible/latest/scenario_guides/guide_meraki.html#merging-existing-and-new-data)に従ったPlaybook作成が必要そうです。
試しに、先ほどの2.の設定に対し、全許可設定の前に全拒否設定を追加してみたいと思います。

## 3-1. Playbook
そのままでは動かなかったため、いくつか修正しています。

- タスク「set new ruleset」でリストデータをappendする際、上記手順ではネスト構造のリスト`[[ a, b ]]`に対し`[[ c, d ]]`をappendをしようとしていたが、これだと`[[ a, b ], [ c, d ]]`となってしまい、設定変更ができない。代わりに`[ a, b ]`に対し`[ c, d ]`をappendするようにし、結果`[ a, b, c, d ]`が得られるようにした。

- タスク「configure firewall rules」で設定変更する際、デフォルトの全許可設定に含まれる`protocol: Any`がエラーではじかれた（正しい値は`protocol: any`）。エラーを防ぐため、`"{{ new_ruleset[:-1] }}"`とする事で、最後の全許可設定を設定対象から除外。（デフォルト設定のため、除外しても最終的な設定は変わらない。）

```yaml:playbook_meraki_mx_l3_firewall4.yml
---

- hosts: cisco
  gather_facts: no
  connection: local

  vars:
    - front_rules: []
    - back_rules: []

  tasks:
    - name: Query firewall rules
      meraki_mx_l3_firewall:
        auth_key: "{{ api_key }}"
        org_name: "{{ org_name }}"
        net_name: "{{ net_name }}"
        state: query
      register: rules

    - name: set output data
      set_fact:
        original_ruleset: "{{ rules.data }}"

    - name: set new rule
      set_fact:
        new_rule:
          - comment: Block all traffic
            src_cidr: Any
            src_port: Any
            dest_cidr: Any
            dest_port: Any
            protocol: any
            policy: deny

    - name: set front rules
      set_fact:
        front_rules: "{{ original_ruleset[:2] }}"

    - name: set back rules
      set_fact:
        back_rules: "{{ original_ruleset[2:] }}"

    - name: set new ruleset
      set_fact:
        new_ruleset: "{{ front_rules + new_rule + back_rules }}"

    - name: display new ruleset
      debug:
        msg: "{{ new_ruleset[:-1] }}"

    - name: configure firewall rules
      meraki_mx_l3_firewall:
        auth_key: "{{ api_key }}"
        org_name: "{{ org_name }}"
        net_name: "{{ net_name }}"
        state: present
        rules: "{{ new_ruleset[:-1] }}"
      register: result

    - name: display output data
      debug:
        msg: "{{ result.data }}"
```

## 3-2. 実行結果
最後のタスクで、想定通り設定変更された事が分かります。

```
$ ansible-playbook -i inventory_meraki.ini playbook_meraki_mx_l3_firewall4.yml

PLAY [cisco] **********************************************************************************************************

TASK [Query firewall rules] *******************************************************************************************
ok: [meraki]

TASK [set output data] ************************************************************************************************
ok: [meraki]

TASK [set new rule] ***************************************************************************************************
ok: [meraki]

TASK [set front rules] ************************************************************************************************
ok: [meraki]

TASK [set back rules] *************************************************************************************************
ok: [meraki]

TASK [set new ruleset] ************************************************************************************************
ok: [meraki]

TASK [display new ruleset] ********************************************************************************************
ok: [meraki] => {
    "msg": [
        {
            "comment": "Block specific HTTPS traffic",
            "dest_cidr": "192.0.2.2/32",
            "dest_port": "443",
            "policy": "deny",
            "protocol": "tcp",
            "src_cidr": "192.168.128.2/32",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Allow traffic to group of servers",
            "dest_cidr": "192.0.2.0/24",
            "dest_port": "Any",
            "policy": "allow",
            "protocol": "any",
            "src_cidr": "192.168.128.0/24",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Block all traffic",
            "dest_cidr": "Any",
            "dest_port": "Any",
            "policy": "deny",
            "protocol": "any",
            "src_cidr": "Any",
            "src_port": "Any"
        }
    ]
}

TASK [configure firewall rules] ***************************************************************************************
changed: [meraki]

TASK [display output data] ********************************************************************************************
ok: [meraki] => {
    "msg": [
        {
            "comment": "Block specific HTTPS traffic",
            "dest_cidr": "192.0.2.2/32",
            "dest_port": "443",
            "policy": "deny",
            "protocol": "tcp",
            "src_cidr": "192.168.128.2/32",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Allow traffic to group of servers",
            "dest_cidr": "192.0.2.0/24",
            "dest_port": "Any",
            "policy": "allow",
            "protocol": "any",
            "src_cidr": "192.168.128.0/24",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Block all traffic",
            "dest_cidr": "Any",
            "dest_port": "Any",
            "policy": "deny",
            "protocol": "any",
            "src_cidr": "Any",
            "src_port": "Any",
            "syslog_enabled": false
        },
        {
            "comment": "Default rule",
            "dest_cidr": "Any",
            "dest_port": "Any",
            "policy": "allow",
            "protocol": "Any",
            "src_cidr": "Any",
            "src_port": "Any",
            "syslog_enabled": false
        }
    ]
}

PLAY RECAP ************************************************************************************************************
meraki                     : ok=9    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
```

## 3-3. ダッシュボード画面
ダッシュボードにも問題なく設定反映されていました。
![無題1222_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/681226bb-25b9-ae5c-9b84-1f2e9798f60e.png)


# 最後に
少しトリッキーなやり方ですが、Firewallポリシーの設定追加までできました。今回はポリシー設定を直接Playbook内に記載しましたが、例えばCSVファイルに定義したパラメーターを読み込ませることも可能です。
GUI設定は確かに直感的で分かりやすいですが、大量のFirewallポリシーをメンテナンスする際は、今回ご紹介したREST APIとAnsibleの組み合わせも選択肢の一つになるのではないでしょうか。