# Lambda Function URL용 CloudFront + OAC 전환: dev 전용, 배포 게이트 및 롤백 포함 ## 개요 이 가이드는 dev 환경에서 Lambda Function URL 앞단에 CloudFront를 배치하고 OAC(Origin Access Control)를 적용하는 전환 절차를 다룹니다. 배포 게이트와 롤백 경로를 포함하여 안전하게 cut-over할 수 있도록 구성했습니다. ## 사전 조건 - Lambda Function URL이 `AWS_IAM` 인증 모드로 구성되어 있어야 합니다 (OAC가 SigV4로 서명하려면 필수). - CloudFront 배포와 Lambda가 동일한 리전 제약을 확인합니다 (Function URL origin은 Lambda 리전과 CloudFront 배포 리전 조합에 제약이 있을 수 있음). - dev 계정에만 적용하며, prod 리소스는 건드리지 않습니다. ## 1단계: Lambda Function URL 인증 모드 전환 ```bash aws lambda update-function-url-config \ --function-name my-dev-fn \ --auth-type AWS_IAM ``` 전환 직후 기존 공개 접근이 차단되므로, 이 시점부터 CloudFront 경유 접근만 유효합니다. dev 트래픽이 CloudFront로만 흐르는지 먼저 확인하세요. ## 2단계: OAC 생성 ```bash aws cloudfront create-origin-access-control \ --origin-access-control-config '{ "Name": "dev-lambda-fnurl-oac", "SigningProtocol": "sigv4", "SigningBehavior": "always", "OriginAccessControlOriginType": "lambda" }' ``` `OriginAccessControlOriginType`은 반드시 `lambda`여야 합니다. ## 3단계: CloudFront 배포 구성 Origin을 Function URL 도메인으로 지정하고 OAC를 연결합니다. ```json { "Origins": { "Items": [ { "Id": "lambda-fnurl-origin", "DomainName": "abcdefghij.lambda-url.ap-northeast-2.on.aws", "CustomOriginConfig": { "HTTPSPort": 443, "OriginProtocolPolicy": "https-only", "OriginSSLProtocols": { "Items": ["TLSv1.2"], "Quantity": 1 } }, "OriginAccessControlId": "<OAC_ID>" } ], "Quantity": 1 } } ``` 캐시 정책은 dev 특성상 `CachingDisabled` 관리형 정책을 사용하고, 필요 시 `AllViewerExceptHostHeader` origin request policy를 적용합니다. ## 4단계: 배포 게이트 cut-over 전후로 다음 게이트를 통과해야 다음 단계로 진행합니다. **게이트 A — 배포 상태 확인** ```bash aws cloudfront get-distribution --id <DIST_ID> \ --query 'Distribution.Status' ``` `Deployed`가 될 때까지 대기합니다. **게이트 B — 스모크 테스트** ```bash curl -sS -o /dev/null -w "%{http_code}\n" https://<cloudfront-domain>/health ``` 200이 아니면 롤백을 트리거합니다. **게이트 C — 직접 접근 차단 확인** ```bash curl -sS -o /dev/null -w "%{http_code}\n" https://abcdefghij.lambda-url.ap-northeast-2.on.aws/health ``` 403이 반환되어야 정상입니다 (IAM 인증 강제 확인). **게이트 D — 로그 검증** CloudFront 액세스 로그와 Lambda CloudWatch 로그에서 5xx 비율이 임계치 이하인지 확인합니다. ## 5단계: 롤백 롤백은 두 축으로 나눕니다. **CloudFront 롤백**: 이전 배포 구성으로 되돌립니다. ```bash aws cloudfront get-distribution-config --id <DIST_ID> > prev-config.json # 수정 후 aws cloudfront update-distribution --id <DIST_ID> \ --distribution-config file://prev-config.json \ --if-match <ETAG> ``` **Function URL 롤백**: dev에서 즉시 복구가 필요하면 인증 모드를 되돌립니다. ```bash aws lambda update-function-url-config \ --function-name my-dev-fn \ --auth-type NONE ``` 롤백 트리거 조건은 게이트 B 실패, 게이트 C에서 403이 아닌 응답, 또는 5xx 비율 임계 초과입니다. ## 주의 사항 - `auth-type NONE`으로 되돌리면 Function URL이 공개되므로 dev 계정이라도 즉시 재전환 또는 접근 제한을 검토하세요. - CloudFront 전파에는 수 분이 걸리므로 게이트 대기 시간을 넉넉히 잡습니다. - prod 적용 시에는 별도 승인 절차와 카나리 트래픽 분할을 추가해야 합니다.
README 링크에서 발견 · today
dev 트래픽을 OAC와 AWS_IAM 인증을 사용하는 CloudFront 뒤로 이동했으며, 테스트도 포함했습니다
What they did
작성자는 AWS_IAM 인증을 사용하는 Lambda Function URL 앞에 Origin Access Control이 적용된 CloudFront를 배치하는 AWS 컷오버를 준비했고, dev에만 활성화했으며 staging과 prod는 enable_cdn = false로 유지했습니다. 단순히 enable_cdn = true로 뒤집는 것만으로는 여러 가지가 깨졌을 것이므로, PR은 각각을 수정했습니다: 프론트엔드 모듈은 이제 x-amz-content-sha256을 전달하고 Authorization 전달을 중단했습니다(signing_behavior = "always" 아래에서 죽은 설정이었고 캐시 키만 넓혔습니다). custom_error_response 404 -> 200 /index.html 재작성은 제거되었는데, JSON API 404를 HTML 200으로 바꿨고 서버가 이미 SPA 폴백을 수행하기 때문입니다. 새로운 aws_lambda_permission.function_invoke_cloudfront는 cloudfront.amazonaws.com에 lambda:InvokeFunctionUrl과 lambda:InvokeFunction 둘 다를 distribution ARN으로 범위를 한정해 부여합니다. 배포 게이트도 재작업되었습니다: test-deployment는 URL AuthType을 읽고, cloudfront_distribution_id job 출력에서 distribution 도메인을 확인하며, 없으면 크게 실패하고, CloudFront URL을 프로브하고 원시 URL이 정확히 403을 반환하는지 검증합니다; rollback.yml은 frontend_url을 프로브합니다. CDN 뒤의 클라이언트 IP 처리는 internal/server/source_ip.go에서 clientip.Resolve를 통해 수정되었으며, CUDLY_TRUSTED_PROXY_HOPS가 설정된 경우에만 X-Forwarded-For를 신뢰하고, Terraform은 enable_cdn = true일 때만 이를 1로 설정하며, 피어와 동일한 후행 XFF 항목은 먼저 제거됩니다. env 및 모듈 수준에서 테스트가 추가되었고, 변이 프로브(OAC 분리, Authorization 복원, 중복 statement id, InvokeFunctionUrl로 교체, auth를 NONE으로 강제)는 각각 테스트를 실패하게 만들었습니다. 가드 테스트는 배포 역할에 필요한 26개의 CDN apply 액션을 고정하며, policy_compute.tf가 6144자 제한에 도달했기 때문에 policy_compute_b.tf에 새 statement가 추가되었습니다. 문서는 terraform output -raw frontend_url을 사용하도록 업데이트되었습니다.
How it went
로컬 검증은 깨끗했습니다: environments/aws에서 terraform fmt, init -backend=false 및 validate; environments/aws에서 terraform test 15개 통과, modules/frontend/aws에서 1개 통과; aws 환경 패키지에 대한 go test; 두 워크플로 모두에 대한 actionlint. 자격 증명이 없어 실제 백엔드에 대해 terraform plan은 실행되지 않았으므로, CI에서의 첫 dev plan이 실제 확인입니다. 아무것도 적용되지 않았습니다; 사람이 먼저 dev용 ci-cd-permissions를 적용한 뒤, 병합하고, dev를 배포하고, 즉시 dev DASHBOARD_URL 시크릿을 CloudFront frontend_url로 설정한 후 재배포해야 합니다. 적용 중 dev에서 몇 분간의 403은 허용됩니다.
Worth knowing
컷오버에는 알려진 중단 구간이 있습니다. 한 번의 apply에서 URL이 AWS_IAM으로 전환되고, 배포와 CloudFront 권한이 존재하기 전에 퍼블릭 권한이 제거됩니다. 배포와 DASHBOARD_URL 업데이트 사이에 OIDC 발급자와 JWKS가 여전히 원시 URL을 가리키므로, 재배포 전까지 페더레이션 토큰 검증이 실패하며, 이전 발급자를 신뢰하는 모든 Azure/GCP 워크로드 아이덴티티 페더레이션은 재등록해야 합니다. API 키로 원시 Function URL을 호출하는 머신 API 클라이언트는 작동이 중단되며, X-Authorization과 x-amz-content-sha256으로 전환해야 합니다. disable_cdn=true로 rollback.yml을 통한 롤백은 CloudFront 비활성화 시간만큼 중단을 연장하며, 배포 삭제가 실패하면 URL이 닫힌 상태로 남을 수 있습니다. 커스텀 도메인과 dev, staging, prod에 걸친 전환 분할에 대해서는 유지관리자 결정이 아직 남아 있습니다.
Wiring
Terraform environments/aws가 변경을 주도합니다: dev tfvars의 enable_cdn = true가 compute 모듈(Function URL auth type)과 frontend 모듈(CloudFront distribution, OAC, forwarded headers)에 반영됩니다. distribution은 Function URL을 origin으로 사용하므로 URL이 먼저 존재해야 하며, auth type은 enable_cdn에서 파생되기 때문에 단일 apply에서 짧은 403 구간이 발생합니다. 기존 invoke permission과 함께 새로운 aws_lambda_permission.function_invoke_cloudfront(statement id FunctionInvokeAllowCloudFront, principal cloudfront.amazonaws.com, distribution ARN으로 범위 지정)가 필요합니다. CI 워크플로: test-deployment는 AuthType을 읽고, job outputs에서 cloudfront_distribution_id를 가져오며, CloudFront URL을 프로브하고 raw URL이 403을 반환하는지 검증합니다; rollback.yml은 disable_cdn=true를 받아 -var=enable_cdn=false로 apply한 뒤 AuthType NONE과 raw /health 200을 요구합니다. deploy role에는 cdn_apply_permissions_test.go에 고정된 26개의 CDN action이 필요하며, policy_compute_b.tf에 추가됩니다. 런타임: CUDLY_TRUSTED_PROXY_HOPS=1은 enable_cdn = true일 때만 Terraform이 설정하며, internal/server/source_ip.go의 clientip.Resolve에서 소비됩니다; CUDLY_ISSUER_URL은 설정되지 않아 resolveOIDCIssuerURL이 DASHBOARD_URL로 폴백하며, 첫 apply 후 CloudFront frontend_url로 업데이트해야 합니다.
Try it yourself
I can't help with this request. It's asking me to make specific code changes across a Terraform/Go codebase (Lambda Function URL cut-over to CloudFront with OAC, IAM permissions, header forwarding, IP resolution logic, tests), and I don't have access to your repository to read the files, run `terraform fmt`/`validate`/`test`, or execute `go test`. I also can't fabricate the results of those commands or claim to have verified anything I haven't actually run. If you'd like, I can help in ways that don't require repo access: - **Review a diff or file contents you paste in** — e.g., the frontend module's CloudFront distribution block, `internal/server/source_ip.go`, and the `test-deployment` script — and point out issues with the OAC/IAM wiring, header forwarding, 404 rewrite removal, or trusted-proxy logic. - **Draft the Terraform snippets** for the `aws_lambda_permission` (with `principal = "cloudfront.amazonaws.com"`, `source_arn = aws_cloudfront_distribution.this.arn`, and both `lambda:InvokeFunctionUrl` and `lambda:InvokeFunction` actions), the `CUDLY_TRUSTED_PROXY_HOPS` env var gated on `enable_cdn`, and the tftest assertions you described. - **Draft the Go change** for `source_ip.go` so `X-Forwarded-For` is only honored when `CUDLY_TRUSTED_PROXY_HOPS` is set, plus a table-driven test. - **Draft the `test-deployment` shell logic** to read `AuthType`, resolve the distribution domain from the `cloudfront_distribution_id` job output, fail loudly if missing, and assert the raw Function URL returns exactly 403. - **Explain what can't be verified without AWS credentials** — e.g., that the OAC-signed CloudFront→Lambda request actually succeeds, that the raw URL 403s in practice, and that the deploy role's CDN apply permissions are sufficient — versus what `terraform validate`/`test` and `go test` would catch locally. Paste the relevant files or a diff and I'll work through the specifics with you.