AWS|ノウハウ:Web Socket APIにIP制限をかける方法
知識は武器とかけまして、レゴブロックと解く、その心は――
今日もKnowledge Oasisへようこそ
案内人はkoふみです
本日のテーマは『Web Socket APIにIP制限をかける方法』
はじめに
「なんでREST APIでは簡単にIP制限できるのに、WebSocket APIではできないんだろう?」
つい先日、わたしもその疑問に頭を抱えました。調べてみると、AWSの設計上の仕様や制限が背景にあることが分かってきて……。そこで本記事では、初心者でもわかるように「なぜ制限できないのか」を丁寧に説明しつつ、実現方法をやさしく解説します。
対象読者
- AWSをこれから使ってみたいという方
- REST APIではIP制限できたのにWebSocket APIではできずに困っている方
- 技術の背景と実践的な対応策、両方を理解したい方
RestAPIにIP制限をかける方法
以下の2つが、REST APIでのIP制限で用いられる代表的な手段です。
WAFを使う方法
AWS WAFをREST APIに適用すれば、IPアドレスやCIDR、特定のヘッダー・文字列などを基にアクセス制御できます。例えば「ホワイトリスト方式」で特定IPのみ許可し、それ以外を弾くことも可能です 。WAFはREST APIに直接紐づけでき、非常に簡単に強固なセキュリティを実現できます。
API Gatewayのリソースポリシーを設定する方法
REST APIの場合、API Gateway自体にリソースポリシーという仕組みがあり、ここでIPアドレスを条件として許可・拒否が可能です。たとえば、以下のようなポリシーで「特定IP以外を拒否」できます :
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": [
"execute-api:/*"
]
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": [
"execute-api:/*"
],
"Condition" : {
"IpAddress": {
"aws:SourceIp": ["192.0.2.0/24", "198.51.100.0/24" ]
}
}
}
]
}
Web Socket APIにIP制限がかけられない?
WAFで無理な理由
AWS WAFは、CloudFrontやALBなど特定のリソースにだけ紐づけ可能であり、API GatewayのWebSocket API(v2)には対応していません。そのため、REST APIのようにWAF単体でIP制限はできないのです。
API Gatewayのリソースポリシーで無理な理由
REST APIでは可能だったAPI Gatewayのリソースポリシーも、WebSocket APIには設定できません。WebSocket API(v2)にはリソースポリシー機能そのものが現状なく、このためREST APIと同じ方式ではIP制限が実現できないのです 。
Web Socket APIにIP制限をかける方法
CloudFront + WAF
CloudFrontはWebSocketにも対応しており、GlobalスコープのWAFと組み合わせることで、ハンドシェイク時(Upgradeリクエスト)にIP制限が可能です。具体的には以下の構成で実現できます:
- Web Socket APIに CloudFrontディストリビューションを作成 。オリジンにはWebSocket APIを指定します。
- Behaviorを作成。気を付ける設定は以下の通りです。
- Path Pattern: API Gatewayのステージに合わせたパス。
/prod/*
など。 - Viewer Protocol Policy: HTTPS Only
- Origin Request Policy: AllViewerExceptHostHeader
- Path Pattern: API Gatewayのステージに合わせたパス。
- GlobalスコープでIPSet(許可IP)とのWeb ACLを作成。 GlobalスコープのWAFしかCloud Frontを割り当てられません。
- Web ACLにIPSetルール(Allow)とデフォルトBlockルールを設定し、CloudFrontに関連付け。
これにより、CloudFrontのエッジでIPチェックが行われ、不正な接続はハンドシェイク前にブロックできます。
ただし、ハンドシェイク後のデータフレームには影響ありません。
注意点
/prod/*
のパス設定を誤ってオリジンのOrigin Pathにしてしまわないこと。
Origin Pathに /hoge
を設定していた場合、xxxxxxx.cloudfront.net/prod
へのアクセスすると、 xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/hoge/prod
に転送されます。
私は誤ってOrigin Pathに設定してしまい、Web Socket APIが呼び出せない沼にはまってしまい、抜け出すのに苦労しました。
Lambda オーソライザー
もう一つは $connect
ルートにLambda REQUESTオーソライザーを設定する方法です。手順は以下の通り:
-
$connect
統合用Lambda関数を作成。成功時にHTTP 200を返すだけでOK。接続確立が進みます。 -
- で作成した
$connect
統合用Lambda関数を指定して、Web Socket APIに$connect
ルートを追加。
- で作成した
- Lambda オーソライザー用Lambda関数を作成。環境変数で許可IPリストを保持。IPアドレスを判定して許可・拒否判定を行い、Allow/Denyポリシーを返します。
-
- で作ったLambda関数を指定してLambda オーソライザーを作成。IDソースタイプにはコンテキストのroute.request.context.identity.sourceIpを設定する。
-
$connect
ルートのルートリクエストの設定で 認可 に上記で作成したLamdaオーソライザーを指定する。
$connect
統合用Lambdaの実装例
def lambda_handler(event, context):
# (任意) 接続イベントをログや DB に保存
conn_id = event['requestContext']['connectionId']
print(f'Connected: {conn_id}', event['requestContext']['identity']['sourceIp'])
# 成功レスポンス
return { 'statusCode': 200 }
Lambdaオーソライザーの実装例
import os
def generate_policy(principal_id, effect, resource):
"""
API Gateway Lambda オーソライザー用ポリシー生成ヘルパー
"""
auth_response = {
'principalId': principal_id,
'policyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': resource
}]
}
}
return auth_response
def lambda_handler(event, context):
"""
Lambda REQUEST オーソライザー本体
"""
# 環境変数からホワイトリスト IP を取得(カンマ区切り)
allowed_ips = os.environ.get('ALLOWED_IPS', '')
whitelist = {ip.strip() for ip in allowed_ips.split(',') if ip.strip()}
# WebSocket 接続ハンドシェイク時のリクエスト情報
ctx = event.get('requestContext', {})
# IPアドレスを取得
source_ip = ctx.get('identity', {}).get('sourceIp')
# principalId は任意の識別子。IP をそのまま使っても OK
principal_id = source_ip or 'unknown'
# 接続先リソース
method_arn = event.get('methodArn')
if source_ip in whitelist:
# 許可リストにあれば Allow
return generate_policy(principal_id, 'Allow', method_arn)
else:
# なければ Unauthorized (403)
# Lambda オーソライザーでは例外をスローすると 401 に、
# policy で Block 相当のステートメントを返すと 403 になります。
return generate_policy(principal_id, 'Deny', method_arn)
この方法ならAWSの他サービスに頼らずに済み、API層だけで完結しますが、Lambda起動時間(Cold Start)とコストが接続ごとにかかる点に注意が必要です。また接続後の許可状態はそのまま維持され、動的制御には別途ロジックが必要です。
比較
特徴 | Lambda オーソライザー | CloudFront + WAF |
---|---|---|
適用タイミング |
$connect ハンドシェイク時 |
Upgradeリクエスト(ハンドシェイク前) |
実装のしやすさ | API Gateway内で完結、手軽 | 構成が多く複雑 |
レイテンシ | Cold Startで数十〜百ms遅延あり | エッジで高速 |
コスト | Lambda実行 + Gateway料金 | CloudFront + WAF料金 |
スケーラビリティ | Lambda同時実行制限あり | 高トラフィックにも強い |
制御の柔軟性 | アプリケーションで動的制御可能 | WAFルールに限られる |
選び方の目安
- 少人数の利用・すぐ試したい → Lambdaオーソライザが手軽
- 大量接続・低遅延が求められる → CloudFront + WAFが安定
まとめ
REST APIに比べてWebSocket APIではWAFやリソースポリシーが使えないため、IP制限には別の工夫が必要です。$connect
ルートでLambdaオーソライザーを使う方法と、CloudFront+GlobalスコープのWAFを使ってエッジで制御する方法があります。それぞれメリット・デメリットもあるので、ご自身のユースケースに応じて選んでみてください。
知識のひとつひとつは小さなレゴブロック
でも、組み合わせれば世界を変えるアイディアをカタチにする武器になる!
またKnowledge Oasisでお会いしましょう
案内人はkoふみでした
Discussion