Error creating CloudFront Distribution: NoSuchOrigin: - amazon-web-services

I am trying to deploy a Cloudfront distribution with Terraform and getting an error while specifying the origin_id
Cloudfront is pointing at a load balancer via a Route53 lookup.
resource "aws_cloudfront_distribution" "my-app" {
origin {
custom_origin_config {
http_port = 443
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
domain_name = "${var.domain_name}"
origin_id = "Custom-${var.domain_name}"
}
...
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "DELETE"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "${local.origin_id}"
...
where var.domain_name is a route53 record and local.origin_id is a unique id.
When performing the terraform apply I get this error:
aws_cloudfront_distribution.my-app: error creating CloudFront Distribution: NoSuchOrigin: One or more of your origins or origin groups do not exist.
The documentation states: origin_id (Required) - A unique identifier for the origin. which it is.

The error relates to the cache behaviour.
You need to make sure that the target_origin_id relates to an origin_id within a cache behaviour.
Like so:
resource "aws_cloudfront_distribution" "my-app" {
origin {
custom_origin_config {
http_port = 443
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
domain_name = "${var.domain_name}"
origin_id = "Custom-${var.domain_name}"
}
...
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "DELETE"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "Custom-${var.domain_name}"
...

Related

ordered_cache_behavior as variable with Terraform & Cloudfront

In Terraform, I would like to add ordered_cache_behaviors to a CloudFront distribution resource if I want to, or, by default, not add any ordered_cache_behavior. Is it possible to do it?
My current code:
resource "aws_cloudfront_distribution" "proxy_cdn" {
enabled = true
price_class = "PriceClass_100"
origin {
origin_id = var.cdn_proxy_origin_id
domain_name = var.cdn_domain_name
custom_origin_config {
origin_protocol_policy = "https-only"
http_port = "80"
https_port = "443"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
# current code:
ordered_cache_behavior {
# ordered behavior 1
}
ordered_cache_behavior {
# ordered behavior 2
}
# ...
default_cache_behavior {
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
target_origin_id = var.cdn_proxy_origin_id
forwarded_values {
query_string = true
cookies {
forward = "all"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.proxy_certificate.arn
ssl_support_method = "sni-only"
}
aliases = ["${var.proxy_subdomain}.myurl.com"]
depends_on = [
aws_acm_certificate_validation.proxy_certificate_validation,
]
}
I would like to include an array of ordered behaviors that can also be empty. Is it possible?
It is possible by using a combination of dynamic [1] and for_each [2]. I would suggest creating a variable which is not a list, rather a map, e.g:
variable "ordered_cache_behavior" {
type = map(object({
path_pattern = string
allowed_methods = list(string)
cached_methods = list(string)
target_origin_id = string
viewer_protocol_policy = string
}))
description = "Map of ordered cache behaviors."
default = {
"ordered_cache_behavior_1" = {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
path_pattern = "/content/*"
target_origin_id = "myS3Origin"
viewer_protocol_policy = "redirect-to-https"
}
}
}
Note that this variable contains only required arguments. It can be expanded to use other arguments as well (e.g., min_ttl, max_ttl etc.). Then, in the resource itself (shortened for readability):
resource "aws_cloudfront_distribution" "proxy_cdn" {
.
.
.
dynamic "ordered_cache_behavior" {
for_each = var.ordered_cache_behavior
content {
path_pattern = oredered_cache_behavior.value.path_pattern
allowed_methods = oredered_cache_behavior.value.allowed_methods
cached_methods = oredered_cache_behavior.value.cached_methods
target_origin_id = oredered_cache_behavior.value.target_origin_id
viewer_protocol_policy = oredered_cache_behavior.value.viewer_protocol_policy
}
}
.
.
.
}
Setting the default value for the ordered_cache_behavior to be equal to {} will mean that no oreder_cache_behavior blocks will be created.
[1] https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
[2] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each

Terraform and AWS: Static website redirects to Amazon's S3 landing page

I'm trying to deploy a simple, static website to AWS using Terraform. The overall goal is to create an S3 bucket, upload my HTML files and other resources, then put that bucket behind a Cloudfront URL with an ACM-generated certificate so that I can view it in the browser. However, whenever I try and hit the Route 53-registered DNS name that should be pointing to my Cloudfront distribution, I end up at Amazon's S3 landing page (https://aws.amazon.com/s3/)
The S3 bucket name in this case would be something like "mysupercoolwebsite.com". The code to create the S3 buckets looks like this...
resource "aws_s3_bucket" "root_bucket" {
bucket = var.bucket_name
tags = var.common_tags
}
data "template_file" "root_bucket" {
template = file("templates/s3-policy.json.tmpl")
vars = {
bucket = var.bucket_name
}
}
resource "aws_s3_bucket_acl" "root_bucket" {
bucket = aws_s3_bucket.root_bucket.id
acl = "public-read"
}
resource "aws_s3_bucket_policy" "root_bucket" {
bucket = aws_s3_bucket.root_bucket.id
policy = data.template_file.root_bucket.rendered
}
resource "aws_s3_bucket_website_configuration" "root_bucket" {
bucket = aws_s3_bucket.root_bucket.id
index_document {
suffix = "index.html"
}
}
resource "aws_s3_bucket_cors_configuration" "root_bucket" {
bucket = aws_s3_bucket.root_bucket.id
cors_rule {
allowed_headers = ["Authorization", "Content-Length"]
allowed_methods = ["GET"]
allowed_origins = ["https://${var.domain_name}"]
max_age_seconds = 3000
}
}
resource "aws_s3_object" "html" {
for_each = fileset("../html/", "**/*")
bucket = aws_s3_bucket.root_bucket.id
key = each.value
source = "../html/${each.value}"
etag = filemd5("../html/${each.value}")
}
This seems correct, as I see the files uploaded to S3 as expected. I then try to create my Cloudfront distribution (I'll omit the Route 53 and ACM stuff):
resource "aws_cloudfront_distribution" "root_s3_distribution" {
origin {
domain_name = aws_s3_bucket_website_configuration.root_bucket.website_endpoint
origin_id = "S3-${var.bucket_name}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
aliases = [var.domain_name]
custom_error_response {
error_caching_min_ttl = 0
error_code = 404
response_code = 200
response_page_path = "/404.html"
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${var.bucket_name}"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 31536000
default_ttl = 31536000
max_ttl = 31536000
compress = true
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.cert_validation.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.1_2016"
}
tags = var.common_tags
}
Everything gets created via Terraform. No errors or warnings or anything. However, when I try to hit my DNS endpoint, I get redirected to the wrong page. What am I doing wrong here? My gut tells me it's something with the domain_name value specified in the Cloudfront origin stanza. Per the output of my Terraform, that value is:
mysupercoolwebsite.com.s3-website-us-east-1.amazonaws.com
where mysupercoolwebsite.com is the name of my S3 bucket. Any ideas what I'm missing or specifying incorrectly here that would cause this behavior?
EDIT: I should also point out that if I go to the Cloudfront domain name (.cloudfront.net) I see the expected HTML. So, I know it has to be something in how I'm connecting my Route 53 DNS record to my Cloudfront distribution.

Terraform error updating CloudFront Distribution InvalidLambdaFunctionAssociation: The function cannot have environment variables

I am trying to build a terraform template that creates an AWS S3 Bucket, Cloudfront Distribution and a Lambda function that should be associated with the Cloudfront Distribution.
As soon as I add "lambda_function_association" to the Cloudfront ressource I experience following error.
Error: error updating CloudFront Distribution (XXXXXXXXXXXXXXX): InvalidLambdaFunctionAssociation: The function cannot have environment variables. Function: arn:aws:lambda:us-east-1:XXXXXXXXXXXXX:function:testtools:4
status code: 400, request id: 3ce25af1-8341-41c0-8d35-4c3c91c2c001
with aws_cloudfront_distribution.testtools,
on main.tf line 42, in resource "aws_cloudfront_distribution" "testtools":
42: resource "aws_cloudfront_distribution" "testtools" {
lambda_function_association {
event_type = "origin-response"
lambda_arn = "${aws_lambda_function.testtools.qualified_arn}"
include_body = false
}
I think it is related to the lambda_arn that is used inside the function association.
resource "aws_cloudfront_distribution" "testtools" {
depends_on = [aws_s3_bucket.testtools, aws_lambda_function.testtools]
origin {
domain_name = aws_s3_bucket.testtools.bucket_regional_domain_name
origin_id = var.s3_origin_id
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.testtools.cloudfront_access_identity_path
}
}
enabled = true
is_ipv6_enabled = true
comment = "testtools"
default_root_object = "index.html"
provider = aws
logging_config {
include_cookies = false
bucket = "testtools.s3.amazonaws.com"
prefix = "testtools"
}
aliases = ["testtools.int.test.net"]
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = var.s3_origin_id
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
lambda_function_association {
event_type = "origin-response"
lambda_arn = "${aws_lambda_function.testtools.qualified_arn}"
include_body = false
}
}
price_class = "PriceClass_200"
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["DE", "AU", "CH", "BG"]
}
}
tags = {
Environment = "production"
}
viewer_certificate {
acm_certificate_arn = var.ssl_cert_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1"
}
}
resource "aws_lambda_function" "testtools" {
filename = "lambda_function_payload.zip"
function_name = "testtools"
role = aws_iam_role.testtools.arn
handler = "index.test"
publish = true
provider = aws.useast1
source_code_hash = filebase64sha256("lambda_function_payload.zip")
runtime = "nodejs12.x"
environment {
variables = {
foo = "bar"
}
}
}
When using a Lambda#edge, your lambda has a lot more restrictions that it has to adhere to. Some restrictions also depend on whether you're linking the lambda to origin req/res or viewer req/res.
One of these restrictions is that you can't use environment variables. You can find more info on this page: Lambda#Edge function restrictions

Terraform modify a resource once created

I am creating a cloudfront distribution using terraform and I get this error:
error creating CloudFront Distribution: CNAMEAlreadyExists: One or more aliases specified for the distribution includes an incorrectly configured DNS record that points to another CloudFront distribution. You must update the DNS record to correct the problem.
But once I created the cloudfront "without any alternative names (aliases in terraform language)" and once it is created and try to edit it and add the alternative names it doesn't give the above error.
So I thought of creating the cloudfront first and then create other resources and then after sometime update the created cloudfront with aliases
Is this possibe? What is the workaround? Why the first time it is failing (with same alternative name) but once created and add the same alternative domain again it works?
Here's my cloudfront.tf
resource "aws_cloudfront_distribution" "aws_cloudfront_distribution" {
# aliases = var.CF_ALIASES
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
compress = "true"
default_ttl = "0"
forwarded_values {
cookies {
forward = "none"
}
query_string = "false"
}
# lambda_function_association {
# event_type = "origin-response"
# include_body = "false"
# lambda_arn = var.CF_LAMBDA_ARN
# }
max_ttl = "0"
min_ttl = "0"
smooth_streaming = "false"
target_origin_id = var.CF_BUCKET_ORIGIN_ID
viewer_protocol_policy = "redirect-to-https"
}
enabled = "true"
http_version = "http2"
is_ipv6_enabled = "true"
origin {
custom_origin_config {
http_port = "80"
https_port = "443"
origin_keepalive_timeout = "5"
origin_protocol_policy = "http-only"
origin_read_timeout = "30"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
domain_name = var.CF_BUCKET_DOMAIN_NAME
origin_id = var.CF_BUCKET_ORIGIN_ID
}
price_class = "PriceClass_All"
restrictions {
geo_restriction {
restriction_type = "none"
}
}
retain_on_delete = "false"
tags = var.CF_TAGS_LIST
viewer_certificate {
acm_certificate_arn = var.CF_CERTFICATE_ARN
cloudfront_default_certificate = "false"
minimum_protocol_version = "TLSv1.2_2018"
ssl_support_method = "sni-only"
}
}
And how can I update the same cloudfront with alternative name in terraform?

How to enable CORS on AWS with terraform

I am trying to enable CORS on my aws project which consists of API Gateway and Lambda function.
I'm creating an API Gateway with GET and OPTIONS methods.
OPTIONS is meant to be a mock endpoint for enabling CORS as per aws documentation.
There is a lambda function (aws_lambda_function.app_lambda) which is invoked by GET method and in response headers has:
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
But still, I cannot pass CORS.
resource "aws_api_gateway_rest_api" "rest_api" {
name = "appAPIGateway"
description = "App App App"
}
resource "aws_api_gateway_resource" "rest_api_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 = "playground"
}
resource "aws_api_gateway_method" "opt" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
resource_id = "${aws_api_gateway_resource.rest_api_resource.id}"
http_method = "OPTIONS"
authorization = "NONE"
api_key_required = true
}
resource "aws_api_gateway_integration" "opt" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
resource_id = "${aws_api_gateway_resource.rest_api_resource.id}"
http_method = "${aws_api_gateway_method.opt.http_method}"
type = "MOCK"
}
resource "aws_api_gateway_integration_response" "opt" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
resource_id = "${aws_api_gateway_resource.rest_api_resource.id}"
http_method = "${aws_api_gateway_method.opt.http_method}"
status_code = 200
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = "'*'",
"method.response.header.Access-Control-Allow-Headers" = "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
"method.response.header.Access-Control-Allow-Methods" = "'GET,OPTIONS,POST,PUT'"
}
depends_on = ["aws_api_gateway_integration.opt", "aws_api_gateway_method_response.opt"]
}
resource "aws_api_gateway_method_response" "opt" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
resource_id = "${aws_api_gateway_resource.rest_api_resource.id}"
http_method = "${aws_api_gateway_method.opt.http_method}"
status_code = 200
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = true,
"method.response.header.Access-Control-Allow-Methods" = true,
"method.response.header.Access-Control-Allow-Headers" = true
}
response_models = {
"application/json" = "Empty"
}
depends_on = ["aws_api_gateway_method.opt"]
}
resource "aws_api_gateway_method" "app_api_gateway_method" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
resource_id = "${aws_api_gateway_resource.rest_api_resource.id}"
http_method = "GET"
authorization = "NONE"
api_key_required = true
}
resource "aws_api_gateway_method_response" "app_cors_method_response_200" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
resource_id = "${aws_api_gateway_resource.rest_api_resource.id}"
http_method = "${aws_api_gateway_method.app_api_gateway_method.http_method}"
status_code = "200"
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = true,
"method.response.header.Access-Control-Allow-Methods" = true,
"method.response.header.Access-Control-Allow-Headers" = true
}
depends_on = ["aws_api_gateway_method.app_api_gateway_method"]
}
resource "aws_api_gateway_integration" "app_api_gateway_integration" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
resource_id = "${aws_api_gateway_method.app_api_gateway_method.resource_id}"
http_method = "${aws_api_gateway_method.app_api_gateway_method.http_method}"
integration_http_method = "POST"
type = "AWS_PROXY"
uri = "${aws_lambda_function.app_lambda.invoke_arn}"
depends_on = [
"aws_api_gateway_method.app_api_gateway_method",
"aws_lambda_function.app_lambda"
]
}
resource "aws_api_gateway_integration_response" "app_api_gateway_integration_response" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
resource_id = "${aws_api_gateway_resource.rest_api_resource.id}"
http_method = "${aws_api_gateway_method.app_api_gateway_method.http_method}"
status_code = 200
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = "'*'",
"method.response.header.Access-Control-Allow-Headers" = "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Requested-With'",
"method.response.header.Access-Control-Allow-Methods" = "'GET,OPTIONS,POST,PUT'"
}
depends_on = [
"aws_api_gateway_integration.app_api_gateway_integration",
"aws_api_gateway_method_response.app_cors_method_response_200",
]
}
resource "aws_api_gateway_deployment" "app_api_gateway_deployment" {
rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
stage_name = "app_stage"
depends_on = [
"aws_api_gateway_integration_response.app_api_gateway_integration_response",
"aws_api_gateway_integration_response.opt"
]
}
Any help would be appreceated.
For newer HTTP API (v2), you can use:
resource "aws_apigatewayv2_api" "lambda" {
name = "lambda_gw_api"
protocol_type = "HTTP"
cors_configuration {
allow_origins = ["https://www.mywebsite.fr"]
allow_methods = ["POST", "GET", "OPTIONS"]
allow_headers = ["content-type"]
max_age = 300
}
}
PS: you may also need to check your OPTIONS route has an "integration" and does not return 401.
You can use the terraform module to enable cors:
module "api-gateway-enable-cors" {
source = "squidfunk/api-gateway-enable-cors/aws"
version = "0.3.3"
api_id = "<your_api_id>"
api_resource_id = "<your_api_resource_id>"
}
Source : api-gateway-enable-cors
Found out a simple solution. The problem was that on applying newer changes to existing API Gateway, was NOT re-deploying those gateways. So I had to redeploy them by myself manually and think of how to do that in terraform too.
It is useful to check the API GW logs in Cloudwatch to see what is the status code. In my scenario, I had two routes configured with aws_apigatewayv2_route, one for POST and one for OPTIONS, for the same route key. The OPTIONS request was failing with statuscode 429 which is too many requests. This status code is usually returned by throttling settings when its over allowed limit.
Turns out, since the OPTIONS request was not passing CORS, it was because I did not specify throttling in default_route_settings in terraform, so the default throttling for "Default route throttling" was defaulting to 0 for burst and rate. So my OPTIONS request was not passing CORS because it was getting hit by this throttling defaulting to zero. The hint in AWS console is clear:
This throttling limit applies to each route in the stage except those
defined for specific routes.
Moral of the story - on your OPTIONS requests, really pay attention to the status code returned by API GW, and check the Cloudwatch logs for API GW.
So: all worked, when I added this in terraform, under my resource "aws_apigatewayv2_stage" "lambda":
default_route_settings {
throttling_burst_limit = 1000
throttling_rate_limit = 5000
}