RESTCONFとPythonでACLを自動作成してみた

この記事は[FUJITSU Advent Calendar 2020](https://qiita.com/advent-calendar/2020/fujitsu)の20日目の記事です。
記事の内容は全て個人の見解であり、執筆内容は執筆者自身の責任です。所属する組織は関係ありません。

#はじめに
Advent Calendar 初参加です。
筆者は普段ネットワークエンジニアとして活動しております。
私事ですが最近、DevNetAssociateを取得しました！
私がDevNetについて勉強する中でRESTCONFに触れ、これを使って何か面白いことはできないかな？
と思い検証した内容をアドベントカレンダーの記事として投稿したいと思います。

#RESTCONFとは？
RESTCONFはHTTP/HTTPS上で動く、ネットワーク機器の設定情報取得/変更を行うプロトコルです。RESTのルールがベースとなります。
ネットワークエンジニアの方では聞いたことがあったり、実際に使ったことのある方がいらっしゃると思います。
一例として、RESTCONFで機器の設定情報を取得しますと、下のスクショのようにXMLまたはJSON形式で情報を取り出せます。
![スクリーンショット 2020-12-10 14.09.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/3bac701a-56e7-26fe-d398-f50f6033572c.png)

#検証環境
この記事のネタを検証した際の環境は以下のとおりです。
CML（Cisco Modelling Labs）を使用し、メインで触る機器をCSR1000v、　ping用マシンとしてubuntuを３台接続しています。
![RESTCONF-memo(1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/d78947db-9555-2ecb-b82f-2d6fc34cc0ae.png)

CSR1000vのIOSバージョンは16.11.01bです。
RESTCONFの実行環境はMacで、Postmanを使用しています。
#ネットワーク機器の事前準備
RESTCONFを有効化するために以下の数行のコマンドを投入しています。

```
 #RESTCONF有効化
 restconf
 ip http secure-server

 その他、AAA authorizationの設定等しておきます。
 ※RESTCONFはBasic認証を用います。
```
参考
https://www.cisco.com/c/ja_jp/td/docs/ios-xml/ios/prog/configuration/1610/b_1610_programmability_cg/b_1610_programmability_cg_chapter_01000.html

#RESTCONFによる設定変更
早速RESTCONFを使ってネットワーク機器の設定変更をしてみます。
今回はNetFlowを設定してみます。
仕事柄、NW内で変なトラフィックが流れていないか、正常な通信が流れているか確認することがたまにあります。
その際いちいち手動で一時的にフローモニターなど設定し、終わったら設定をキレイに消すことが面倒だった経験がありました。
そのくらいサクッと設定しろと言われるかもしれませんが、ご容赦ください。。
では、RESTCONFでNetflowの設定ができるか試してみます。

##１．Postmanから、PUTメソッドでFlexibleNetFlowを設定
![スクリーンショット 2020-12-10 15.41.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/d81aeafc-0379-ef8d-bd04-07dce800cc62.png)
Postmanで実行してみるとこんな感じになります。
RESTのGETが読み取り用に対してPUTは更新用のメソッドです。

スクショの通り、Bodyの中身に設定したい内容をJSON形式で作り、PUTメソッドで機器に送信しています。
どうやってこのJSONデータを作ったのかというと、一度手動で従来どおりにNetFlowの設定を入れます。その状態でGETメソッドで情報を確認するとJSONまたはXML形式でデータ構造を覗けます。
その結果をもとに設定内容を作っています。（他に良い方法を知らないため。。）
![スクリーンショット 2020-12-10 15.51.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/ac5a52aa-82c5-bd87-a291-ce319cada702.png)
注意点として、**ヘッダーのAccept(Content-Type)で「application/yang-data+json」を指定しましょう**。無指定の場合はXMLで返ってきます。XML→JSONの変換が面倒なので、ヘッダーの指定をしておくと便利です。


##２．インタフェースにFlow monitorを設定
続いて、インタフェースにFlow Monitorを入れていきます。
![スクリーンショット 2020-12-10 16.50.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/94402d4e-5c36-7ca7-5a19-eaf842482210.png)
このような結果が返ってきます。
上記と同様、PUTで渡すJSONデータは一旦手動でインタフェースにフローモニタを入れて、その状態でGETメソッドで中身を確認し、データを作っています。

##３．フローを確認
では、実際にトラフィックを流し、キャッシュを取得してみます。
![スクリーンショット 2020-12-10 17.10.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/ee3bba9a-ebf5-55a5-7950-022a6375b1ae.png)

このような結果となりました。
CLIのshowコマンドで見る方法と違いJSON形式でデータが返ってくるので、データの加工がしやすいメリットがあります。

##４．NetFlowの設定を削除
フローの中身を確認できましたので設定をキレイに消します。
まず、インタフェースに設定されているフローモニターを消して・・・
![スクリーンショット 2020-12-10 17.34.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/b68bf253-67c4-fc74-10c0-17fb54d5b072.png)

最後にFlexible Netflowの設定を消します。
![スクリーンショット 2020-12-10 17.39.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/6b11a109-2b7b-f07c-d990-69c0dd9b8f93.png)

コマンド体系を把握していなくてもPostmanからGUI操作で簡単に設定を消せます。
CLI操作に不慣れな人でもGUIならハードルは下がると思います。
また、設定のゴミが残ったり違うポートの設定を誤って消すといったことも削減できるのかなと思いました。

#Pythonスクリプトとして動かしてみる
ここまでPostmanでNetflowの設定変更をしてみました。
では複数の作業をPythonスクリプトとして動かしてみます。
**※検証環境のため警告表示をわざと非表示にしています。**

```python:NetFlow_Create&Delete.py
import requests
import json
import warnings
warnings.filterwarnings('ignore')

routeraddress = 'A.A.A.A' #操作したい機器のアドレス
base_url = 'https://' + routeraddress + '/restconf/data/Cisco-IOS-XE-native:native'
header = {
  'Accept': 'application/yang-data+json',
  'Content-Type': 'application/yang-data+json',
  'Authorization': 'Basic ＊＊＊＊＊' #Basic認証で使う文字列
}

#Flexible Netflowを設定する
print("＊＊＊Flexible Netflowを設定します＊＊＊")
url = base_url + '/flow'
payload = "{\n  \"Cisco-IOS-XE-native:flow\": {\n    \"Cisco-IOS-XE-flow:record\": [\n      {\n        \"name\": \"IPFlow\",\n        \"match\": {\n          \"interface\": { \"input\": {} },\n          \"ipv4\": {\n            \"destination\": { \"address\": [ null ] },\n            \"source\": { \"address\": [ null ] }\n          }\n        }\n      }\n    ],\n    \"Cisco-IOS-XE-flow:monitor\": [\n      {\n        \"name\": \"IPFlow\",\n        \"cache\": {\n          \"timeout\": {\n            \"active\": 604800,\n            \"inactive\": 604800\n          }\n        },\n        \"record\": { \"type\": \"IPFlow\" }\n      }\n    ]\n  }\n}"

headers = header
response = requests.request("PUT", url, headers=headers, data = payload, verify=False)
#print(response.text.encode('utf8'))
print("FlexibleNetflowの設定完了！！")

#フローモニターを任意のインタフェースに設定する。
interfaceNo = input("どのインタフェースにフローモニタを設定しますか？（ex.1,2,3..）＞")
url = base_url + '/interface/GigabitEthernet=' + interfaceNo + "/ip/Cisco-IOS-XE-flow:flow"

payload = "{\n  \"Cisco-IOS-XE-flow:flow\": {\n    \"monitor\": [\n      {\n        \"name\": \"IPFlow\",\n        \"input\": [\n            null\n        ]\n      }\n    ]\n  }\n}\n"
headers = header

response = requests.request("PUT", url, headers=headers, data = payload, verify=False)
#print(response.text.encode('utf8'))
print('＊＊＊フローモニター設定中＊＊＊')
print("GigabitEthernet", interfaceNo, 'にフローモニターを設定しました！')

Sakujo = input("Netflow設定を削除しますか？（Yes or No） > ")
Anser = str(Sakujo)
if Anser == str("Yes"):
    #Yesだったら設定を削除する。
    #まずインタフェースのフローモニターを削除
    url1 = base_url + '/interface/GigabitEthernet=' + interfaceNo +  "/ip/flow"
    payload = {}
    headers = header

    response = requests.request("DELETE", url1, headers=headers, data = payload, verify=False)
    print("GigabitEthernet", interfaceNo, "のフローモニターを削除しました！！")

    #続いてFlexibleNetflowの削除を実施。
    url2 = base_url + '/flow'
    payload = {}
    headers = header
    response = requests.request("DELETE", url2, headers=headers, data = payload, verify=False)
    print("FlexibleNetflowの設定を削除しました！")
    print("＊＊＊削除完了＊＊＊")

elif Anser == str("No"):
    #Noが入力されたら設定を消さずに処理終了。
    print("わかりました。設定を消さずに残しておきます。消す場合はもう一度スクリプトを実行してください。")

else:
    #それ以外の文字が入力されたら下のメッセージを表示。
    print("すみませんが入力ミスです・・・。もう一度スクリプトを実行してください！")
```

実行すると以下の通りになります。
![スクリーンショット 2020-12-11 14.54.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/9c66819b-7029-d197-0e20-fdb1a9d1065f.png)

フローのキャッシュを取得するスクリプトは以下です。

```Python:Netflow_Cache.py
import requests
import json
import warnings
warnings.filterwarnings('ignore')

url = "https://<機器のアドレス>/restconf/data/Cisco-IOS-XE-flow-monitor-oper:flow-monitors"

payload = {}
headers = {
  'Accept': 'application/yang-data+json',
  'Authorization': 'Basic *****' #Basic認証で使う文字列
}

print("フローのキャッシュを取得します・・・")
response = requests.request("GET", url, headers=headers, data = payload, verify=False).json()
cachedata = json.dumps(response, indent=4).replace('\\n', '\n')

#print(response.text.encode('utf8'))
print(cachedata)
```

実行結果↓
![スクリーンショット 2020-12-11 15.16.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/54cf25b4-76bb-dd9d-9d8d-a8e80847514a.png)



Postmanには**実行した結果をPython形式でスクリプトを出力してくれる機能があります。**
プログラミング経験の無い方でもスクリプトを作成できるのでとても便利です。

#NetFlowキャッシュからACLを作ってみる
先程のスクリプトでNetFlowキャッシュデータを取得する様子をご覧いただきました。
JSONデータはPythonと相性が良く、特定のデータを抽出して活用できます。
以下のスクリプトを作成してみました。

```Python:Netflow_Cache_ACL.py
import requests
import json
import warnings
warnings.filterwarnings('ignore')

url = "https://<機器のアドレス>/restconf/data/Cisco-IOS-XE-flow-monitor-oper:flow-monitors"

payload = {}
headers = {
  'Accept': 'application/yang-data+json',
  'Authorization': 'Basic Y2lzY286Y2lzY28='
}

print("フローのキャッシュを取得します・・・")
response = requests.request("GET", url, headers=headers, data = payload, verify=False).json()
cachedata = json.dumps(response, indent=4).replace('\\n', '\n') #文字列として表示する場合
print('**********↓↓JSONデータの全量↓↓**********')
print(cachedata)

print('**********↓↓データを抽出してアクセスリスト作成↓↓**********')
print('---------------------')
count = 0 #送信元アドレス「１」という風に表示したいので定義。
count2 = 0 #アクセスリストの番号を設定するため定義。
for key in response['Cisco-IOS-XE-flow-monitor-oper:flow-monitors']['flow-monitor'][0]['flows']['flow']:
    count += 1 #１からカウントアップさせる。
    print('送信元アドレス',count, ':',key['source-address'] ) #JSONデータ内の送信元アドレスだけ抽出
    #print(key['source-address'])
    print('宛先アドレス',count, ':', key['destination-address']) #JSONデータの宛先アドレスだけ抽出
    #print(key['destination-address'])

    print('---------------------') #区切りの線。なくても良い。
    count2 += 10
    print('Access-list No.', count)
    print(count2, 'permit ip host',key['source-address'], 'host', key['destination-address'] )
    print('---------------------') #区切りの線。なくても良い。
```

実行結果↓
![スクリーンショット 2020-12-17 11.47.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472957/d7417bf5-9b84-3080-0243-df9116442820.png)

あくまでも、キャッシュで取得できている通信が正常なものであることが前提になりますが
ACLを自動で作り出せました。

突貫工事で作ったためスクリプトの改善点は多くあります・・・。
引き続き便利なスクリプトを作っていきたい所存です。
Netflow,ACLに限らず他の設定変更でも試してみたいですね。

#最後に
ここまで記事を読んでいただきましてありがとうございます。

さて既出ネタとは思いますが、RESTCONFを使ってネットワーク機器の設定変更をやってみました。
従来のターミナルソフトから機器へ接続するのとだいぶ違う方法になりますが
Postmanのようなアプリケーションから数回のクリックで設定変更ができることを御覧いただきました。
そしてPythonスクリプトにして組み上げると、より便利に作業を自動化できることも紹介しました。

筆者は正直プログラミングが苦手です:frowning2:（学習し始めて約１年半）
ただし、私のレベルの人でも簡単な部分（設定情報を引っ張ってみるとか）から始めていくと記事に書いたことはすぐにできます！
興味を持たれた方はぜひRESTCONFを触ってみてください！

今後、ネットワークの運用管理の自動化がますます加速していくと思います。
このようなツールを使って自動化できる作業はどんどんマシンに任せていきたいですね。

#付録
RESTCONFやってみたいけど機器が手元にない。。
そんな方は以下のリンク先を覗いてみてください。仮想環境のCSR1000vを触れます。
（筆者はCisco社の回し者ではありません）
[Cisco IOS XE Sandboxes](https://developer.cisco.com/docs/ios-xe/#!sandboxes/cisco-ios-xe-sandboxes)