AWS

API GatewayのIP制限:リソースポリシーとWAFのIPセットで送信元を絞る手順と使い分け

API GatewayのIP制限:リソースポリシーとWAFのIPセットで送信元を絞る手順と使い分け

Amazon API GatewayでIP制限を掛ける方法は、REST APIのリソースポリシーにaws:SourceIpを書くか、AWS WAFのIPセットをWeb ACLで関連付けるかの2つが中心です。本記事では両方の設定をCLIとJSONで示し、許可リストを明示的なDenyで書かないとLambdaオーソライザー併用時に素通りする理由、ポリシー8,192文字とIPセット1万件という上限の差、社内からだけ呼ばせるプライベートAPI、HTTP APIでの代替手段まで扱います。API Gateway自体の役割はAPIゲートウェイの役割と導入判断で整理しています。

まとめ:REST APIはリソースポリシー、件数と変更頻度が増えたらWAFのIPセット

許可元が数十件のCIDRに収まり、変更が月に1回あるかどうかなら、REST APIのリソースポリシーで対応可能です。追加料金がかからず、IPが外れた呼び出しはオーソライザーやバックエンドに届く前に403で止まります。ただし書き換えるたびに再デプロイが要り、ポリシー全体で8,192文字という上限があります。

許可元が頻繁に変わる、複数のAPIで同じリストを共有したい、数百件を超える、という条件が1つでもあればWAFのIPセットに移します。HTTP APIはどちらにも対応していないため、Lambdaオーソライザーで送信元IPを判定するか、前段にCloudFrontを置いてそこでWeb ACLを掛けます。社内システムからしか呼ばないAPIなら、IPで絞るよりプライベートAPIにしてVPCエンドポイントで閉じるほうが確実です。

API GatewayのIP制限の設定箇所とREST・HTTP APIごとの対応範囲

最初に確かめるのはAPIの種類です。REST APIかHTTP APIかで、使える手段がほぼ決まります。

リソースポリシーとWAFはREST API専用で、HTTP APIでは使えない制約

AWS公式のREST APIとHTTP APIの機能比較では、リソースポリシー、AWS WAF、プライベートエンドポイントの3つがREST APIだけの機能として並んでいます(2026年10月時点)。

IP制限の手段 REST API HTTP API 変更の反映
リソースポリシー(aws:SourceIp) 対応 非対応 再デプロイ後
AWS WAFのIPセット 対応 非対応 数秒から数分
プライベートAPI+VPCエンドポイント 対応 非対応 再デプロイ後
Lambdaオーソライザーで判定 対応 対応 関数の更新時
前段CloudFrontのWeb ACL 対応 対応 数秒から数分

HTTP APIで送信元を絞りたいなら、選択肢は下2行に限られます。IP制限が要件に入った時点でREST APIを選び直すことも、選択肢の1つです。料金差はAPI Gatewayの料金の実額計算で比べられます。

評価はWAFからリソースポリシー、オーソライザーの順で拒否が先に返る

REST APIでは、API GatewayのWAF連携の公式ページにあるとおり、Web ACLのルールがリソースポリシーより先に評価されます。WAFで遮断されたリクエストはリソースポリシーにもオーソライザーにも届きません。リソースポリシーとLambdaオーソライザーの関係はリソースポリシーと認可フローの公式解説にあり、オーソライザーを呼ぶ前に明示的なDenyだけを先に調べ、該当すれば即座に拒否すると書かれています。

この順序のおかげで、IPが外れた呼び出しにLambdaの実行料金はかかりません。逆に言えば、明示的なDenyとして書いていないIP条件は、この事前チェックでは何も止めません。次の章のポリシーをDenyで書く理由はここにあります。

リソースポリシーで送信元IPを許可リスト化する手順をCLIで試す

ここからは東京リージョンの既存REST API(ID abcde12345、ステージ prod)に、社内拠点のCIDRだけを通すポリシーを付けます。実行するIAMユーザーにはapigateway:UpdateRestApiPolicyとapigateway:PATCHの両方が要ります。

許可リストはAllowだけで書かず、NotIpAddressの明示的なDenyで組む理由

公式の評価表(Table A)では、LambdaオーソライザーがAllowを返し、リソースポリシーがAllowもDenyもしていない場合、結果はAllowです。「許可するIPだけをAllowに書く」ポリシーでは、IPが外れた呼び出しはポリシー上は無言の扱いになり、オーソライザーが通せばそのまま通ります。IP制限が効いていないのに、検証時は認証が無いリクエストで試すため403が返り、気づきにくい失敗です。

