API GatewayのリソースポリシーでWAFを使わずにIP制限
Posted On 2025-01-08
モック統合でもリソースポリシーを利用すれば、API Gateway単体で簡単にIP制限をかけられる。WAFに比べ高機能ではないものの、開発時の安全対策として十分に活用できる。
目次
はじめに
- SCSを勉強していたら出てきたもの。API GatewayのIP制限はWAFを使うのが定番だが、API Gateway単体でもリソースポリシーを使うとIP制限をできるというもの
- WAFに比べて高機能ではないが、「開発時にAPI Gatewayを野ざらしにしておくのが怖いです^^」みたいなときは使えそうな機能
ポイント
API Gatewayのモック統合をベースに進める。
モックであってもIP制限は可能。なぜならモック統合とはLambdaのついていないAPI Gatewayの単体で仮のレスポンスを返す機能で、リソースポリシーはAPI Gatewayの自体の機能だから。
許可するIPレンジを変数で定義する。
variable "allowed_ips" {
description = "APIアクセスを許可するIPアドレスまたはCIDRのリスト"
type = list(string)
default = ["123.0.45.0/24"] # 例: 許可するIPレンジ
}
localsにポリシーを定義する
locals {
# ...
ip_restriction_policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "IPAllow",
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.example_api.id}/*/*/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": ${jsonencode(var.allowed_ips)}
}
}
}
]
}
POLICY
}
既に定義しているAPI Gatewayに対してポリシーを付与する
resource "aws_api_gateway_rest_api" "example_api" {
name = "ArithmeticOperationsAPI_Mock"
description = "Mock API for arithmetic operations"
endpoint_configuration {
types = ["REGIONAL"]
}
}
resource "aws_api_gateway_rest_api_policy" "ip_restriction" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
policy = local.ip_restriction_policy
}
再デプロイのトリガーに追加する
resource "aws_api_gateway_deployment" "example_deployment" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
triggers = {
# ...
redeployment_policy = sha1(local.ip_restriction_policy)
}
lifecycle {
create_before_destroy = true
}
}
結果
許可されているIPからcurlした場合
$ curl https://p5rkuqovn2.execute-api.ap-northeast-1.amazonaws.com/dev/add
{
"message": "Mock response for add"
}
許可外のIPからcurlした場合
$ curl https://p5rkuqovn2.execute-api.ap-northeast-1.amazonaws.com/dev/add
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:********9012:p5rkuqovn2/dev/GET/add"
注意点
- ポリシーの変更が即反映されるわけではない。特にAPI Gatewayにキャッシュが入っていて(CloudFrontがついているケース)、キャッシュに古いポリシーが残っていると反映にラグがある場合がある。すぐ反映されなくても数分~数十分待ったほうが良さげ
全体のコード
variable "allowed_ips" {
description = "APIアクセスを許可するIPアドレスまたはCIDRのリスト"
type = list(string)
default = ["123.0.45.0/24"] # 例: 許可するIPレンジ
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
locals {
operation_funcs = {
"add" = "add"
"sub" = "sub"
"prd" = "prd"
"div" = "div"
}
ip_restriction_policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "IPAllow",
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.example_api.id}/*/*/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": ${jsonencode(var.allowed_ips)}
}
}
}
]
}
POLICY
}
resource "aws_api_gateway_rest_api" "example_api" {
name = "ArithmeticOperationsAPI_Mock"
description = "Mock API for arithmetic operations"
endpoint_configuration {
types = ["REGIONAL"]
}
}
resource "aws_api_gateway_rest_api_policy" "ip_restriction" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
policy = local.ip_restriction_policy
}
# 各operation用のリソース作成
resource "aws_api_gateway_resource" "operation_resource" {
for_each = local.operation_funcs
rest_api_id = aws_api_gateway_rest_api.example_api.id
parent_id = aws_api_gateway_rest_api.example_api.root_resource_id
path_part = each.key
}
# 各operationに対してGETメソッドを作成
resource "aws_api_gateway_method" "operation_method" {
for_each = local.operation_funcs
rest_api_id = aws_api_gateway_rest_api.example_api.id
resource_id = aws_api_gateway_resource.operation_resource[each.key].id
http_method = "GET"
authorization = "NONE"
}
# モック統合設定
resource "aws_api_gateway_integration" "operation_integration" {
for_each = local.operation_funcs
rest_api_id = aws_api_gateway_rest_api.example_api.id
resource_id = aws_api_gateway_resource.operation_resource[each.key].id
http_method = aws_api_gateway_method.operation_method[each.key].http_method
type = "MOCK"
request_templates = {
"application/json" = <<EOF
{
"statusCode": 200
}
EOF
}
}
resource "aws_api_gateway_method_response" "operation_method_response_200" {
for_each = local.operation_funcs
rest_api_id = aws_api_gateway_rest_api.example_api.id
resource_id = aws_api_gateway_resource.operation_resource[each.key].id
http_method = aws_api_gateway_method.operation_method[each.key].http_method
status_code = "200"
response_models = {
"application/json" = "Empty"
}
}
resource "aws_api_gateway_integration_response" "operation_integration_response_200" {
for_each = local.operation_funcs
rest_api_id = aws_api_gateway_rest_api.example_api.id
resource_id = aws_api_gateway_resource.operation_resource[each.key].id
http_method = aws_api_gateway_method.operation_method[each.key].http_method
status_code = aws_api_gateway_method_response.operation_method_response_200[each.key].status_code
response_templates = {
"application/json" = <<EOF
{
"message": "Mock response for ${each.key}"
}
EOF
}
}
resource "aws_api_gateway_deployment" "example_deployment" {
rest_api_id = aws_api_gateway_rest_api.example_api.id
triggers = {
redeployment_resource = sha1(jsonencode(values(aws_api_gateway_resource.operation_resource)))
redeployment_method = sha1(jsonencode(values(aws_api_gateway_method.operation_method)))
redeployment_integration = sha1(jsonencode(values(aws_api_gateway_integration.operation_integration)))
redeployment_method_response = sha1(jsonencode(values(aws_api_gateway_method_response.operation_method_response_200)))
redeployment_integration_response = sha1(jsonencode(values(aws_api_gateway_integration_response.operation_integration_response_200)))
redeployment_policy = sha1(local.ip_restriction_policy)
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_api_gateway_stage" "example" {
deployment_id = aws_api_gateway_deployment.example_deployment.id
rest_api_id = aws_api_gateway_rest_api.example_api.id
stage_name = "dev"
}
output "api_endpoint" {
value = aws_api_gateway_stage.example.invoke_url
}
所感
- まあなんか使えそう
- ガッツリこれで制限するっていうよりかは、テスト時の不正なリクエストを弾くのが目的かな。本番だったらWAFのほうが良さそう
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー