Cisco SD-WANでZoomのIPを自動設定

# はじめに
今回はCiscoSD-WANのvManageを使ってネットワーク機器のトラフィックコントロールのためにIPアドレスのリストをWEBサイトから取得してAPI経由で入力を自動化するスクリプトを書いてみました。

# 説明
### このスクリプトの説明
Zoomのアドレス一式をScrapingしてプレフィックス（10.0.0.0/24のような記載）をピックアップして一式を設定することにする。取得したプレフィックス一式を既存のData-Plefix-listにアップデートするが、動作中のトラフィックポリシーで使用されている場合は、ポリシーを一時無効としてから中身をアップデートしないといけない。一時ポリシーを無効、アップデート、再度有効化する方式をとっている。
Zoomのアドレス一式は以下から取得した。

```html
https://support.zoom.us/hc/en-us/articles/201362683-Network-firewall-or-proxy-server-settings-for-Zoom
```
**動作中のポリシー一式をコピーしながらスライドさせる本番環境用の通信に影響のないバージョンもあるが、今回はテスト目的のためにこちらを公開**

### 各種変数
コード内の各種UUIDは以下のURLから取得できます。普通にログインしてからTABをコピーしてアクセスするとよい。

```html
https://<vmanage-ip>/dataservice/template/policy/list/dataprefix
https://<vmanage-ip>/dataservice/template/policy/list/dataipv6prefix #IPv6にも対応できる
https://<vmanage-ip>/dataservice/template/policy/vsmart
```

より詳細なAPIのURIの説明はAPIDOCS（Swagger)を参照すると確認できます。

```html
https://<vmanage-ip>/apidocs/
```

上記Swagger画面上でのTokenを取得するには以下のURLにアクセス

```html
https://<vmanage-ip>/dataservice/client/token
```

# コードサンプル
```python
#!/usr/bin/python
import re
import sys
import time
import requests
import json
import ipaddress
import uuid
import urllib3
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)

# Enter your vManage credentials, data-prefix-list-uuid, vSmart-polocy-uuid
vManage_IP = "<vmanage-ip>"
vManage_ID = "admin"
vManage_Password = "admin"
Data_prefix_list_uuid = "54f0055c-89c1-4d66-a1cd-a85beb23f86e"　#UUIDは事前に確認
Vsmart_policy_uuid = "d00c8a63-7412-488c-8214-a5805aa305b7" #UUIDは事前に確認

def get_addresslist(url):
    response = requests.get(url)
    return response.content

def export_file(content, list_file_name):
    with open(list_file_name, "wb") as list_file:
        list_file.write(content)

def create_scrape_destination_list(sourcefile):
    result = []
    with open(sourcefile, "r") as f:
        fin = f.read()
        found = re.findall(r'(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})\/(?:[\d]{1,2})',fin)
        if found not in result:
            result = json.dumps(found)
    return json.loads(result)

def login(vmanage_ip, username, password):
    session = {}
    base_url_str = 'https://%s:443/'%vmanage_ip
    login_action = '/j_security_check'
    login_data = {'j_username' : username, 'j_password' : password}
    login_url = base_url_str + login_action
#    url = base_url_str + loginyy_url
    url = base_url_str
    sess = requests.session()
    #URL for retrieving client token
    token_url = base_url_str + 'dataservice/client/token'
    # If the vmanage has a certificate signed by a trusted authority change verify to True
    login_response = sess.post(url=login_url, data=login_data, verify=False)
    login_token  = sess.get(url=token_url, verify=False)
    try:
        if login_response.status_code == 200 and login_token.status_code == 200 :
            sess.headers['X-XSRF-TOKEN'] = login_token.content
            session[vmanage_ip] = sess
            print(sess.cookies)
            return session[vmanage_ip]
        elif '<html>' in login_response.content:
            print ("Login Failed")
            sys.exit(0)
        else:
            print("Unknown exception")
    except Exception as err:
        return

def put_prefix_list_builder(list_IPv4):
    lst = [] # List of IP Prefixes
    # Change list_IPv4 to dictionary per line
    for pn in list_IPv4:
        d = {}
        d['ipPrefix'] = pn
        lst.append(d)
    json.dumps(list_IPv4)
    payload = {
                  "name": "zoom-ipv4-prefix-list",
                  "type": "dataPrefix",
                  "entries": lst
                }
    test = json.dumps(payload)
    headers = {'Content-Type': 'application/json'}
    sessions = login(vManage_IP, vManage_ID, vManage_Password)
    url = 'https://'+vManage_IP+':443/dataservice/template/policy/list/dataprefix/'+Data_prefix_list_uuid
    print('vManage API URL :'+url)
    r = sessions.put(url, data=test, headers=headers, verify=False)
    if r.status_code != 200:
        print(r.raise_for_status)
    else:
        return

def deactivate_vsmart_policy():
    vsmartd_payload = {
      "isPolicyActivated": "false",
      "policyId": Vsmart_policy_uuid
      }
    test2 = json.dumps(vsmartd_payload)
    headers = {'Content-Type': 'application/json'}
    sessions = login(vManage_IP, vManage_ID, vManage_Password)
    url2 = 'https://'+vManage_IP+':443/dataservice/template/policy/vsmart/deactivate/'+Vsmart_policy_uuid+'?confirm=true'
    print('vManage API URL :'+url2)
    r = sessions.post(url2, data=test2, headers=headers, verify=False)
    if r.status_code != 200:
        print(r.raise_for_status)
        exit()
    else:
        return

def activate_vsmart_policy():
    vsmartd_payload = {
      "isPolicyActivated": "true",
      "policyId": Vsmart_policy_uuid
      }
    test3 = json.dumps(vsmartd_payload)
    headers = {'Content-Type': 'application/json'}
    sessions = login(vManage_IP, vManage_ID, vManage_Password)
    url3 = 'https://'+vManage_IP+':443/dataservice/template/policy/vsmart/activate/'+Vsmart_policy_uuid
    print('vManage API URL :'+url3)
    r = sessions.post(url3, data=test3, headers=headers, verify=False)
    if r.status_code != 200:
        print(r.raise_for_status)
    else:
        return

if __name__ == "__main__":
    MASTER_FILE_NAME = "zoom-address-range.txt"
    URL = 'https://support.zoom.us/hc/en-us/articles/201362683-Network-firewall-or-proxy-server-settings-for-Zoom'
    print('zoom URL :'+URL)
    address_list_file = get_addresslist(URL)
    export = export_file(address_list_file, MASTER_FILE_NAME)
    list_IPv4 = create_scrape_destination_list(MASTER_FILE_NAME)
    deactivate_vsmart_policy()
    print("sleep 15sec until deactivate complate")
    time.sleep(15)
    put_prefix_list_builder(list_IPv4)
    activate_vsmart_policy()
    print("Process Completed!!")
```

