AWSとAzureのサイト間VPNをスクリプトで有効化・無効化する

# はじめに
AWSとAzureのサイト間VPNを開発で使用しています。
VMは必要なときだけ起動するようにしていますが、VPNについては常に有効な状態にしておくという使い方をしていました。

開発が落ち着きVMを停止している時間が多くVPNの課金が目立つようになり、AWSで約3,700円、Azureで約3,000円の合計約6,700円が毎月発生していますので、微々たるものですが使わないときは止めておこうと思い立ち、スクリプトでの制御を行うようにしてみました。

オフィシャルで用意されているAPI(SDK)を叩いているだけなので、特に難しいことをやっている訳ではありませんが、一例としてご紹介します。

# 前提

* AWSとAzureのサイト間VPNを使用している。
* AWSおよびAzureのAPI(SDK)を実行できる状態になっている。
* 各クラウドのVPNやネットワークの設定、SDKを使用してのAPIの実行方法は本記事では省略致します。
* 本記事の内容は、動作を保障するものではありません。同様の方法で制御を行う場合は、環境のバックアップや動作の確認を必ず実施してください。
* 本記事では、Azureのリソースやオブジェクトをサービスと表現しています。

# 制御するサービス

以下はAWSとAzure間のサイト間VPN例です。
![Untitled Diagram-Page-1 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518444/40270156-2e77-5385-ee99-0fea547d794d.png)

課金が発生するサービスはAWS、Azureともにリモートゲートウェイの部分になります。
※ ①VPNGatewayと④VirtualNetworkGateway の部分
これらのサービスさえ停止すれば課金が発生しなくなりますが、中途半端にローカルゲートウェイが有効になった状態になっていると、設定しているIPアドレス宛にコネクションを張ってしまう可能性があるかと思いますので、VPNに関する他のサービス(ローカルゲートウェイなど)も停止します。

※ リモートゲートウェイを停止するとパブリックIPアドレスも開放され、そのIPアドレスを他のユーザーが使用する可能性があるため接続先としての設定したままにするのは宜しくない。
※ 各クラウドで不要な通信は発生しないような設計になっていることも考えられますが、無効なIPアドレスなので止めておきましょう。

# 制御方法

当該のサービスを一時的に停止・再開することができればよいのですが、APIのドキュメントなどを確認してみても、そのようなステータスが見つかりませんでした。
(もし一時停止てきたとしても、リソースが確保されている状態となり課金は継続しそうな予感がします)
ですので、タイトルでは有効化・無効化としていますが、実際にはサービスを削除・再作成する形で実現しています。

# 有効化の手順(実行するAPI)
![Untitled Diagram-Page-2 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518444/e0eab383-d851-ce8c-e350-ae2165ec9f98.png)

<dt>1. Azure VirtualNetworkGateways(create)</dt>
<dd>Azureの仮想ネットワークゲートウェイを起動します。
※ このAPIの実行は非常に時間がかるため運用には一工夫が要ります。(後術します)</dd>
<dt>2. Azure PublicIPAddresses(get)</dt>
<dd>「1. Azure VirtualNetworkGateways」で起動した、Azure側のVPNゲートウェイのパブリックIPアドレスを取得します。</dd>
<dt>3. AWS CreateCustomerGateway</dt>
<dd>AWSのカスタマゲートウェイを作成します。
作成する際に、「2. Azure PublicIPAddresses」で取得したAzure側のパプリックIPを設定します。</dd>
<dt>4. AWS CreateVpnConnection</dt>
<dd>AWSのVPNコネクションを作成します。
作成した際のレスポンスに、VPNの設定情報がXML形式で含まれているので、
XMLをパースして事前共有キーとAWS側のパブリックIPアドレスを取得します。</dd>
<dt>5. AWS CreateVpnConnectionRoute</dt>
<dd>AWSのVPNコネクションにルート情報を作成します。</dd>
<dt>6. AWS CreateTags</dt>
<dd>「4. AWS CreateVpnConnection」と「5. AWS CreateVpnConnectionRoute」で作成したサービスに任意のダグを付けます。</dd>
<dt>7. Azure LocalNetworkGateway(create)</dt>
<dd>Azureのローカルネットワークゲートウェイを作成します。
作成する際に「4. AWS CreateVpnConnection」で取得したAWS側のパブリックIPアドレスを設定します。</dd>
<dt>8. Azure VirtualNetworkGatewayConnection(create)</dt>
<dd>Azureの仮想ネットワークゲートウェイ接続を作成します。
作成する際に「4. AWS CreateVpnConnection」で取得したAWSのVPNゲートウェイで生成した事前共有キーを設定します。</dd>
以上で有効化が完了です！