そこで全体をAllowし、許可リストに入らないIPをNotIpAddressのDenyで落とします。

{
  "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": {
        "NotIpAddress": {
          "aws:SourceIp": ["203.0.113.0/24", "198.51.100.10/32"]
        }
      }
    }
  ]
}

execute-api:/*は全ステージ・全メソッド・全パスを表す省略記法で、保存時にAPI GatewayがフルARNへ変換します。このファイルをpolicy.jsonとして保存します。Cognitoオーソライザーを使うAPIは、リソースポリシー側に明示的なAllowが必要と公式に書かれているので、1つ目のAllow文は消さないでください。

policy.jsonの適用とステージ再デプロイによるIP制限の反映手順

update-rest-apiのパッチ値にはJSONを文字列として埋め込む必要があります。シェルでの引用符の入れ子を避けるため、jqでパッチ操作のファイルを作ってから渡します。

# ポリシーを1行の文字列にしてパッチ操作を作る
jq -n --arg v "$(jq -c . policy.json)" \
  '[{"op":"replace","path":"/policy","value":$v}]' > patch.json

aws apigateway update-rest-api --region ap-northeast-1 \
  --rest-api-id abcde12345 \
  --patch-operations file://patch.json

# 保存しただけでは動作は変わらない。ステージへ再デプロイする
aws apigateway create-deployment --region ap-northeast-1 \
  --rest-api-id abcde12345 --stage-name prod \
  --description "IP allowlist"

リソースポリシーの作成と適用の公式手順には、ポリシーを更新・保存しただけではAPIの実行時の動作は変わらず、デプロイが必要だと明記されています。「ポリシーを書いたのに効かない」という問い合わせの多くはこの再デプロイ漏れです。

許可外のIPからcurlで403を確かめ、アクセスログのsourceIpと照合する

許可リストに入っていない回線から呼ぶと、ステータス403と次のような本文が返ります。

$ curl -s -w "\n%{http_code}\n" https://abcde12345.execute-api.ap-northeast-1.amazonaws.com/prod/health
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:********1111:abcde12345/prod/GET/health ..."}
403

本文の書き出しは公式のトラブルシューティングに載っている文言と同じで、末尾はアカウントIDを伏せたリソースARNです。許可したはずの拠点から403になるときは、API Gatewayが見ているIPを確かめます。アクセスログの書式に次の変数を入れておくと、1行ごとに判定に使われた送信元が残ります。

{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","status":"$context.status","wafStatus":"$context.waf.status","responseType":"$context.error.responseType"}

$context.identity.sourceIpは、アクセスログ変数の公式一覧で「API Gatewayのエンドポイントへ直接TCP接続してきた送信元」と定義されています。社内プロキシやNATを経由していれば、記録されるのは、そこのグローバルIPです。wafStatusにはWAFが返したステータスが入るので、ここが403ならWAF、WAFが通したうえでstatusが403ならリソースポリシーかオーソライザーで止まったと切り分けられます。ログの出力先とIAMロールの設定はAPI Gatewayのログ設定の手順を参照してください。

AWS WAFのIPセットでIP制限を組むWeb ACLの設定例と上限

WAFでは、IPアドレスのリストを「IPセット」という独立したリソースとして作り、Web ACLのルールから参照します。リストを書き換えても再デプロイは不要です。

create-ip-setでIPv4用のセットを作り、既定アクションをBlockにする

IPセットはIPバージョンを1つ指定して作ります。IPv6も許可するなら、IPv6用のセットを別に作ってルールを2本にします。

# 許可する拠点のCIDRをIPセットにする(REGIONALはAPIと同じリージョン)
aws wafv2 create-ip-set --region ap-northeast-1 \
  --name office-allow-v4 --scope REGIONAL \
  --ip-address-version IPV4 \
  --addresses 203.0.113.0/24 198.51.100.10/32 \
  --query "Summary.ARN" --output text

許可リスト型にするときは、Web ACLの既定アクションをBlockにし、IPセットに一致したリクエストだけをAllowするルールを置きます。次の内容をrules.jsonとして保存し、create-web-aclの--rulesと--default-action Block={}に渡します。

[
  {
    "Name": "allow-office",
    "Priority": 0,
    "Statement": {
      "IPSetReferenceStatement": {
        "ARN": "arn:aws:wafv2:ap-northeast-1:111122223333:regional/ipset/office-allow-v4/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"
      }
    },
    "Action": { "Allow": {} },
    "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "allow-office" }
  }
]

Web ACLの作成とREST APIステージへの関連付けはAPI GatewayにAWS WAFを関連付ける手順と同じです。既にSQLインジェクション対策などのWeb ACLを付けているなら、新しく作らずにこのルールを優先度の高い位置へ足します。1つのステージに関連付けられるWeb ACLは1つだけです。

IPセットは1万件まで、リソースポリシーは8,192文字までという上限差

両者の差がいちばん出るのが件数です。AWS WAFのクォータでは、1つのIPセットに入るCIDRは1万件で、IPセットはリージョンあたり100個まで作れます。IPセット照合のルールは1件1WCUで、件数を増やしてもWCUは増えません。

一方、REST APIのクォータ表ではリソースポリシーの長さが8,192文字です。"198.51.100.10/32", のように1件が20字前後になるため、他の記述を除くと実用上は300件台が目安になります。上限は引き上げ申請ができますが、同じ表の改善策には「WAFv2で保護する」「個別のIPではなく範囲で書く」の2つが挙がっています。数百件を超えそうなら、最初からIPセットで組むのが公式の示す方向です。

X-Forwarded-Forを使う転送IP設定の偽装リスクとFallbackの選び方

WAFのIPセット照合には、送信元IPの代わりにX-Forwarded-Forなどのヘッダの値で判定する「転送IP設定」があります。社内の別のロードバランサーを経由してAPIを呼ぶ構成で使いたくなる設定ですが、転送IPの公式解説は、攻撃者がヘッダの内容を書き換えて検査をすり抜けようとする可能性を明記しています。

許可リスト型のルールで転送IPを使うと、外部の誰でもX-Forwarded-For: 203.0.113.5を付けるだけで許可側に入れます。ヘッダが無いリクエストにはルール自体が適用されず、Fallbackも働かない点も公式に書かれています。許可の判定に転送IPは使わず、直接の送信元IPで判定してください。転送IPを使ってよいのは、信頼できる前段が必ずヘッダを上書きする構成で、遮断側のルールに限る場合です。

プライベートAPIとVPCエンドポイントで、社内からだけ呼べるAPIにする構成

呼び出し元がすべてVPC内のサーバーか、Direct Connectでつながった社内ネットワークなら、パブリックなAPIをIPで絞るより、APIをインターネットから見えなくするほうが確実です。

aws:SourceVpceで特定のVPCエンドポイント以外からの呼び出しを拒否する

プライベートAPIの公式ページによると、プライベートAPIはREST APIだけで作れ、VPC内のインターフェイス型VPCエンドポイント(execute-api)を通してのみ呼べます。リソースポリシーが無いとデプロイできず、公式のベストプラクティスとしてaws:SourceVpcかaws:SourceVpceで呼び出し元を絞ることが挙がっています。

{
  "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": { "StringNotEquals": { "aws:SourceVpce": "vpce-0a1b2c3d4e5f67890" } }
    }
  ]
}

VPCエンドポイントは時間単位の料金がかかり、AZごとに課金されます。費用の考え方はVPCエンドポイントの2種類と料金比較、仕組みはAWS PrivateLinkの仕組みと採用判断で扱っています。

VPCエンドポイントが送信元を書き換えるため、aws:VpcSourceIpで書く注意

プライベートAPIで「VPC内の特定のサブネットだけ」に絞りたい場合、aws:SourceIpは使えません。リソースポリシーの公式例には、execute-apiのVPCエンドポイントが元の送信元IPを書き換えるため、元の呼び出し元IPで判定するにはaws:VpcSourceIpを使うと書かれています。

先ほどのパブリックAPI用ポリシーを流用してaws:SourceIpに10.0.1.0/24を書くと、条件が一致せずすべてDenyになります。プライベートAPIの条件キーはaws:SourceVpce(どのエンドポイントか)とaws:VpcSourceIp(VPC内のどのIPか)の2つで考えてください。

API GatewayのIP制限がすり抜ける・効かない典型パターンと対策

設定自体は正しいのに、別の入口から呼べてしまう、または正規の利用者まで止めてしまう失敗があります。

既定のexecute-apiエンドポイントが残り、カスタムドメイン側の制限を迂回される

カスタムドメインの前にCloudFrontを置き、そこでWeb ACLを掛けている構成では、abcde12345.execute-api…の既定エンドポイントを直接呼ばれると前段の制限を通りません。既定エンドポイント無効化の公式手順で塞ぎます。無効化後は既定エンドポイントへの呼び出しに403が返り、設定は全ステージに及びます。

aws apigateway update-rest-api --region ap-northeast-1 \
  --rest-api-id abcde12345 \
  --patch-operations op=replace,path=/disableExecuteApiEndpoint,value='True'

aws apigateway create-deployment --region ap-northeast-1 \
  --rest-api-id abcde12345 --stage-name prod

カスタムドメインの設定がまだならAPI Gatewayのカスタムドメイン設定を先に済ませます。CloudFront経由に限定する別の方法(秘密ヘッダーの検証など)はCloudFront×API Gateway×Lambdaの連携構成にまとめています。自前のCloudFrontを前段に置く場合、API Gatewayのリソースポリシーがどのアドレスで判定するかは、前章のアクセスログでsourceIpを見て実測してから許可リストを書いてください。前段で絞るなら、CloudFront用(CLOUDFRONTスコープ・us-east-1)のIPセットで判定するのが確実です。

dualstackに切り替えた直後にIPv6で接続する利用者が403になる原因

REST APIのIPアドレスタイプは既定がIPv4で、dualstackに変えるとIPv6でも呼べるようになります。IPアドレスタイプの公式解説によると、この変更は再デプロイなしで即時に効きます。

許可リストがIPv4のCIDRだけのまま切り替えると、IPv6が使える回線の利用者はIPv6で接続し、NotIpAddressの条件に当たって403になります。dualstackにするなら、拠点のIPv6プレフィックス(例:2001:db8:1234::/48)をポリシーかIPv6用のIPセットへ先に足してから切り替えます。プライベートAPIは最初からdualstackしか選べません。

HTTP APIのLambdaオーソライザーでIP判定するときのキャッシュの落とし穴

HTTP APIでは、HTTP API用Lambdaオーソライザーの公式ページにあるとおり、ペイロード2.0のrequestContext.http.sourceIpに送信元IPが入ります。シンプルレスポンスを有効にすれば、真偽値を返すだけで済みます。

import ipaddress
import os

# 例: ALLOWED_CIDRS="203.0.113.0/24,198.51.100.10/32"
ALLOWED = [ipaddress.ip_network(c) for c in os.environ["ALLOWED_CIDRS"].split(",")]

def lambda_handler(event, context):
    ip = ipaddress.ip_address(event["requestContext"]["http"]["sourceIp"])
    return {"isAuthorized": any(ip in net for net in ALLOWED)}

注意するのはキャッシュです。オーソライザーの結果はidentity source(例:Authorizationヘッダ)をキーにキャッシュされます。トークンだけをキーにすると、許可されたIPで一度通ったトークンは、TTLの間は別のIPから使っても通ります。IP判定を入れるならキャッシュTTLを0にするか、identity sourceに$context.identity.sourceIpを加えてキーにIPを含めてください。

リソースポリシーとWAFのどちらでIP制限するかの採用判断と見送る場面

ここまでの仕様を、どの条件でどちらを選ぶかに落とします。

許可元が数十件以下で、変更が月1回以下ならリソースポリシーで足りる条件

取引先数社のCIDRや自社拠点だけを通す社外向けAPIは、リソースポリシーで十分です。WAFのWeb ACL料金とルール料金がかからず、ポリシーはAPI定義と一緒にCloudFormationやTerraformで管理できます。再デプロイが必要という手間も、変更が月1回程度なら運用上の負担になりません。

許可元が頻繁に変わるAPIや複数APIの共通管理では、WAFのIPセットを選ぶ判断

次のどれかに当てはまるなら、WAFのIPセットを選びます。

  • 取引先の追加や拠点の回線変更で、許可リストを週単位で書き換える
  • 同じ許可リストを複数のAPIやステージで共有したい(IPセットを更新すれば参照する全ルールに反映される)
  • 許可元が数百件を超え、リソースポリシーの8,192文字に近づく
  • 既にSQLインジェクション対策などでWeb ACLを付けている

IPセットの更新はupdate-ip-setで行い、UpdateIPSetのAPIリファレンスどおり、リストは差分ではなく全件置き換えです。get-ip-setで取得したLockTokenを渡す必要があり、反映に必要な時間は数秒から数分です。手作業で1件だけ渡して既存の許可元を消す事故が起きやすいので、許可リストはリポジトリで管理し、パイプラインから全件を渡す形にしておきます。取引先ごとのAPI公開やIP管理の自動化まで含めて設計する場合は、API開発・システム連携の支援でご相談いただけます。

IP制限だけで守らないほうがよいAPIと、認証を併用すべき場面

IP制限は「どこから来たか」しか見ません。許可したCIDRの中にいる端末が乗っ取られれば、そのまま呼べます。個人情報や決済を扱うAPIをIP制限だけで公開するのは見送ってください。IAM認可、Cognito、Lambdaオーソライザーのいずれかと必ず組み合わせます。

逆に、モバイルアプリや一般利用者のブラウザから呼ばれるAPIにIP制限は向きません。利用者の回線は変わり続けるため、許可リストを維持できないからです。この場合は認証とWAFのレートベースルールで守り、IP制限は管理用のパスだけに限定します。

よくある質問

API GatewayのIP制限について、設定時によく出る疑問をまとめました。

HTTP APIでIPアドレス制限はできますか?

リソースポリシーとAWS WAFはHTTP APIに対応していないため、同じ方法ではできません。方法は2つあります。1つはLambdaオーソライザーでrequestContext.http.sourceIpを判定する方法で、この場合はキャッシュのキーに送信元IPを含めます。もう1つは前段にCloudFrontを置き、CloudFront用のWeb ACLでIPセットを照合する方法です。どちらも手間がかかるため、IP制限が必須ならREST APIを選ぶほうが素直です。

リソースポリシーを変更したのに反映されないのはなぜですか?

ステージへの再デプロイをしていないことがほとんどの原因です。公式ドキュメントには、ポリシーを更新・保存しただけでは実行時の動作は変わらないと書かれています。create-deploymentでステージに再デプロイしてください。WAFのIPセットは再デプロイ不要ですが、反映まで数秒から数分かかり、その間は場所によって新旧の判定が混ざります。

IP制限で拒否されたときはどんなレスポンスが返りますか?

リソースポリシーで拒否されると403が返り、本文は「User: anonymous is not authorized to perform: execute-api:Invoke」で始まるメッセージです。WAFで遮断された場合も既定では403です。どちらで止まったかはアクセスログの$context.waf.statusで見分けられます。ゲートウェイレスポンスを編集すれば、リソースポリシーの403の本文は変えられます。

IPv6のアドレスも許可リストに入れられますか?

IPv6のアドレスも許可リストへの登録が可能です。リソースポリシーはaws:SourceIpの配列にIPv4とIPv6のCIDRを並べて記述する形式です。WAFのIPセットは1つのセットにIPバージョンを1つしか持てないため、IPv6用のセットを別に作り、ルールも分けます。APIのIPアドレスタイプをdualstackに変えると即時にIPv6の接続が始まるので、許可リストを先に更新しておきます。

特定のパスだけにIP制限を掛けることはできますか?

特定のパスだけを対象にしたIP制限も可能です。リソースポリシーなら、Deny文のResourceをexecute-api:/prod/*/admin/*のように絞れば、/admin配下だけが許可リストの対象になります。WAFなら、URIパスの一致条件とIPセット不一致の条件をAND文で組み、Blockするルールにします。管理画面用のAPIだけ社内に限定する、という使い方が典型です。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.16 テックブログ バーガーキング不正アクセスで約1,118万件の情報漏洩|対象確認・案内メールの真偽と運営側のログ点検
  2. 2026.10.16 テックブログ ソースネクスト不正アクセスで最大2,252万件流出|ログインできない理由・パスワード再設定と会員APIの点検
  3. 2026.10.15 テックブログ リゾートトラスト不正アクセス:会員2.6万件・カハラ横浜3.6万件の対象確認と窓口
  4. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更の手順
  5. 2026.10.16 テックブログ ワラウ不正アクセスで連携トークン流出|Yahoo! JAPAN ID再連携と対象確認

RELATED POSTS 関連記事

目次