はじめに
モデル駆動型プログラマビリティで使用されるトランスポートプロトコル(NETCONF、RESTCONF、gRPC)の内、RESTCONFを使ってCisco IOS-XEのACL設定を行った時のメモです。
操作ツールとしては、Postman、Python、Ansible等がありますが、今回はPythonを使い、名前付き拡張ACLに対してCRUD(作成、取得、更新、削除)を行いました。
1. RESTCONF概要
RESTCONFは、RESTFulインターフェースを提供するHTTP(S)ベースのプロトコルです。データ形式として、XMLに加えJSON形式もサポートしています(NETCONFはSSHベースでXML形式のみ)。
RESTCONF/NETCONFのCRUD操作
RESTCONF | NETCONF |
---|---|
GET |
<get> , <get-config>
|
POST |
<edit-config> (operation="create") |
PUT |
<edit-config> (operation="create/replace") |
PATCH |
<edit-config> (operation="merge") |
DELETE |
<edit-config> (operation="delete") |
IOSはもともとREST APIもサポートしていますが、REST APIがベンダー固有の実装なのに対し、RESTCONFはRFC8040で標準化されたプロトコルであり、別物になります。
RESTCONFを含むモデル駆動型プログラマビリティの概要は、DevNet Learning Labsの以下セッションで分かり易くまとめられています。
モデル駆動型プログラマビリティの紹介
2. 用意した環境
こちらの記事と同様、Cisco dCloud環境を利用させて頂きました。
対象機器は「CSR1000v with IOS XE 16.08」、クライアントのPythonバージョンは「3.6.8」を使用しました。
3. ACL作成
まず、ACL設定が何も無い状態から、ACL名TEST
を1行追加します。
3-1. Pythonコード
URLhttps://[IPアドレス]:[ポート番号]/restconf/data/Cisco-IOS-XE-native:native/ip/access-list
に対し、PUT
メソッドでリクエストを行っています。Body内のデータはJSON形式で定義しています。
ちなみにPOST
メソッドでは作成できませんでした。
#!/usr/bin/python
import requests
import json
# disable warnings from SSL/TLS certificates
requests.packages.urllib3.disable_warnings()
# credentials for CSR1000v
HOST = '[IPアドレス]'
PORT = 443 # 環境によって異なる
USER = '[ユーザ名]'
PASS = '[パスワード]'
def main():
# url string to issue request
url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list".format(h=HOST, p=PORT)
# RESTCONF media types for REST API headers
headers = {'Content-Type': 'application/yang-data+json',
'Accept': 'application/yang-data+json'}
# RESTCONF doby for new ACL
body_data = {
"Cisco-IOS-XE-native:access-list": {
"Cisco-IOS-XE-acl:extended": [
{
"name": "TEST",
"access-list-seq-rule": [
{
"sequence": "30",
"ace-rule": {
"action": "permit",
"protocol": "ip",
"ipv4-address": "192.168.4.0",
"mask": "0.0.0.255",
"dest-ipv4-address": "192.168.100.0",
"dest-mask": "0.0.0.255"
}
}
]
}
]
}
}
# this statement performs a PUT on the specified url
response = requests.put(url, auth=(USER, PASS),
headers=headers, data=json.dumps(body_data), verify=False)
# print the json that is returned
print(response)
if __name__ == '__main__':
main()
3-2. 実行結果
Status Code 204 (No Content)が返ってきました。
$ python create_acl.py
<Response [204]>
Configを見ると、想定通りのACLが作成されていました。
csr1#sh run | begin TEST
ip access-list extended TEST
permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
4. ACL取得
次に、作成したACLをRESTCONFで情報取得してみます。
4-1. Pythonコード(1) IP設定取得
main()
関数以外は3.と同様のため、これ以降は抜粋して記載します。
まず、URLhttps://[IPアドレス]:[ポート番号]/restconf/data/Cisco-IOS-XE-native:native/ip
に対し、IP関連設定の情報をGET
メソッドで取得してみました。
def main():
# url string to issue request
url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip".format(h=HOST, p=PORT)
# RESTCONF media types for REST API headers
headers = {'Content-Type': 'application/yang-data+json',
'Accept': 'application/yang-data+json'}
# this statement performs a GET on the specified url
response = requests.get(url, auth=(USER, PASS),
headers=headers, verify=False)
# print the json that is returned
print(response.text)
4-2. 実行結果(1) IP設定取得
途中の"access-list": { "Cisco-IOS-XE-acl:extended": [ ~ ] }
で、設定した内容を確認できました。
ちなみに、ACL設定前はこの"access-list"
キーを含めて作成されていませんでした。そのため、3.の作成時、下の階層の"Cisco-IOS-XE-acl:extended": [ ~ ]
をいきなりPUTしてもエラーになりました。
$ python get_ip_info.py
{
"Cisco-IOS-XE-native:ip": {
"domain": {
"name": "demo.dcloud.cisco.com"
},
"forward-protocol": {
"protocol": "nd"
},
"route": {
"ip-route-interface-forwarding-list": [
{
"prefix": "0.0.0.0",
"mask": "0.0.0.0",
"fwd-list": [
{
"fwd": "GigabitEthernet1",
"interface-next-hop": [
{
"ip-address": "198.18.128.1"
}
]
}
]
}
]
},
"ssh": {
"rsa": {
"keypair-name": "ssh-key"
},
"version": 2
},
"access-list": {
"Cisco-IOS-XE-acl:extended": [
{
"name": "TEST",
"access-list-seq-rule": [
{
"sequence": "30",
"ace-rule": {
"action": "permit",
"protocol": "ip",
"ipv4-address": "192.168.4.0",
"mask": "0.0.0.255",
"dest-ipv4-address": "192.168.100.0",
"dest-mask": "0.0.0.255"
}
}
]
}
]
},
"Cisco-IOS-XE-http:http": {
"authentication": {
"local": [null]
},
"server": true,
"secure-server": true
}
}
}
4-3. Pythonコード(2) 拡張ACL設定取得
こちらは、より深い階層のURLhttps://[IPアドレス]:[ポート番号]/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/extended
で、拡張ACLのみをピンポイントで取得する例です。
def main():
# url string to issue request
url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/extended".format(h=HOST, p=PORT)
# RESTCONF media types for REST API headers
headers = {'Content-Type': 'application/yang-data+json',
'Accept': 'application/yang-data+json'}
# this statement performs a GET on the specified url
response = requests.get(url, auth=(USER, PASS),
headers=headers, verify=False)
# print the json that is returned
print(response.text)
4-4. 実行結果(2) 拡張ACL設定取得
$ python get_acl.py
{
"Cisco-IOS-XE-acl:extended": [
{
"name": "TEST",
"access-list-seq-rule": [
{
"sequence": "30",
"ace-rule": {
"action": "permit",
"protocol": "ip",
"ipv4-address": "192.168.4.0",
"mask": "0.0.0.255",
"dest-ipv4-address": "192.168.100.0",
"dest-mask": "0.0.0.255"
}
}
]
}
]
}
5. ACLマージ
既存ACLに対し、エントリ(ACE)追加と、別のACLを追加する例をご紹介します。
5-1. Pythonコード(1) エントリ追加
既存のACL名TEST
の2行目に、エントリを追加してみます。先ほどのシーケンス番号30
に対し、今回は50
を指定しています。ちなみにシーケンス番号を指定しない場合、エラーで設定できませんでした。
マージ用のメソッドであるPATCH
を使い、既存設定はそのままで、Body内の内容を追加している形です。
def main():
# url string to issue request
url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list".format(h=HOST, p=PORT)
# RESTCONF media types for REST API headers
headers = {'Content-Type': 'application/yang-data+json',
'Accept': 'application/yang-data+json'}
# RESTCONF doby for new ACL
body_data = {
"Cisco-IOS-XE-native:access-list": {
"Cisco-IOS-XE-acl:extended": [
{
"name": "TEST",
"access-list-seq-rule": [
{
"sequence": "50",
"ace-rule": {
"action": "permit",
"protocol": "ip",
"ipv4-address": "192.168.5.0",
"mask": "0.0.0.255",
"dest-ipv4-address": "192.168.100.0",
"dest-mask": "0.0.0.255"
}
}
]
}
]
}
}
# this statement performs a PUT on the specified url
response = requests.patch(url, auth=(USER, PASS),
headers=headers, data=json.dumps(body_data), verify=False)
# print the json that is returned
print(response)
5-2. 実行結果(1) エントリ追加
Status Code 204 (No Content)が返ってきました。
$ python merge_ace.py
<Response [204]>
Configを見ると、想定通り2行目に追加されていました。
csr1#sh run | begin TEST
ip access-list extended TEST
permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
permit ip 192.168.5.0 0.0.0.255 192.168.100.0 0.0.0.255
5-3. Pythonコード(2) 別のACL追加
今度は、別のACL名TEST2
を1行追加してみます。
def main():
# url string to issue request
url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list".format(h=HOST, p=PORT)
# RESTCONF media types for REST API headers
headers = {'Content-Type': 'application/yang-data+json',
'Accept': 'application/yang-data+json'}
# RESTCONF doby for new ACL
body_data = {
"Cisco-IOS-XE-native:access-list": {
"Cisco-IOS-XE-acl:extended": [
{
"name": "TEST2",
"access-list-seq-rule": [
{
"sequence": "70",
"ace-rule": {
"action": "permit",
"protocol": "ip",
"ipv4-address": "192.168.7.0",
"mask": "0.0.0.255",
"dest-ipv4-address": "192.168.100.0",
"dest-mask": "0.0.0.255"
}
}
]
}
]
}
}
# this statement performs a PUT on the specified url
response = requests.patch(url, auth=(USER, PASS),
headers=headers, data=json.dumps(body_data), verify=False)
# print the json that is returned
print(response)
5-4. 実行結果(2) 別のACL追加
$ python merge_another_acl.py
<Response [204]>
ACL名TEST2
が追加されている事が分かります。
csr1#sh run | begin TEST
ip access-list extended TEST
permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
permit ip 192.168.5.0 0.0.0.255 192.168.100.0 0.0.0.255
ip access-list extended TEST2
permit ip 192.168.7.0 0.0.0.255 192.168.100.0 0.0.0.255
6. ACLリプレイス
今度は、既存ACLを上書きし、別のACL名TEST3
を作成する例です。通常のACL作業では基本的に実施しない(意図せずやってしまうと大事故になる)ケースだと思います。ただ、ACL設定以外では、パラメーターシートを元に「~~の設定は〇〇であるべき」のように宣言型で定義できるので、既存設定削除などの煩わしさ無しで設定できるメリットはあると思います。
6-1. Pythonコード
メソッドは、新規作成時と同様PUT
を用います。
def main():
# url string to issue request
url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list".format(h=HOST, p=PORT)
# RESTCONF media types for REST API headers
headers = {'Content-Type': 'application/yang-data+json',
'Accept': 'application/yang-data+json'}
# RESTCONF doby for new ACL
body_data = {
"Cisco-IOS-XE-native:access-list": {
"Cisco-IOS-XE-acl:extended": [
{
"name": "TEST3",
"access-list-seq-rule": [
{
"sequence": "90",
"ace-rule": {
"action": "permit",
"protocol": "ip",
"ipv4-address": "192.168.9.0",
"mask": "0.0.0.255",
"dest-ipv4-address": "192.168.100.0",
"dest-mask": "0.0.0.255"
}
}
]
}
]
}
}
# this statement performs a PUT on the specified url
response = requests.put(url, auth=(USER, PASS),
headers=headers, data=json.dumps(body_data), verify=False)
# print the json that is returned
print(response)
6-2. 出力結果
$ python replace_acl.py
<Response [204]>
見事に上書されました。
csr1#sh run | begin TEST
ip access-list extended TEST3
permit ip 192.168.9.0 0.0.0.255 192.168.100.0 0.0.0.255
ちなみに、以下記事を書いた方によると、NX-OSの場合、PUT
メソッドがRFC8040の仕様通りではなく、PATCH
メソッドと同様マージになるケースもあるようです。
Exploring IOS-XE and NX-OS based RESTCONF Implementations with YANG and Openconfig
7. ACL削除
最後に、作成したACLの削除を行ってみます。
事前に「3.ACL作成」と「5.ACLマージ」を実行し、TEST3
が上書きされ、TEST
とTEST2
が設定された状態にしておきます。
csr1#sh run | begin TEST
ip access-list extended TEST
permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
permit ip 192.168.5.0 0.0.0.255 192.168.100.0 0.0.0.255
ip access-list extended TEST2
permit ip 192.168.7.0 0.0.0.255 192.168.100.0 0.0.0.255
7-1. Pythonコード
URLhttps://[IPアドレス]:[ポート番号]/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/extended=TEST2
に対し、DELETE
メソッドでリクエストを行います。
URLの末尾に/extended=TEST2
を指定することで、TEST2
をACL単位で削除できます。
(エントリ単位での削除方法はちょっと分かりませんでした。。)
def main():
# url string to issue request
url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/ip/access-list/extended=TEST2".format(h=HOST, p=PORT)
# RESTCONF media types for REST API headers
headers = {'Content-Type': 'application/yang-data+json',
'Accept': 'application/yang-data+json'}
# this statement performs a DELETE on the specified url
response = requests.delete(url, auth=(USER, PASS),
headers=headers, verify=False)
# print the json that is returned
print(response)
7-2. 実行結果
$ python delete_acl.py
<Response [204]>
TEST2
が削除されました。
csr1#sh run | begin TEST
ip access-list extended TEST
permit ip 192.168.4.0 0.0.0.255 192.168.100.0 0.0.0.255
permit ip 192.168.5.0 0.0.0.255 192.168.100.0 0.0.0.255
最後に
まだインターネット上にサンプルコードが少なかったので、お試しでやってみました。以前の記事で、要件資料と複雑なJinja2テンプレートを組み合わせてACL Configの生成を行いましたが、RESTCONFであれば、要件資料のようなパラメーター情報をJSON形式に変換すれば設定ができるため、自動化と相性が良いと思います。
今回はPythonの例でしたが、Ansibleのrestconf_get
とrestconf_config
モジュールを使った例や、設定項目毎のモデル(URL)の確認・作成方法もまとめてみたいと思います。