# 無効化の手順(実行するAPI)
![Untitled Diagram-Page-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518444/17a5062e-f758-3df0-361c-b6cc81c9cce9.png)

<dt>1. AWS DeleteVpnConnection</dt>
<dd>有効化で付与したタグを元にAWSのVPNConnectionを削除します。</dd>
<dt>2. AWS DeleteCustomerGateway</dt>
<dd>有効化で付与したタグを元にAWSのカスタマーゲートウェイを削除します。</dd>
<dt>3. Azure VirtualNetworkGatewayConnections(delete)</dt>
<dd>Azureの仮想ネットワークゲートウェイ接続を削除します。</dd>
<dt>4. Azure VirtualNetworkGateways(delete)</dt>
<dd>Azureの仮想ネットワークゲートウェイを削除します。</dd>
<dt>5. Azure LocalNetworkGateways(delete)</dt>
<dd>Azureのローカルネットワークゲートウェイを削除する。</dd>
以上で無効化が完了です！

# AzureのVirtualNetworkGatewayの起動に時間がかかる問題

上記のスクリプトで一部問題があります。
Azureの仮想ネットワークゲートウェイの作成が完了するまで、非常に時間がかかる場合があるのです。
調子が良いときは30分ほど、時間がかかるときは1時間ほど待たされます。
急用で使いたいときに1時間以上待たされてしまうのは困るので、営業日の業務時間前に、
仮想ネットワークゲートウェイのみ作成しておき必要に応じてその他を作成する、という運用もよいかと思います。
他のAPIは数分で完了するため、VMの起動などを待っている間にVPNを有効にすることができます。

# 参考スクリプト(Python3での例)

