Terraform and CORS-Enabled AWS API Gateway and AWS WAF
この記事はAWS WAF と API Gateway endpoint で CORS を有効化について。
以下にsampleを載せてあります。
REST API リソースが API 独自のドメイン以外のドメインからリクエストを受け取る場合、 リソースの選択されたメソッドで Cross-Origin Resource Sharing (CORS) を有効にする必要があるので、
CORS 対応のために、今回は以下のレスポンスヘッダーを付与させます。
- Access-Control-Allow-Origin
API Gateway のREST APIの設定
WAFとAPI Gatewayをつなげるので、 endpoint_configuration
の設定が必要になる。
## API Gateway - Rest API resource "aws_api_gateway_rest_api" "rest_api" { name = "${var.env}-${var.name}-api-gateway" description = "${var.env}-${var.name}-api-gateway" minimum_compression_size = 0 endpoint_configuration { types = ["REGIONAL"] } }
API GatewayのResourcesの設定
TerraformのProviderを参考に作成するだけで大丈夫。
## API Gateway - Resources resource "aws_api_gateway_resource" "resource" { depends_on = ["aws_api_gateway_rest_api.rest_api"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" parent_id = "${aws_api_gateway_rest_api.rest_api.root_resource_id}" path_part = "${var.name}" }
API GatewayのMethodの設定
response_models
や response_parameters
をつける以外は
TerraformのProviderを参考に作成するだけで大丈夫。
CORS対応のために Access-Control-Allow-Origin
をレスポンスヘッダに付与させます。
## API Gateway - Method Setting # post resource "aws_api_gateway_method" "method_post" { depends_on = ["aws_api_gateway_resource.resource"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "POST" authorization = "NONE" api_key_required = false request_parameters = { "method.request.querystring.key" = true } } resource "aws_api_gateway_method_response" "method_response_post_200" { depends_on = ["aws_api_gateway_method.method_post"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_post.http_method}" status_code = "200" response_models = { "application/json" = "Empty" } response_parameters { "method.response.header.Access-Control-Allow-Origin" = true } } resource "aws_api_gateway_method_response" "method_response_post_400" { depends_on = ["aws_api_gateway_method.method_post"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_post.http_method}" status_code = "400" response_models = { "application/json" = "Empty" } response_parameters { "method.response.header.Access-Control-Allow-Origin" = true } }
API GatewayのIntegrationの設定
- ハマりポイント
selection_pattern
が必要aws_api_gateway_integration_response
はaws_api_gateway_method_response
に依存する。
## API Gateway - Integration Setting resource "aws_api_gateway_integration" "integration_post" { depends_on = ["aws_api_gateway_method.method_post"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_post.http_method}" integration_http_method = "POST" type = "HTTP" uri = "${var.url}" } resource "aws_api_gateway_integration_response" "integration_response_post_200" { depends_on = ["aws_api_gateway_method_response.method_response_post_200"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_post.http_method}" status_code = "${aws_api_gateway_method_response.method_response_post_200.status_code}" selection_pattern = "200" response_templates = { "application/json" = "" } response_parameters = { "method.response.header.Access-Control-Allow-Origin" = "'*'" } } resource "aws_api_gateway_integration_response" "integration_response_post_400" { depends_on = ["aws_api_gateway_method_response.method_response_post_400"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_post.http_method}" status_code = "${aws_api_gateway_method_response.method_response_post_400.status_code}" selection_pattern = "4\\d{2}" response_templates = { "application/json" = "" } response_parameters = { "method.response.header.Access-Control-Allow-Origin" = "'*'" } }
API Gateway では、Mock 統合タイプで OPTIONS
メソッドを設定して CORS を有効にし、
前述のレスポンスヘッダーをメソッドのレスポンスヘッダーとして返す。
200 レスポンスで Access-Control-Allow-Origin:'*request-originating server addresses*'
ヘッダーを返すようにすること。
特定の request-originating server addresses
の静的な値を *
に置き換えることもできるけど
広範なサポートを有効にすることはよく考えること。任意のサーバーを指定可能なのでできるならその方がよいので。
今回では *
をつけています。
今回はMock統合タイプでbackendがGoogle Formを例にしている。
Access-Control-Allow-Origin
を設定すれば CORS
は有効化できます。
- ハマりポイント
OPTIONS
メソッドを使用するのを忘れないselection_pattern
が必要aws_api_gateway_integration_response
はaws_api_gateway_method_response
に依存する。
# options resource "aws_api_gateway_method" "method_options" { depends_on = ["aws_api_gateway_resource.resource"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "OPTIONS" authorization = "NONE" api_key_required = false request_parameters = { "method.request.querystring.key" = true } } resource "aws_api_gateway_method_response" "method_response_options_200" { depends_on = ["aws_api_gateway_method.method_options"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_options.http_method}" status_code = "200" response_models = { "application/json" = "Empty" } response_parameters { "method.response.header.Access-Control-Allow-Origin" = true } } resource "aws_api_gateway_method_response" "method_response_options_400" { depends_on = ["aws_api_gateway_method.method_options"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_options.http_method}" status_code = "400" response_models = { "application/json" = "Empty" } response_parameters { "method.response.header.Access-Control-Allow-Origin" = true } } resource "aws_api_gateway_integration" "integration_options" { depends_on = ["aws_api_gateway_method.method_options"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_options.http_method}" integration_http_method = "OPTIONS" type = "MOCK" } resource "aws_api_gateway_integration_response" "integration_response_options_200" { depends_on = ["aws_api_gateway_method_response.method_response_options_200"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_options.http_method}" status_code = "${aws_api_gateway_method_response.method_response_options_200.status_code}" selection_pattern = "200" response_templates = { "application/json" = "" } response_parameters = { "method.response.header.Access-Control-Allow-Origin" = "'*'" } } resource "aws_api_gateway_integration_response" "integration_response_options_400" { depends_on = ["aws_api_gateway_method_response.method_response_options_400"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" resource_id = "${aws_api_gateway_resource.resource.id}" http_method = "${aws_api_gateway_method.method_options.http_method}" status_code = "${aws_api_gateway_method_response.method_response_options_400.status_code}" selection_pattern = "4\\d{2}" response_templates = { "application/json" = "" } response_parameters = { "method.response.header.Access-Control-Allow-Origin" = "'*'" } }
- ハマりポイント
aws_api_gateway_deploymen
とaws_api_gateway_stage
は両方必要ない (stage-nameがconflict する)。こちら参照)
## API Gateway - Deployment resource "aws_api_gateway_deployment" "deployment_200" { depends_on = [ "aws_api_gateway_integration.integration_post", "aws_api_gateway_integration_response.integration_response_post_200", ] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" stage_name = "${var.env}" } resource "aws_api_gateway_method_settings" "method_settings_200" { depends_on = ["aws_api_gateway_deployment.deployment_200"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" stage_name = "${var.env}" method_path = "*/*" settings { metrics_enabled = true logging_level = "INFO" } } resource "aws_api_gateway_deployment" "deployment_400" { depends_on = [ "aws_api_gateway_integration.integration_post", "aws_api_gateway_integration_response.integration_response_post_400", ] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" stage_name = "${var.env}" } resource "aws_api_gateway_method_settings" "method_settings_400" { depends_on = ["aws_api_gateway_deployment.deployment_400"] rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}" stage_name = "${var.env}" method_path = "*/*" settings { metrics_enabled = true logging_level = "INFO" } }
WAFのRuleはIP制限。
同一IPアドレスから5分間に2000(最低値)を超えたリクエストをトリガー。
WAF ResourcesではなくWAF Regional Resoucesを使う必要がある。
- ハマりポイント
metric_name
は英数字のみなので-
などは使わない。こちら参照)
## WAF RULE resource "aws_wafregional_rate_based_rule" "wafrule" { name = "${var.env}-bov-form-protect-dos-rule" metric_name = "${var.env}bovrule" rate_key = "IP" rate_limit = 2000 }
WAF RULEに該当したらBLOCKさせる設定をする。
aws_wafregional_web_acl_association) ではまだ ALB
しか公式サポートしていないようだけど、
WAFとAPI Gatewayの繋ぎこみの設定は web_acl_id
とresource_arn
を書くことで可能。
resource_arn
の書き方は AWSドキュメンテーションなど参照)
もしくはAssociateWebACLのドキュメンテーション)
- ハマりポイント
wafregional_ipset
はALBで使うproviderで今回はIP指定もないので使わない。こちら参照)aws_wafregional_ipset
の変わりにaws_wafregional_rate_based_rule
を使用する
## WAF_ACL rate_based block over 2000 access resource "aws_wafregional_web_acl" "wafacl" { name = "${var.env}-bov-form-protect-dos-acl" metric_name = "${var.env}BovFormAccess" default_action { type = "ALLOW" } rule { action { type = "BLOCK" } priority = 1 rule_id = "${aws_wafregional_rate_based_rule.wafrule.id}" type = "RATE_BASED" } } resource "aws_wafregional_web_acl_association" "wafassosiation" { depends_on = ["aws_wafregional_web_acl.wafacl"] resource_arn = "arn:aws:apigateway:ap-northeast-1::/restapis/${aws_api_gateway_rest_api.rest_api.id}/stages/${var.env}" web_acl_id = "${aws_wafregional_web_acl.wafacl.id}" }