Office365通信のローカルブレイクアウトを自動化させる - 番外編 –

ビジネス推進本部 第3応用技術部第1チーム
由原 亮太、川畑 勇貴

 

Office365通信のローカルブレイクアウトを自動化させる - 前編・後編 -では、Microsoft Office365の宛先一覧を基にローカルブレイクアウトを実現する仕組みを紹介しました。

しかし、前編で利用したXMLベースの宛先一覧は2018年10月2日で更新が終了してしまい、前編で作成したプログラムでは最新の情報を取得することができなくなりました。

今回は番外編として新たにRESTサービスとして提供されるようになった宛先一覧を基にCSVファイルを作っていきます。

連載インデックス

O365宛て通信に対する経路制御

ローカルブレイクアウトは、Microsoftが公開している宛先一覧を基にO365通信を識別して経路を曲げ、データセンタを経由させずに支社から直接インターネットアクセスを行う事で従来よりも効率的なO365利用を実現します。
宛先一覧の中にはO365と通信する際に使用されるIPv4、IPv6、FQDNの一覧が羅列されており、こちらの情報を元にO365通信を制御するための設定をルータ等のインターネット接続機器に入れ込む必要があります。

O365に対する宛先一覧の提供方式の変更

従来、Microsoftより提供されていたOffice365の宛先一覧はXML形式でありましたが、2018年10月2日より、一覧の更新が終了しました。
それに代わって現在はRESTサービスとして宛先一覧が公開されています。

RESTサービス化した宛先一覧は下記のようにJSON形式またはCSV形式で情報を取得することができます。

宛先一覧(JSON形式)の構造

前編と同様に、提供されているデータ構造のまま活用することも可能ですが、アドレス形式ごとに並べ替えた方が自動化ツールなどに読み込ませる際に便利です。
そのため、今回もアドレス一覧を利用しやすい形にするためにPythonを用いてIPv4、IPv6、FQDNの3つに分割し、整形してみることにします。

活用しやすい形へ変換

今回は3つの手順で宛先一覧の情報を変換しています。
[手順]
1.O365宛先一覧の取得
2.一覧から必要箇所を抽出し配列にする
3.抽出した配列をCSVファイルとして保存する

 

今回作成したPythonソースコードの全体
import requests
import json
import ipaddress
import uuid
import csv


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 load_json_file(data_source):
    with open(data_source, "r") as target_file:
        address_list = target_file.read()
    return json.loads(address_list)


def create_o365_destination_list(sourcefile, content_type):
    tmp_list = []
    result = []
    if content_type == "URL":
        for x in range(len(sourcefile)):
            if "urls" in sourcefile[x]:
                tmp_list.extend(sourcefile[x]["urls"])

        for x in range(len(tmp_list)):
            result.append([tmp_list[x]])

    elif content_type == "IPv4":
        for x in range(len(sourcefile)):
            if "ips" in sourcefile[x]:
                tmp_list = tmp_list + sourcefile[x]["ips"]
        for x in range(len(tmp_list)):
            try:
                ipaddress.IPv4Network(tmp_list[x])
                ip4 = ipaddress.ip_network(tmp_list[x])
                result.append([ip4.network_address, ip4.netmask, ip4.hostmask])
            except:
                pass

    elif content_type == "IPv6":
        for x in range(len(sourcefile)):
            if "ips" in sourcefile[x]:
                tmp_list = tmp_list + sourcefile[x]["ips"]
        for x in range(len(tmp_list)):
            try:
                ipaddress.IPv4Network(tmp_list[x])
            except ipaddress.AddressValueError:
                result.append([tmp_list[x]])
    return result