```python3:main.py
import sys
import boto3
import logging
from azure.mgmt.resource import SubscriptionClient
from azure.mgmt.network import NetworkManagementClient
from azure.common.client_factory import get_client_from_auth_file
from xml.etree import ElementTree

logging.basicConfig(level=logging.INFO, format="%(asctime)s :%(message)s")
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# aws
aws_setting = {
    # ec2Tag is Favorite tag name eg) VPN_AZURE
    'ec2Tag': '<VPN_AZURE>',
    'accessKey': '<ACCESS_KEY>',
    'secretAccessKey': '<SECRET_ACCESS_KEY>',
    'DestinationCidrBlock': '<DESTINATION_CIDR_BLOCK>',
    'VpnGatewayId': '<VPN_GATEWAY_ID>'
}

# azure
azure_setting = {
    "subscriptionId": '<SUBSCRIPTION_ID>',
    "resourceGroupName": '<RESOURCE_GROUP_NAME>',
    "virtualNetworkGatewayConnectionName": '<VIRTUAL_NETWORK_GATEWAY_CONNECTION_NAME>',
    "virtualNetworkGatewayName": '<VIRTUAL_NETWORK_GATEWAY_NAME>',
    "localNetworkGatewayName": '<LOCAL_NETWORK_GATEWAY_NAME>',
    "virtualNetworkGatewayPublicIpName": '<VIRTUAL_NETWORK_GATEWAY_PUBLIC_IP_NAME>',
    # eg) ['10.0.0.0/16']
    "addressPrefixes": ['<ADDRESS_PREFIXES>'],
    # eg) /subscriptions/******/resourceGroups/hogehoge/providers/Microsoft.Network/virtualNetworkGateways/*******
    "virtualNetworkGateway1Id": '<VIRTUAL_NETWORK_GATEWAY1_ID>',
    "localNetworkGateway2Id": '<LOCAL_NETWORK_GATEWAY2_ID>',
    "subnetId": "<SUBNET_ID>",
    "publicIPAddressId": "<PUBLIC_IP_ADDRESS_ID>",
    # eg) Japan East
    "location": '<LOCATION>',
}

def usage_and_exit():
    print("Usage: python3 main.py [enable|disable]")
    exit()

def create_network_clinet():
    return get_client_from_auth_file(NetworkManagementClient, auth_path="../credential.json")

def create_ec2():
    return boto3.client('ec2',
        aws_access_key_id= aws_setting.get('accessKey'),
        aws_secret_access_key= aws_setting.get('secretAccessKey'),
        region_name='ap-northeast-1')

def enable():
    network_client = create_network_clinet()
    ec2 = create_ec2()

    logger.info("Azure Create VPNGateway")
    poller = network_client.virtual_network_gateways.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayName"),
        {
            "sku": {
                "name": "Basic",
                "tier": "Basic"
            },
            "gatewayType": "Vpn",
            "vpnType": "RouteBased",
            "ipConfigurations": [
                {
                    "name": azure_setting.get("virtualNetworkGatewayName"),
                    "privateIPAllocationMethod": "Dynamic",
                    "subnet": {
                        "id": azure_setting.get("subnetId"),
                    },
                    "publicIPAddress": {
                        "id": azure_setting.get("publicIPAddressId"),
                    }
                }
            ],
            "location": azure_setting.get("location"),
        })
    azure_vpn_gateway = poller.result()
    logger.info("Azure Create VPNGateway ... complete!")

    logger.info("Azure Get PublicIP")
    publicIpAddresses = network_client.public_ip_addresses.get(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayPublicIpName")
    )
    logger.info("Azure Get PublicIP ... complete!")

    azure_ip = publicIpAddresses.ip_address
    logger.info("Get Azure IP: {0}".format(azure_ip))

    logger.info("AWS Create VPNGateway")
    customer_gateway = ec2.create_customer_gateway(
            PublicIp= azure_ip,
            Type= "ipsec.1",
            BgpAsn= 65000)
    logger.info("AWS Create CustomerGateway ... complete!")

    vpn_gateway_id = customer_gateway.get('CustomerGateway').get('CustomerGatewayId')
    logger.info("AWS Get CustomerGatewayId : {0}".format(vpn_gateway_id))

    logger.info("AWS Create VPNConnection")
    vpn_connection = ec2.create_vpn_connection(
            CustomerGatewayId= vpn_gateway_id,
            VpnGatewayId= aws_setting.get('VpnGatewayId'),
            Type= "ipsec.1",
            Options={
                'StaticRoutesOnly': True
            })
    logger.info("AWS Create VPNConnection ... complete!")

    vpn_connection_id = vpn_connection.get('VpnConnection').get('VpnConnectionId')
    logger.info("AWS Get VpnConnectionId : {0}".format(vpn_connection_id))

    logger.info("AWS Create VPNConnection")
    vpn_connection_route = ec2.create_vpn_connection_route(
        DestinationCidrBlock= aws_setting.get('DestinationCidrBlock'),
        VpnConnectionId= vpn_connection_id
    )
    logger.info("AWS Create VPNConnection ... complete!")

    xml = vpn_connection.get('VpnConnection').get('CustomerGatewayConfiguration')
    elem = ElementTree.fromstring(xml)

    ip = elem.find('ipsec_tunnel').find('vpn_gateway').find('tunnel_outside_address').find('ip_address').text
    key = elem.find('ipsec_tunnel').find('ike').find('pre_shared_key').text
    # logger.info("AWS PublicIP: {0}\nAWS SharedKey: {1}".format(ip, key))

    logger.info("AWS Create Tags")
    create_tags = ec2.create_tags(
        Resources= [
            vpn_gateway_id,
            vpn_connection_id
        ],
        Tags= [{
            'Key': 'Name',
            'Value': aws_setting.get('ec2Tag')
        }]
    )
    logger.info("AWS Create Tags ... complete!")

    logger.info("Azure Create LocalNetworkGateway")
    poller = network_client.local_network_gateways.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("localNetworkGatewayName"),
        {
                "gatewayIpAddress": ip,
                "localNetworkAddressSpace": {
                    "addressPrefixes": azure_setting.get("addressPrefixes")
                },
                "location": azure_setting.get("location")
        }
    )
    result = poller.result()
    logger.info("Azure Create LocalNetworkGateway ... complete!")

    logger.info("Azure Create virtualNetworkGatewayConnection")
    poller = network_client.virtual_network_gateway_connections.create_or_update(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayConnectionName"),
        {
            "sharedKey": key,
            "virtualNetworkGateway1": {
                "id": azure_setting.get("virtualNetworkGateway1Id")
            },
            "localNetworkGateway2": {
                "id": azure_setting.get("localNetworkGateway2Id"),
            },
            "connectionType2": "IPsec",
            "connectionProtocol": "IKEv2",
            "connectionMode": "Default",
            "location": azure_setting.get("location"),
            "connectionType": "IPsec"
        }
    )
    result = poller.result()
    logger.info("Azure Create virtualNetworkGatewayConnection ... complete!")

    logger.info("all completed !")
    
def disable():
    network_client = create_network_clinet()
    ec2 = create_ec2()

    logger.info("AWS Get DescribeVpnConnection")
    vpn_connections = ec2.describe_vpn_connections(
        Filters= [{
            'Name': 'tag:Name',
            'Values': [aws_setting.get('ec2Tag')]
        }]
    )
    logger.info("AWS Get DescribeVpnConnection ... complete!")

    logger.info("AWS Delete VPNConnection")
    if len(vpn_connections.get('VpnConnections')) >= 1:
        ec2.delete_vpn_connection(VpnConnectionId= vpn_connections.get('VpnConnections')[0].get('VpnConnectionId'))
        logger.info("AWS Delete VPNConnection ... complete!")
    else:
        logger.info("AWS Skip Delete VPNConnection")

    logger.info("AWS Get DescribeVpnConnection")
    customer_gateways = ec2.describe_customer_gateways(
        Filters= [{
            'Name': 'tag:Name',
            'Values': [aws_setting.get('ec2Tag')]
        }]
    )
    logger.info("AWS Get DescribeVpnConnection ... complete!")

    logger.info("AWS Delete CustomerGateways")
    if len(customer_gateways.get('CustomerGateways')) >= 1:
        ec2.delete_customer_gateway(CustomerGatewayId= customer_gateways.get('CustomerGateways')[0].get('CustomerGatewayId'))
        logger.info("AWS Delete CustomerGateways ... complete!")
    else:
        logger.info("AWS Skip Delete CustomerGateways")

    logger.info("Azure Delete VirtualNetworkGatewayConnection")
    poller = network_client.virtual_network_gateway_connections.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayConnectionName"),
    )
    ret = poller.result()
    logger.info("Azure Delete VirtualNetworkGatewayConnection ... complete!")

    logger.info("Azure Delete VirtualNetworkGateway")
    poller = network_client.virtual_network_gateways.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("virtualNetworkGatewayName"),
    )
    poller.result()
    logger.info("Azure Delete VirtualNetworkGateway ... complete!")

    logger.info("Azure Delete LocalNetworkGateway")
    poller = network_client.local_network_gateways.delete(
        azure_setting.get("resourceGroupName"),
        azure_setting.get("localNetworkGatewayName"),
    )
    poller.result()
    logger.info("Azure Delete LocalNetworkGateway ... complete!")
    logger.info("all completed !")

args = sys.argv
if len(args) < 2:
    usage_and_exit()

if args[1] == 'enable':
    enable()
elif args[1] == 'disable':
    disable()
else:
    usage_and_exit()
```

# おまけ

上記のサンプルスクリプトをGitHubで公開しています。
https://github.com/Piecemeal-Technology-Inc/aws-azure-vpn-activation

※サンプルスクリプトは動作を保障するものではありません。実行する前にVPN環境のバックアップや実行後のクラウドの状況確認を必ず実施してください。

## 使い方
AWSやAzureの認証情報などを設定して頂き、以下のコマンドで有効化、無効化を実行できます。

```bash:有効化
python3 ./main.py enable
```
```bash:無効化
python3 ./main.py disable
```