やったこと
AWS Client VPN を AWS IoT1-Click のクリック・ダブルクリックで起動・停止(=エンドポイントのサブネットへの関連付け・関連付け解除)するようにしました。
AWS Client VPN の料金は、下の引用の通り、Client VPN(エンドポイント)にサブネットを関連付けることで発生します。
関連付けを行ったままだと、夜間など利用しない時間帯にも料金が発生してしまうため、利用する時だけサブネットの関連付けを行うようにしました。
AWS Client VPN では、アクティブなクライアント接続の数に対して、
および Client VPN に関連付けられているサブネットの数に対してそれぞれ 1 時間単位の料金が発生します。
前提
-
IoTデバイスとして、AWS IoT エンタープライズボタンが必要です。今回はLTE-MのeSIM内蔵の SORACOM LTE-M Button powered by AWSを使用しました。
-
サブネットの関連付けを解除すると VPN 接続ができなくなり、料金も発生しないため、「サブネットの関連付け = AWS Client VPN の起動」、「サブネットの関連付け解除 = AWS Client VPN の停止」と定義しています。
-
AWS Client VPN の設定手順は、こちらの記事(AWS Client VPN 設定メモ)を参考にして行ったため割愛します。
(AWS CLI による設定手順がまとめられています。非常に参考になりました、ありがとうございます)
環境
-
IoT デバイス
-
SORACOM LTE-M Button powered by AWS
-
クライアントPC
-
Windows 10 バージョン 1803 (OSビルド 17134.829)
-
PowerShell バージョン 5.1.17134.765
-
AWS CLI バージョン 1.16.148
-
Python バージョン 2.7.15
-
pip バージョン 9.0.1
-
OpenVPN バージョン 2.4.7
-
AWS サービス
-
AWS Client VPN
-
AWS IAM
-
AWS Lambda
-
AWS CloudWatch Logs
-
AWS IoT1-Click
設定手順
手順に出てくる変数には、以下の例のように、予め値を設定してください。
# 1. IAM の Lambda 実行ロール作成用
$Region="ap-northeast-1"
$RoleName="client_vpn_admin_role"
# 2. Client VPN を操作する Lambda 関数作成用
$FunctionName="manage_client_vpn"
$handlerName="manage_client_vpn_function.lambda_handler"
1. IAM の Lambda 実行ロールを作成する
Lambda 関数は、IAM で作成されたロールを必要とし、ロールは関数を実行するのに必要なアクセス権限を提供します。
ここでは、ロールを作成し、Lambda 関数が「Cloudwatch Logsへのログ出力」と「Client VPN エンドポイントに対するサブネット関連付け/関連付け解除」を実行するのに必要なアクセス権限(ポリシー)をロールへアタッチします。
(予め、AWS CLI を実行するカレントディレクトリへ下記の role-policy.json を作成してください。)
# ロールの作成
aws iam create-role --role-name $RoleName --assume-role-policy-document file://role-policy.json
# ロールの確認
aws iam get-role --role-name $RoleName
# ポリシーのアタッチ
aws iam attach-role-policy --role-name $RoleName --policy-arn "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
aws iam attach-role-policy --role-name $RoleName --policy-arn "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
# アタッチしたポリシーの確認
aws iam list-attached-role-policies --role-name $RoleName
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
2. Lambda 関数の作成
2.1 デプロイパッケージの作成
任意のディレクトリで、Lamda へデプロイするパッケージを作成します。
(参考:Lambda上でAWSCLIを動かしてS3 Syncする、aws-cliでLambdaのScheduled Eventを作成する)
mkdir lambda-cli
cd lambda-cli
pip install awscli -t .
作成した lambda-cli ディレクトリに以下の Python スクリプト( aws、manage_client_vpn_function.py )を配置します。
import sys
import awscli.clidriver
def main():
return awscli.clidriver.main()
if __name__ == '__main__':
result = main()
sys.exit(result)
import subprocess
def lambda_handler(event, context):
client_vpn_endpoint_id = "[あなたの Client VPN エンドポイント ID]"
subnet_id_list = ["あなたの 関連付けるサブネット ID(カンマ区切りで複数記述可)"]
click_type = event['deviceEvent']['buttonClicked']['clickType']
if (click_type == "SINGLE"):
message = "The button is clicked once."
start_client_vpn(client_vpn_endpoint_id, subnet_id_list)
elif (click_type == "DOUBLE"):
message = "The button is double clicked."
stop_client_vpn(client_vpn_endpoint_id)
elif (click_type == "LONG"):
message = "The button is long pressed."
else:
message = "click_type is unknown."
print(message)
def start_client_vpn(client_vpn_endpoint_id, subnet_id_list):
for subnet_id in subnet_id_list :
cmd = "python aws ec2 associate-client-vpn-target-network --client-vpn-endpoint-id " + client_vpn_endpoint_id + " --subnet-id " + subnet_id
result = subprocess.run(
cmd.split(" "),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
print(result.stdout.decode())
def stop_client_vpn(client_vpn_endpoint_id):
cmd = "python aws ec2 describe-client-vpn-target-networks --client-vpn-endpoint-id " + client_vpn_endpoint_id + " --query ClientVpnTargetNetworks[].AssociationId --output text"
result = subprocess.run(
cmd.split(" "),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
association_id_list = result.stdout.decode().strip().split()
print("[DEBUG] association_id_list: %s" % association_id_list)
for association_id in association_id_list:
cmd = "python aws ec2 disassociate-client-vpn-target-network --client-vpn-endpoint-id " + client_vpn_endpoint_id + " --association-id " + association_id
result = subprocess.run(
cmd.split(" "),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
print(result.stdout.decode())
Python スクリプトを作成したら、lambda-cli ディレクトリをZIP圧縮します。
2.2 Lambda へのデプロイ
ZIPファイルを指定して、Lambda 関数を作成します。(bash で実行する場合は、改行文字を「`」→「\」に置換してください)
# ロール ARN の取得
$RoleArn=$(aws iam get-role --role-name $RoleName --query 'Role.Arn' --output text)
# Lambda 関数の作成
aws lambda create-function `
--function-name $FunctionName `
--runtime python3.7 `
--role $RoleArn `
--handler $handlerName `
--timeout 120 `
--zip-file fileb://lambda-cli.zip `
--region $Region
# Lambda 関数の確認
aws lambda get-function --function-name $FunctionName
# 補足:Lambda 関数を更新
aws lambda update-function-code `
--function-name $FunctionName `
--zip-file fileb://lambda-cli.zip `
--publish
2.3 Lambda 関数のテスト実行
GUI でテスト実行をしてもよいですが、ここでは AWS CLI で実行します。
# Lambda 関数のテスト実行
aws lambda invoke --invocation-type Event `
--function-name $FunctionName `
--region $Region `
--log-type Tail outfile.txt
CloudWatch Logs に次のようなログが出力されていれば、OK です。
{
"AssociationId": "cvpn-assoc-XXXXXXXXXXXXXXXX",
"Status": {
"Code": "associating"
}
}
The button is clicked once.
{
"AssociationId": "cvpn-assoc-XXXXXXXXXXXXXXXX",
"Status": {
"Code": "disassociating"
}
}
The button is double clicked.
3. AWS IoT 1-Click に登録する
こちらの記事ステップ 1: AWS IoT 1-Clickに登録するを参考にしてください。
4. AWS IoT 1-Click のプロジェクトを作成する
AWS管理コンソールから AWS IoT 1-Click のコンソールを開き、以下の画面ショットの通り入力していきます。
登録したデバイスを選択し、プレイスメントの作成を完了すれば、設定は全て完了です。
試しに、IoTデバイスのボタンをクリックして、AWS IoT 1-Click のコンソールのレポートから Lambda関数呼び出しに成功したことを確認できれば合格です。
お疲れ様でした。
最後に
前の記事 節約術!? AWS Client VPN を Lambda で定時に起動・停止してみた で、定時に Client VPN エンドポイントの関連付け・関連付け解除をする方法を紹介しました。
今回は以前から気になっていたIoTボタンと連携して、関連付け・関連付け解除をできるようにしてみました。
定時実行よりも融通がききますし、ボタンをクリックしたら、よし!開発するぞっというやる気スイッチも入るのでおすすめです(笑)。
最後まで読んでいただきまして、ありがとうございました。