Meraki MXのFWルールを一括インポートしたい

# はじめに
この記事はシスコの有志による [Cisco Systems Japan Advent Calendar 2022](https://qiita.com/advent-calendar/2022/cisco) として投稿しています。

# やりたいこと
FWを他の製品に置き換えるときに大変なのがFWルールの移行がひとつに挙げられます。お客様によっては数百行となるルールを移行しなければならないケースもあります。CLIベースの製品であればコマンドを流すことで比較的工数は抑えられるのですが、Merakiの場合はCLIはなく、ダッシュボードからのGUI操作となるため、一括でFWルールを流し込みたいという要件を満たすことができません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325571/7ac34e00-8b9e-75de-0527-3cc54f63de0f.png)
そこで役立つのがDashboard APIです。MXには[Update Network Appliance Firewall L3 Firewall Rules](https://developer.cisco.com/meraki/api-v1/#!update-network-appliance-firewall-l-3-firewall-rules)を使って一括インポートさせることが可能です。このAPIを使ってCSV形式のFWルールを一括インポートしてみたいと思います。

# L3FWルール　APIエンドポイント解説
このAPIエンドポイントのURLはこちらになります。メソッドはPUTになります。
```http
PUT /networks/{networkId}/appliance/firewall/l3FirewallRules
```
このAPIエンドポイント利用する上で一番のポイントはBODYでL3FWルールを下記JSONフォーマットで送信する必要があります。CSVの1行ごとを下記フォーマットに成型する作業がPOSTする前に必要となります。複数ルールになる場合は","で追加していく必要があり、効率よく成型するためにはテンプレート機能が有効的です。
```json
{
    "rules": [
        {
            "comment": "Allow TCP traffic to subnet with HTTP servers.",
            "policy": "allow",
            "protocol": "tcp",
            "destPort": "443",
            "destCidr": "192.168.1.0/24",
            "srcPort": "Any",
            "srcCidr": "Any",
            "syslogEnabled": false
        }
    ]
}
```
# Jinja2テンプレートで成型する
今回は効率よくCSVの1行ごとのルールをJSONフォーマットに成型するためにJinja2のテンプレートを使用します。Jinja2テンプレートについては@simonritchieさんの[記事](https://qiita.com/simonritchie/items/cc2021ac6860e92de25d)が参考になるので是非一読ください。

CSVファイルのフォーマットは以下となります。
```
comment,policy,protocol,src_port,src_cidr,dst_port,dst_cidr,syslog
```
このフォーマットに合わせてj2ファイルを準備します。
```jinja
{
  "rules": [
    {%- for rule in data %}
      {
          "comment": "{{ rule.comment }}",
          "policy": "{{ rule.policy }}",
          "protocol": "{{ rule.protocol }}",
          "destPort": {{ rule.dst_port }},
          "destCidr": "{{ rule.dst_cidr }}",
          "srcPort": "{{ rule.src_port }}",
          "srcCidr": "{{ rule.src_cidr }}",
          "syslogEnabled": {{ rule.syslog }}
      }
    {%- if loop.index < data|length -%}
    ,
    {%- endif %}
    {%- endfor %}
  ]
}
```
コードとしては、このj2をget_templateで取り込み、レンダリングするという流れになります。
```python
    template = env.get_template('meraki_policy.j2')

    data = [i for i in csv.DictReader(open(args[1]))]
    post_data = template.render({"data": data})
```

# 実際にやってみる
以下のようなCSVファイルを準備します。
```l3rule_test.csv
comment,policy,protocol,src_port,src_cidr,dst_port,dst_cidr,syslog
test1,allow,tcp,any,any,1001,1.1.1.1/32,false
test2,allow,tcp,any,any,1002,1.1.1.1/32,false
test3,allow,tcp,any,any,1003,1.1.1.1/32,false
test4,allow,tcp,any,any,1004,1.1.1.1/32,false
test5,allow,tcp,any,any,1005,1.1.1.1/32,false
```
pythonのメインはこちら。apikeyと該当MXのnetidをご自身のものに置き換えてください。
```python
# -*- coding: utf-8 -*-
import sys
import requests
from jinja2 import Template, Environment, FileSystemLoader
import csv

args = sys.argv

baseUrl = "https://api.meraki.com/api/v1"
apikey = "YOUR_MERAKI_API_KEY"
netid = "YOUR_NETWROK_ID"


def main():
    headers = {'X-Cisco-Meraki-API-Key': apikey, 'Content-Type': 'application/json'}
    api_url = baseUrl + '/networks/' + netid + '/appliance/firewall/l3FirewallRules'

    try:
        r = None
        env = Environment(loader=FileSystemLoader('./templates'))
        template = env.get_template('meraki_policy.j2')

        data = [i for i in csv.DictReader(open(args[1]))]
        post_data = template.render({"data": data})

        print(post_data)

        r = requests.put(api_url, data=post_data, headers=headers)
        status_code = r.status_code
        resp = r.text
        print("Status code is: " + str(status_code))
        if status_code == 200:
            print("Update L3 firewall rule was successful...")
            return resp
        else:
            print("Error occurred in POST L3 firewall rule --> " + resp)
    except requests.exceptions.HTTPError as err:
        print("Error in connection --> " + str(err))
    finally:
        if r: r.close()

if __name__ == '__main__':
    main()
```
上記のままですとPOST前に確認のためPOSTするデータをPRINTするようにしてますので、コメントアウトしてもらってもいいです。

3000ルールまでは一括で問題なくインポートできることは確認済です。MerakiのUIで1000以上のルールがあるとGUI表示パフォーマンスは大丈夫なの？と気にされる方も実際に試してもらうと表示パフォーマンスを確認できますね。MX実機がなくても試せますから。

今回のサンプルは[github](https://github.com/skatayam/meraki_import_mx_l3_rules)に公開していますのでご活用ください。

# まとめ
MerakiはGUIベースだからFWルールの一括インポートには向いてないよね？と思われている方、もしくはそう思っている方がいれば是非このAPIを知っていただきたいです。今回はPUTでのインポートを行いましたが、GETのAPIエンドポイントも用意されているので設定されているルールの一覧を取得することも可能です。
また、Jinja2テンプレートを使えば繰り返しのデータをAPIに求められるフォーマットに効率よく成型することが可能です。コードも上記の通り非常にシンプルですので、是非試してみてはいかがでしょうか。
MerakiのAPIはアカウントページからAPIキーさえ生成してしまえば誰でも簡単に、すぐに利用することが可能です。GUIはなるべくシンプルにしたいというブランドコンセプトのため、お客様の要件、やりたいこと、見たいことを１００%満たすことはできないです。そのためにMerakiではAPIエンドポイントの開発・提供に力を入れており、Meraki製品と3rd Party製品との連携など新しい価値の想像を目指しています。是非APIを活用してみてください。