def createcsv(filename, column_name, address_list):
    with open(filename, 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerows([column_name])
        writer.writerows(address_list)


if __name__ == "__main__":

    MASTER_FILE_NAME = "o365_master_file.txt"
    URL_LIST = "o365UrlList.csv"
    IPv4_LIST = "o365IPv4List.csv"
    IPv6_LIST = "o365IPv6List.csv"

    DATA_FORMAT = "json"
    CLIENTREQUESTID = str(uuid.uuid4())
    URL = 'https://endpoints.office.com/endpoints/worldwide?clientrequestid='+CLIENTREQUESTID+'&format='+DATA_FORMAT
    print(URL)
    address_list_file = get_addresslist(URL)
    export_file(address_list_file, MASTER_FILE_NAME)
    address_list_json = load_json_file(MASTER_FILE_NAME)

    list_IPv4 = create_o365_destination_list(address_list_json, 'IPv4')
    list_IPv6 = create_o365_destination_list(address_list_json, 'IPv6')
    list_URL = create_o365_destination_list(address_list_json, 'URL')

    createcsv(IPv4_LIST, ["IPv4", "SubnetMask", "wildcardMask"], list_IPv4)
    createcsv(IPv6_LIST, ["IPv6"], list_IPv6)
    createcsv(URL_LIST, ["URL"], list_URL)

[手順1]
O365の宛先一覧はMicrosoftのページ<https://endpoints.office.com/endpoints/worldwide>にRESTサービスとして公開されてます。
実際に取得する際には下記のようにClientRequestIDと取得する際のフォーマットを指定する必要があります。
<https://endpoints.office.com/endpoints/worldwide?clientrequestid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&format=json>

今回は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 load_json_file(data_source):
    with open(data_source, "r") as target_file:
        address_list = target_file.read()
    return json.loads(address_list)

if __name__ == "__main__":

    MASTER_FILE_NAME = "o365_master_file.txt"
    DATA_FORMAT = "json"
    CLIENTREQUESTID = str(uuid.uuid4())
    URL = 'https://endpoints.office.com/endpoints/worldwide?clientrequestid='+CLIENTREQUESTID+'&format='+DATA_FORMAT

    address_list_file = get_addresslist(URL)
    export_file(address_list_file, MASTER_FILE_NAME)
    address_list_json = load_json_file(MASTER_FILE_NAME)

[手順2]
読み込んだ宛先一覧を基にfor文を使用し、宛先一覧の中から<IPv4>、<IPv6>、<FQDN>のみをそれぞれ抽出します。コラム後編の、Ciscoルータのコンフィグ自動化のため<IPv4>はIPアドレス、サブネット、ワイルドカードの3要素に置換しIOS-XEのACLに対応させています。抽出された結果は配列に格納されます。

def create_o365_destination_list(sourcefile, content_type):
    tmp_list = []
    result = []
    if content_type == "URL":
        for x in range(len(sourcefile)):
            if "urls" in sourcefile[x]:
                tmp_list.extend(sourcefile[x]["urls"])

        for x in range(len(tmp_list)):
            result.append([tmp_list[x]])

    elif content_type == "IPv4":
        for x in range(len(sourcefile)):
            if "ips" in sourcefile[x]:
                tmp_list = tmp_list + sourcefile[x]["ips"]
        for x in range(len(tmp_list)):
            try:
                ipaddress.IPv4Network(tmp_list[x])
                ip4 = ipaddress.ip_network(tmp_list[x])
                result.append([ip4.network_address, ip4.netmask, ip4.hostmask])
            except:
                pass

    elif content_type == "IPv6":
        for x in range(len(sourcefile)):
            if "ips" in sourcefile[x]:
                tmp_list = tmp_list + sourcefile[x]["ips"]
        for x in range(len(tmp_list)):
            try:
                ipaddress.IPv4Network(tmp_list[x])
            except ipaddress.AddressValueError:
                result.append([tmp_list[x]])
    return result

[手順3]
配列にした抽出結果をCSVファイルとして保存します。

def createcsv(filename, column_name, address_list):
    with open(filename, 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerows([column_name])
        writer.writerows(address_list)
if __name__ == "__main__":

    MASTER_FILE_NAME = "o365_master_file.txt"
    URL_LIST = "o365UrlList.csv"
    IPv4_LIST = "o365IPv4List.csv"
    IPv6_LIST = "o365IPv6List.csv"

    DATA_FORMAT = "json"
    CLIENTREQUESTID = str(uuid.uuid4())
    URL = 'https://endpoints.office.com/endpoints/worldwide?clientrequestid='+CLIENTREQUESTID+'&format='+DATA_FORMAT
    print(URL)

    address_list_file = get_addresslist(URL)
    export_file(address_list_file, MASTER_FILE_NAME)
    address_list_json = load_json_file(MASTER_FILE_NAME)

    list_IPv4 = create_o365_destination_list(address_list_json, 'IPv4')
    list_IPv6 = create_o365_destination_list(address_list_json, 'IPv6')
    list_URL = create_o365_destination_list(address_list_json, 'URL')

    createcsv(IPv4_LIST, ["IPv4", "SubnetMask", "wildcardMask"], list_IPv4)
    createcsv(IPv6_LIST, ["IPv6"], list_IPv6)
    createcsv(URL_LIST, ["URL"], list_URL)

宛先一覧の配布方法が変わってもプログラムを少し書き換えるだけで手軽にCSVファイル化することができます。

今回のまとめ

前編と比較して、宛先一覧の配布方式がXMLからJSON形式で配布されるようになったのが、今回の大きな変更点です。
新しくなった提供形式ではJSONファイルとして情報を取得することができるため、XMLファイルをDict型に変換する作業が不要です。

今後、ローカルブレイクアウトを検討される時には、プログラムを利用し自社環境により最適化したローカルブレイクアウトの実現を検討してみてはいかかでしょうか。詳細をお聞きしたい方は弊社担当営業までご連絡下さい。

執筆者プロフィール

由原 亮太
ネットワンシステムズ株式会社 ビジネス推進本部
第3応用技術部 第1チーム所属
新卒入社で応用技術部に配属されてから今日までCisco社ルータ製品(ISR/ASR1000シリーズ)の担当として、評価・検証および様々な案件サポートに従事。
ネットワークの可能性を広げるためにプログラミングスキルを活かして奮闘中。
・CCNP
・応用情報技術者

川畑 勇貴
ネットワンシステムズ株式会社 ビジネス推進本部
第3応用技術部 第1チーム所属
ネットワンシステムズに入社し、Ciscoミドルエンド・ローエンドルータ製品を担当。
ネットワークに関わる先進テクノロジーの調査、研究に従事している。
執筆者プロフィールに「・CCIE」と載せるべく日々研鑽中。
・CCNP
・TOEIC L&R 915

イベント/レポート

pagetop