こんにちは、ディベロップメントサービス1課の山本です。
最近ハマっている飲み物はセブンイレブンさんから出てる『濃いライチサイダー』です。
ソルティライチに炭酸を付与したような飲み心地でとても美味しいです。
今回はAWS Client VPN のコスト削減方法についてご紹介します。
よく使われますが地味にコストのかかるサービスですので、料金を少しでも安くしたい方必見です。
- この記事の対象者は?
- AWS Client VPN の料金
- 進め方
- コンソールから強制切断してみた
- 対策
- 構成図
- DynamoDB(ユーザー接続管理)
- AWS Lambda (接続時間の監視)
- AWS Lambda (クライアント接続ハンドラー)
- 確認
- まとめ
- さいごに
この記事の対象者は?
- AWS Client VPN を利用されている方
- よく VPN を繋いだまま放置している方
AWS Client VPN の料金
東京リージョンでの料金は以下の通りです。
No. | 項目 | 料金 |
---|---|---|
1 | AWS Client VPN エンドポイントアソシエーション | USD 0.15/時間 |
2 | AWS Client VPN 接続 | USD 0.05/時間 |
1の料金は AWS Client VPN に関連づけられているサブネットの数に対して、
2の料金は アクティブなユーザーの数だけそれぞれ発生します。
ここでクイズです。
以下の場合、月額何円になるでしょうか
- 前提条件(1USD = 160円)
- 割り当てられているサブネット数:2
- 1日でサブネットに割り当てられている時間:24時間 (常時)
- アクティブなユーザー数:10
- 1日でアクティブな時間:12時間
10人での利用なので、いっても1万円程度ですかね?
答えです。
- AWS Client VPN エンドポイントアソシエーション
- 0.15[USD/時間] × 24[時間] × 2[サブネット数] × 30[日] × 160[円/USD] = 34,560[円]
- AWS Client VPN 接続
- 0.05[USD/時間] × 12[時間] × 10[ユーザー数] × 30[日] × 160[円/USD] = 28,800[円]
- 合計
- 34,560[円] + 28,800[円] = 63,360[円]
月額63,360円は意外に高い。
128 MBの AWS Lambda でしたら、起動時間 1 秒で 月に2億回起動できます。
※ 上記の内容は、2024年6月27日時点のため正式料金は以下の公式ページを参考ください。 aws.amazon.com
進め方
今回はユーザーがアクティブな時間を減らす方法で、コスト削減を実現します。
必要があって繋いだけれども、そのまま放置している人たちを強制遮断してみます。
サブネットが割り当てられている時間を減らす方法は、弊社過去ブログにて紹介しておりますのでそちらをご参考ください。
コンソールから強制切断してみた
クライアント VPN エンドポイント > 接続タブ からアクティブなユーザーを強制切断することが可能です。
まずは、長時間接続しているユーザーを手動で強制切断してみます。
これで無事にVPNから切断できました。
と思ったら?!
AWS VPN Client 側が一時的な通信不良と思ったのか、すぐに再接続されました。
これでは意味がないですね。
対策
強制切断から一定期間(1分程度)の接続禁止時間を設けることで対策してみます。
以下のように定期監視
とVPN接続時
に特定の処理をさせて実現します。
- 定期監視
- 1時間に1回、長時間接続ユーザーの有無を監視
- 長時間接続ユーザーをブラックリストに登録
- 長時間接続ユーザーを強制切断
- VPN接続時
- ユーザーがブラックリストに登録されている場合、接続を拒否する
構成図
AWS Lambda と DynamoDB を組み合わせて、実現します。
DynamoDB(ユーザー接続管理)
ユーザーはデバイスを一意に識別する common_name
を利用します。
禁止期間が終わったらデータを削除したいので、禁止解除までの時間 で TTL を設定します。
キー名 | 説明 | 備考 |
---|---|---|
common_name | AWS Client VPN での接続名 | パーティションキー に割り当て |
expired_at | 禁止解除までの時間 | TTL に設定 |
AWS Lambda (接続時間の監視)
以下のコードで作成します。
イベント元を Event Bridge に設定して、1時間に1回起動させます。
import json import boto3 import datetime import os ENDPOINT_ID = os.environ['ENDPOINT_ID'] # Client VPN エンドポイント ID TERMINATE_TIME = int(os.environ['TERMINATE_TIME']) # 接続を切断するまでの時間(時間) TABLE_NAME = os.environ['TABLE_NAME'] # DynamoDB テーブル名 TTL = int(os.environ['TTL']) # 禁止期間(秒)1分程度で設定 def lambda_handler(event, context): client = boto3.client('ec2') dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(TABLE_NAME) # クライアント VPN の接続情報を取得 response = client.describe_client_vpn_connections( ClientVpnEndpointId=ENDPOINT_ID ) for connection in response['Connections']: # 接続がアクティブかどうかを判定 if connection["Status"]["Code"] == "active": connect_time = datetime.datetime.strptime(connection['ConnectionEstablishedTime'], "%Y-%m-%d %H:%M:%S") diff_time = datetime.datetime.now() - connect_time # 接続が一定時間経過している場合は接続を切断 if diff_time > datetime.timedelta(hours=TERMINATE_TIME): common_name = connection["CommonName"] # DynamoDBに接続情報を登録 table.put_item( Item={ 'common_name': common_name, 'expired_at': int(datetime.datetime.now().timestamp()) + TTL } ) # 接続を切断 client.terminate_client_vpn_connections( ClientVpnEndpointId=ENDPOINT_ID, ConnectionId=connection['ConnectionId'] ) return { 'statusCode': 200, 'body': json.dumps('Success') }
AWS Lambda (クライアント接続ハンドラー)
AWS Client VPN には接続承認を管理する AWS Lambda ハンドラーを設定できます。
接続承認 - AWS クライアント VPN
入力(event)
{ "connection-id": <connection ID>, "endpoint-id": <client VPN endpoint ID>, "common-name": <cert-common-name>, "username": <user identifier>, "platform": <OS platform>, "platform-version": <OS version>, "public-ip": <public IP address>, "client-openvpn-version": <client OpenVPN version>, "aws-client-version": <AWS client version>, "groups": <group identifier>, "schema-version": "v3" }
出力(response)
{ "allow": boolean, "error-msg-on-denied-connection": "", "posture-compliance-statuses": [], "schema-version": "v3" }
入力データからcommon-name
を取得し、ブラックリストへの登録有無を確認します。
ブラックリスト登録時は出力データのalllow
を Falseに設定。
ブラックリスト未登録時は出力データのalllow
を Trueに設定して返却します。
以下のコードで作成します。
import boto3 import datetime import os ENDPOINT_ID = os.environ['ENDPOINT_ID'] TABLE_NAME = os.environ['TABLE_NAME'] # Lambda VPN クライアントハンドラー用の関数 def lambda_handler(event, context): # リクエストデータから接続情報を取得 common_name = event["common-name"] # DynamoDB へ接続 dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(TABLE_NAME) try: response = table.get_item( Key={ 'common_name': common_name } ) expired_at = response['Item']["expired_at"] # 禁止期間が切れているかどうかを判定 if int(datetime.datetime.now().timestamp()) < expired_at: is_allowed = False else: is_allowed = True except Exception: # DynamoDBに登録されていない場合は接続を許可 is_allowed = True # レスポンスデータを作成 response_data = { "allow": is_allowed, "error-msg-on-denied-connection": "", "posture-compliance-statuses": [], "schema-version": "v3" } # レスポンスデータを返却 return response_data
注意点
Lambda 関数の名前は、 AWSClientVPN- プレフィックスで始まる必要があります。
要件と考慮事項
クライアント接続ハンドラーの要件と考慮事項を次に示します。
Lambda 関数の名前は、 AWSClientVPN- プレフィックスで始まる必要があります。
確認
無事、接続エラーとなり強制切断することに成功しました。
まとめ
- コンソール上からAWS Client VPN接続の強制切断は可能だが、クライアント側ですぐ再接続される
- 接続承認を管理する AWS Lambda ハンドラー を設定することで、ユーザーの接続可否は管理可能
さいごに
賢いクライアントソフトに対抗するため、頑張りました。
本ブログがどなかたのお役に立てれば幸いです。