
CloudFront+APIGatewayの構成で送信元をCloudFrontのみに制限する場合に、APIGatewayにWAFを設定し、カスタムヘッダーで制限するのが一般的らしいのでやってみた。
やること
terraformで下記を実施。
CloudFront+APIGatewayを作り、APIGatewayにWAFをアタッチ
CloudFrontにカスタムヘッダーを設定
WAFにカスタムヘッダーを含まないHTTPリクエストはブロックするルールを設定
構成

実践!
1.環境作成
1-1.下記を実行し、コードを作成
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_lambda_function" "example" {
function_name = "example_lambda_function"
runtime = "python3.8"
handler = "lambda_function.lambda_handler"
filename = "lambda_function.zip"
role = aws_iam_role.lambda_exec.arn
}
resource "aws_iam_role" "lambda_exec" {
name = "lambda_exec_role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
},
},
],
})
}
resource "aws_wafv2_web_acl" "example" {
name = "example-acl"
description = "An example ACL"
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "BlockMissingHeader"
priority = 0
action {
block {}
}
statement {
not_statement {
statement {
byte_match_statement {
field_to_match {
single_header {
name = "testheader"
}
}
positional_constraint = "EXACTLY"
search_string = "testvalue"
text_transformation {
priority = 0
type = "NONE"
}
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "BlockMissingHeader"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "example-acl"
sampled_requests_enabled = false
}
}
resource "aws_api_gateway_rest_api" "example" {
name = "ExampleAPI"
description = "Example API"
}
resource "aws_api_gateway_resource" "example" {
rest_api_id = aws_api_gateway_rest_api.example.id
parent_id = aws_api_gateway_rest_api.example.root_resource_id
path_part = "examplepath"
}
resource "aws_api_gateway_method" "example" {
rest_api_id = aws_api_gateway_rest_api.example.id
resource_id = aws_api_gateway_resource.example.id
http_method = "GET"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "example" {
rest_api_id = aws_api_gateway_rest_api.example.id
resource_id = aws_api_gateway_resource.example.id
http_method = aws_api_gateway_method.example.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.example.invoke_arn
}
resource "aws_api_gateway_deployment" "example" {
depends_on = [aws_api_gateway_integration.example]
rest_api_id = aws_api_gateway_rest_api.example.id
}
resource "aws_api_gateway_stage" "example_stage" {
stage_name = "test"
rest_api_id = aws_api_gateway_rest_api.example.id
deployment_id = aws_api_gateway_deployment.example.id
xray_tracing_enabled = false
}
resource "aws_lambda_permission" "api_gateway" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.example.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.example.execution_arn}/*/*/*"
}
resource "aws_wafv2_web_acl_association" "example_association" {
resource_arn = aws_api_gateway_stage.example_stage.arn
web_acl_arn = aws_wafv2_web_acl.example.arn
}
resource "aws_cloudfront_distribution" "example" {
origin {
domain_name = "${aws_api_gateway_rest_api.example.id}.execute-api.ap-northeast-1.amazonaws.com"
origin_id = "APIGatewayOrigin"
custom_header {
name = "testheader"
value = "testvalue"
}
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "APIGatewayOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
ordered_cache_behavior {
path_pattern = "/test/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "APIGatewayOrigin"
forwarded_values {
query_string = true
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
price_class = "PriceClass_100"
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
1-2.terraform実行
2.動作確認(CloudFront)
2-1.[CloudFront] - ディストリビューションドメイン名確認

2-2.下記をAWS外部から実行し、200OKが返ってくることを確認
> curl https://d15xfm2x3yiw75.cloudfront.net/test/examplepath -L -v
* Trying 143.204.130.97:443...
* Connected to d15xfm2x3yiw75.cloudfront.net (143.204.130.97) port 443
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* using HTTP/1.1
> GET /test/examplepath HTTP/1.1
> Host: d15xfm2x3yiw75.cloudfront.net
> User-Agent: curl/8.4.0
> Accept: */*
>
* schannel: remote party requests renegotiation
* schannel: renegotiating SSL/TLS connection
* schannel: SSL/TLS connection renegotiated
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 20
< Connection: keep-alive
< Date: Thu, 23 Nov 2023 23:40:41 GMT
< x-amzn-RequestId: eed3548c-6b0d-432d-9016-7cfb7b5ea3e7
< x-amz-apigw-id: O4BnAH3xNjMEHog=
< X-Amzn-Trace-Id: Root=1-655fe2f9-37eab6833bcb060519da7ff4
< Via: 1.1 0f1561546531d4bd49ef6c69e8989712.cloudfront.net (CloudFront), 1.1 05aec04162b0fed6e9762cd1edd66a72.cloudfront.net (CloudFront)
< X-Amz-Cf-Pop: SFO5-C3
< X-Cache: Hit from cloudfront
< X-Amz-Cf-Pop: SFO5-C1
< X-Amz-Cf-Id: DWcy_JqNhUQMEn3O-rKtfPbE_qs4R8PDpEjsaFh0a-JHJ7Mp3TQBLw==
< Age: 102
<
"Hello from Lambda!"* Connection
3.動作確認(APIGateway)
3-1.[API Gateway] - 作成したAPIを選択

3-2. [ステージ]

3-3.[GET]を選択し呼び出しURLを確認

3-4.下記をAWS外部から実行し、403 Forbiddenが返ってくることを確認
> curl https://i7yfsdlxxk.execute-api.ap-northeast-1.amazonaws.com/tes
t/examplepath -L -v
* Trying 99.84.133.47:443...
* Connected to i7yfsdlxxk.execute-api.ap-northeast-1.amazonaws.com (99.84.133.47) port 443
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* using HTTP/1.1
> GET /test/examplepath HTTP/1.1
> Host: i7yfsdlxxk.execute-api.ap-northeast-1.amazonaws.com
> User-Agent: curl/8.4.0
> Accept: */*
>
* schannel: remote party requests renegotiation
* schannel: renegotiating SSL/TLS connection
* schannel: SSL/TLS connection renegotiated
< HTTP/1.1 403 Forbidden
< Content-Type: application/json
< Content-Length: 23
< Connection: keep-alive
< Date: Fri, 24 Nov 2023 00:09:38 GMT
< x-amzn-RequestId: 7f69ce70-ae7c-4d44-8a9b-bf4d7d681b47
< x-amzn-ErrorType: ForbiddenException
< x-amz-apigw-id: O4F2aFRQtjMEKBQ=
< X-Cache: Error from cloudfront
< Via: 1.1 591400b2958a6516fdef3d2bc0ac208e.cloudfront.net (CloudFront)
< X-Amz-Cf-Pop: NRT57-C3
< X-Amz-Cf-Id: JbsXNRIGw7dqYD_HoBqzykfb69mwmCA5X_kZXxvNP4wI2hYTL8h2dA==
<
{"message":"Forbidden"}* Connection
感想
簡単な設定なはずなのになかなかに苦戦した・・・( ̄д ̄)ハァ