AWS Client VPN で特定の時間帯のみクライアント VPN エンドポイントへの接続を許可する仕組みを考えてみた

Client Connect Handler を使用することで、メンテナンスウインドウ中のアクセスを拒否したり、日中帯のみアクセスを許可したり、といったコントロールができます。

コンバンハ、千葉(幸)です。

AWS Client VPN は、VPC 上の AWS リソース、および VPC を経由した対向の環境への安全なアクセスを提供する マネージドな クライアントベースの VPN サービスです。

Client VPN エンドポイントを設置した VPC 上のリソースはもちろん、そこと接続された環境(ピアリング先の VPC やオンプレ環境など)に対してもアクセスが可能となります。

リモートアクセスの手法として便利なサービスですが、使っていく上で特定の時間帯はリソースにアクセスさせたくない、という要件が発生するかもしれません。

接続先のサーバがメンテナンス期間中であったり、何かあった時に迅速に対応が取れない深夜帯には作業して欲しくない、といったケースです。

Client VPN エンドポイントは、EC2 インスタンスのように一時的に停止する、といった操作には対応していません。特定の時間帯のみ停止/起動する、という操作が可能であれば上記の要件も比較的簡単に満たせますが、そうはいかないのです。

とすると、「関連づけを解除する」「ルートテーブルや承認ルールを無効化する」「Client VPN ENI のSecuriryGroup アウトバウンドを制限する」といった操作を一時的に行い、アクセス可能な時間となったら再度有効化する、という代替策が考えつきます。しかし、これらを実現するためにはそれなりの作り込みが必要となりますし、つど設定変更を行う、というのはできるだけ避けたいものです。

そこで、先日のアップデートで対応したクライアント接続ハンドラの利用を考えてみました。これを使えば、作り込みのボリュームは減らせますし、 Lambda 関数のコードを書き換えることで柔軟な制御が行えるようになります。

先にまとめ

  • Client VPN のクライアント接続ハンドラを使用する
  • Lambda 関数のランタイムは Python 3.8 を使用
  • コードはあくまで検証用のものです
  • 以下のような要件に対応
    • 接続を許可する曜日を指定可能
    • 接続が許可された曜日の中で、接続可能な時間帯を指定可能
    • 日付をまたいだ時間帯の設定には未対応

今回の構成について補足

クライアント接続ハンドラ機能は、エンドユーザーが Client VPN エンドポイントに接続を試みた際に、事前に定義された条件に基づいて接続可否をコントロールできる機能です。

Client VPN によりLambda 関数が同期的に呼び出され、そこで定義された内容に基づき接続可否が決定されます。 Lambda 関数はカスタマー側で設定しておく必要があります。

今回は、以下のようなイメージで、エンドユーザーが接続を試みた際に Lambda 関数を呼び出し、現在時刻を取得します。環境変数に設定した曜日、時間帯に合致する場合のみ接続を許可する、という構成をとります。

なお、本エントリでは Client VPN エンドポイントへのクライアント接続ハンドラ設定は完了しているものとし、 Lambda 関数の設定をメインで取り上げていきます。

以下エントリでもクライアント接続ハンドラを使用するケースについて取り上げていますので、あわせてご参照ください。

Lambda 関数の設定

Lambda 関数は以下のように構成します。なお、基本的な設定としてはこのようになっています。

  • 名称:AWSClientVPN-Test
  • ランタイム:Python 3.8
  • IAM ロール:Lambda 関数作成時のデフォルト

環境変数

環境変数には以下のような設定を行います。

キー
ALLOWED_DAYS 接続を許可する曜日(省略形)をカンマ区切りで指定
ALLOWED_TIME_FROM 接続を許可する時間の 開始 を HHMM 形式で指定
ALLOWED_TIME_TO 接続を許可する時間の 終了 を HHMM 形式で指定

なお、タイムゾーンは JST(UTC+9)を基準とします。

また、冒頭の繰り返しとなりますが、日付(曜日)をまたいだウインドウには対応させていません。

コード

以下のように設定しました。

至ってシンプルなものです。

import datetime
import os

now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9)))
now_day = now.strftime('%a')
now_time = int(now.strftime('%H%M'))

allowed_days = os.getenv("ALLOWED_DAYS").split(',')
allowed_time_from = int(os.getenv("ALLOWED_TIME_FROM"))
allowed_time_to = int(os.getenv("ALLOWED_TIME_TO"))

def lambda_handler(event, context):

    if now_day not in allowed_days:
        flag = False
        msg = "You are not allowed access today."
    elif allowed_time_from <= now_time < allowed_time_to:
        flag = True
        msg = ""
    else:
        flag = False
        msg = "Access during this time is prohibited."
   
    return {
            "allow": flag,
            "error-msg-on-failed-posture-compliance": msg,
            "posture-compliance-statuses": [],
            "schema-version": "v1"
            }

現在時刻を取得する処理、環境変数の値と比較可能な形に変換する処理については以下を参考にしました。

やってみた

環境変数を変更しながら、各パターンを確認してみます。

  1. 曜日指定によりブロックされるパターン
  2. 時間指定によりブロックされるパターン
  3. 問題なく接続できるパターン

使用するクライアントは AWS VPN Client for macOS 1.2.4 です。

1. 曜日指定によりブロックされるパターン

今は火曜日です。

% date
2020年 12月 1日 火曜日 21時05分37秒 JST

環境変数から Tueを除外しておきます。

接続を試みると、指定したメッセージが表示された上で、接続が拒否されます。

2. 時間指定によりブロックされるパターン

今は火曜日の21時台です。

% date
2020年 12月 1日 火曜日 21時11分36秒 JST

環境変数で Tue を追加した上で、終了時間を2000で設定しておきます。

先ほどとは異なるメッセージで接続が拒否されます。

3. 問題なく接続できるパターン

今は火曜日の21時15分です。 re:Invent の開催期間 真っ最中です。

% date
2020年 12月 1日 火曜日 21時15分16秒 JST

環境変数で、現在時刻が接続を許可する時間帯におさまるよう設定しておきます。

問題なく接続が成功しました。

一通りの挙動を確認できました。

終わりに

AWS Client VPN で特定の時間帯のみ接続を許可するという仕組みでした。

クライアント接続ハンドラー(Client Connect Handler)のアップデートブログの中で、時間帯によるアクセスのコントロールについて触れられていたので、是非とも試したいものでした。

今回記載したコードはあくまで検証用のものですが、ベースとしてご参考になれば幸いです。

以上、千葉(幸)がお送りしました。