Terraform配置API Gateway V2对接CloudFront返回301/403问题
问题根因
- 你看到的CloudFront默认301响应页面、API Gateway无访问日志,核心是协议策略触发了边缘节点直接响应:当
viewer_protocol_policy = "redirect-to-https"时,CloudFront收到HTTP请求会直接在边缘节点返回301跳转,根本不会回源到API Gateway,所以后端查不到请求记录;配置为https-only时会直接拒绝HTTP请求返回403,也不会回源。 - API Gateway源域名提取正则写法错误:Terraform的
replace函数使用RE2正则,不需要额外加/作为正则分隔符,你当前写的"/^https?://([^/]*).*/"无法匹配https://开头的invoke_url,导致提取出的源域名带协议前缀,CloudFront源配置无效,HTTPS请求也无法正确转发到后端。 - 回源头转发配置缺失:
/api/*路径的缓存规则仅转发了Origin头,没有转发API Gateway域名校验必需的Host头,即使请求到达后端也会因域名不匹配返回403/301。 - 路径拼接逻辑错误:配置的
origin_path会直接拼接在用户请求路径前转发给API Gateway,当前配置回源路径会变成/serverless_lambda_stage/api/xxx,和后端实际路由不匹配;且默认不会自动去掉/api前缀,后端无法匹配到对应接口。 - 冗余配置冲突:你的需求是把API挂载到主域名的
/api路径下,不需要单独配置api.xxxx.com自定义域名、对应子域名托管区及解析记录,冗余配置可能引发解析或回源冲突。 - 缺少主域名到CloudFront的解析记录:当前配置里没有把
xxxx.com和www.xxxx.com解析到CloudFront分发域名的记录,部分场景下会出现解析异常。
配置调整方案
1. 修正API Gateway源配置
修正正则提取逻辑,确认阶段路径配置正确:
origin { # 去掉正则前后的/分隔符,正确提取纯域名 domain_name = replace(aws_apigatewayv2_stage.lambda.invoke_url, "^https?://([^/]*).*", "$1") origin_id = "apigw" # 直接引用阶段名,避免硬编码路径错误,如果用$default阶段这里填空字符串即可 origin_path = "/${aws_apigatewayv2_stage.lambda.name}" custom_origin_config { http_port = 80 https_port = 443 origin_protocol_policy = "https-only" origin_ssl_protocols = ["TLSv1.2"] } }
2. 调整/api路径缓存行为
补全必要头转发,修改协议策略,增加路径改写逻辑去掉/api前缀:
ordered_cache_behavior { path_pattern = "/api/*" allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] cached_methods = ["GET", "HEAD"] target_origin_id = "apigw" default_ttl = 0 min_ttl = 0 max_ttl = 0 forwarded_values { query_string = true # 补全API Gateway校验必需的Host头,以及后端识别请求需要的其他头 headers = ["Origin", "Host", "X-Forwarded-Proto", "X-Forwarded-For", "Authorization", "Accept"] cookies { forward = "all" } } # 改为redirect-to-https,实现HTTP自动跳HTTPS,不会直接返回403 viewer_protocol_policy = "redirect-to-https" # 关联Lambda@Edge在回源前去掉/api前缀 lambda_function_association { event_type = "origin-request" lambda_arn = aws_lambda_function.cf_path_rewrite.qualified_arn include_body = false } }
3. 添加路径改写Lambda@Edge
首先新增Lambda执行角色,允许CloudFront调用:
resource "aws_iam_role" "lambda_edge_role" { name = "lambda-edge-cf-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] } } ] }) } # 给角色加Lambda基础执行权限即可 resource "aws_iam_role_policy_attachment" "lambda_edge_basic" { role = aws_iam_role.lambda_edge_role.name policy_arn = "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" }
然后创建路径改写函数,把打包好的代码(index.js)上传:
resource "aws_lambda_function" "cf_path_rewrite" { filename = "path_rewrite.zip" function_name = "cf-path-rewrite" role = aws_iam_role.lambda_edge_role.arn handler = "index.handler" runtime = "nodejs18.x" publish = true # Lambda@Edge必须发布版本才能被CloudFront关联 }
对应的index.js代码逻辑很简单:
exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; // 去掉URI开头的/api前缀 request.uri = request.uri.replace(/^\/api/, '') || '/'; callback(null, request); };
4. 清理冗余配置、补全解析记录
删除以下和当前需求无关的资源,避免冲突:
aws_apigatewayv2_domain_name下配置的api.xxxx.com自定义域名资源aws_route53_zone.api_main子域名托管区aws_route53_record.api-ns子域名NS记录aws_route53_record.api_picturethisapi子域名指向API Gateway的A记录
补全主域名和www子域名到CloudFront的别名解析:
resource "aws_route53_record" "main_cloudfront" { zone_id = aws_route53_zone.main.zone_id name = var.site_domain type = "A" alias { name = aws_cloudfront_distribution.dist.domain_name zone_id = aws_cloudfront_distribution.dist.hosted_zone_id evaluate_target_health = false } } resource "aws_route53_record" "www_cloudfront" { zone_id = aws_route53_zone.main.zone_id name = "www.${var.site_domain}" type = "A" alias { name = aws_cloudfront_distribution.dist.domain_name zone_id = aws_cloudfront_distribution.dist.hosted_zone_id evaluate_target_health = false } }
验证步骤
- 执行
terraform plan确认变更无误后应用配置 - 等待CloudFront部署完成(通常5-15分钟)
- 先测试HTTPS接口:
curl -v https://xxxx.com/api/你的接口路径,确认返回正常响应,API Gateway日志可查到请求记录 - 测试HTTP跳转:
curl -v http://xxxx.com/api/你的接口路径,确认返回301跳转到对应HTTPS地址
如果不想用Lambda@Edge做路径改写,也可以给API Gateway的所有路由统一加上/api前缀,去掉Lambda关联配置即可,只是后续接口维护成本更高。
内容的提问来源于stack exchange,提问作者Conor
相关产品推荐
相关产品推荐

