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}"
}