CloudFront+APIGatewayの構成で送信元をCloudFrontのみに制限する場合に、APIGatewayにWAFを設定し、カスタムヘッダーで制限するのが一般的らしいのでやってみた。
やること
terraformで下記を実施。
CloudFront+APIGatewayを作り、APIGatewayにWAFをアタッチ
CloudFrontにカスタムヘッダーを設定
WAFにカスタムヘッダーを含まないHTTPリクエストはブロックするルールを設定
構成
実践!
1.環境作成
1-1.下記を実行し、コードを作成
# vi main.tf
provider "aws" { region = "ap-northeast-1" } ## Lambda関数 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 } ## Lambdaロール 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" }, }, ], }) } # WAF 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 } } ## APIGateway 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 # stage_name = "test" } 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}/*/*/*" } # APIGatewayにWAFを関連付け 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 } # CloudFront resource "aws_cloudfront_distribution" "example" { origin { ## domain_name = aws_api_gateway_rest_api.example.domain_name ## domain_name = "${aws_api_gateway_rest_api.example.id}.execute-api.ap-northeast-1.amazonaws.com" 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" # 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実行
# terraform plan # terraform apply
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 #0 to host d15xfm2x3yiw75.cloudfront.net left intact
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 #0 to host i7yfsdlxxk.execute-api.ap-northeast-1.amazonaws.com left intact
感想
簡単な設定なはずなのになかなかに苦戦した・・・( ̄д ̄)ハァ