#  実行結果

```bash
bash-3.2$ python create_scrape_destination_list.py 
zoom URL :https://support.zoom.us/hc/en-us/articles/201362683-Network-firewall-or-proxy-server-settings-for-Zoom
<RequestsCookieJar[<Cookie JSESSIONID=GT8wOkW8XzFWD2m-QQY_GdkLXk0Nra2-FVBmk7Ot.b6066f88-0ded-44c2-ab84-410a189f9352 for 10.71.131.72/>]>
vManage API URL :https://10.71.131.72:443/dataservice/template/policy/vsmart/deactivate/d00c8a63-7412-488c-8214-a5805aa305b7?confirm=true
sleep 15sec until deactivate complate
<RequestsCookieJar[<Cookie JSESSIONID=Z-M4wX6irej5VI80t73p-DDQ6O2Ddnwv4VkcatFM.b6066f88-0ded-44c2-ab84-410a189f9352 for 10.71.131.72/>]>
vManage API URL :https://10.71.131.72:443/dataservice/template/policy/list/dataprefix/54f0055c-89c1-4d66-a1cd-a85beb23f86e
<RequestsCookieJar[<Cookie JSESSIONID=Jld0rBUgZ4c5ZLWL3SYO4-zWq4HCL3o25xQcEYxE.b6066f88-0ded-44c2-ab84-410a189f9352 for 10.71.131.72/>]>
vManage API URL :https://10.71.131.72:443/dataservice/template/policy/vsmart/activate/d00c8a63-7412-488c-8214-a5805aa305b7
Process Completed!!
```

# 実行結果画像
ZoomのIPv4アドレスプレフィックスを一式投入することができました。
![スクリーンショット 2020-12-20 17.09.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259135/5e550078-0353-550b-68ee-b10392446c4f.png)

# おわりに
office365やWebEXといったCiscoネイティブなアプリは設定が自動化されていますが、その他の有名どころのAppに対してはこのようなスクリプトを有効活用すると良いかと思います。

Ciscoアドベントカレンダーでも別の投稿をしていますのでご興味があれば
https://qiita.com/advent-calendar/2020/cisco
https://qiita.com/advent-calendar/2020/cisco2


# 参考文献
- [バックアップリストアのおすすめコード](https://github.com/CiscoDevNet/sastre)