Skip to content

Response headers policies cannot be created and attached in the same module invocation #186

@stingofung

Description

@stingofung

Description

When defining response_headers_policies inside the module and trying to attach them in the same module invocation, none of the available paths work:

  • Attempt 1 (example-style, commented in docs):
# using a response header policy you're dynamically creating below
# response_header_policy = "cors_policy"
response_header_policy = "my-custom-rhp"

No change is applied; the default cache behavior ends up without the policy attached.

  • Attempt 2 (lookup by name):
response_headers_policy_name = "my-custom-rhp"

Fails unless the policy already exists (managed or pre-created). If the policy is only defined in response_headers_policies in the same module, the plan fails with:
Error: no matching CloudFront Response Headers Policy (my-custom-rhp)

Result: It is impossible to create and attach a custom response headers policy in a single apply using the module alone.

  • ✋ I have searched the open/closed issues and my issue is not listed.

Versions

  • Module version [Required]: 6.0.2

  • Terraform version: Terraform v1.14.0

  • Provider version(s): hashicorp/aws v6.25.0

Reproduction Code [Required]

module "cloudfront_example" {
  source  = "terraform-aws-modules/cloudfront/aws"
  version = "6.0.2"

  comment = "Repro response headers policy"
  enabled = true

  origin = {
    s3 = {
      domain_name           = "example-bucket.s3.amazonaws.com"
      origin_access_control = "s3"
      origin_id             = "s3"
    }
  }

  default_cache_behavior = {
    target_origin_id       = "s3"
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    default_ttl            = 0
    max_ttl                = 0
    min_ttl                = 0

    # Attempt 1 (from docs example, commented): no attachment happens
    # using a response header policy you're dynamically creating below
    response_header_policy = "my-custom-rhp"

    # Attempt 2 (lookup by name): fails unless policy pre-exists
    # response_headers_policy_name = "my-custom-rhp"
  }

  response_headers_policies = {
    my-custom-rhp = {
      comment = "My custom RHP"
      cors_config = {
        access_control_allow_credentials = false
        origin_override                  = true
        access_control_allow_headers     = { items = ["*"] }
        access_control_allow_methods     = { items = ["GET", "HEAD"] }
        access_control_allow_origins     = { items = ["*"] }
        access_control_max_age_sec       = 600
      }
    }
  }

  viewer_certificate = {
    cloudfront_default_certificate = true
  }
}

Expected behavior

  • In a single apply, the module should create the response headers policy defined in response_headers_policies and allow attaching it to the cache behavior without a dependency cycle. Either:
  • response_headers_policy_name should resolve the newly created policy, or
  • response_headers_policy_id should be usable without creating a cycle when referring to the module’s own created policy.

Steps to reproduce:

terraform init
terraform apply

Actual behavior

  • Attempt 1: response_header_policy = "my-custom-rhp" (as in the commented example) results in no attachment.
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
symbols:
  + create

Terraform will perform the following actions:

  # module.cloudfront_example.aws_cloudfront_distribution.this[0] will be created
  + resource "aws_cloudfront_distribution" "this" {
      + arn                             = (known after apply)
      + caller_reference                = (known after apply)
      + comment                         = "Repro response headers policy"
      + continuous_deployment_policy_id = (known after apply)
      + domain_name                     = (known after apply)
      + enabled                         = true
      + etag                            = (known after apply)
      + hosted_zone_id                  = (known after apply)
      + http_version                    = "http2"
      + id                              = (known after apply)
      + in_progress_validation_batches  = (known after apply)
      + is_ipv6_enabled                 = true
      + last_modified_time              = (known after apply)
      + logging_v1_enabled              = (known after apply)
      + price_class                     = "PriceClass_All"
      + retain_on_delete                = false
      + staging                         = false
      + status                          = (known after apply)
      + trusted_key_groups              = (known after apply)
      + trusted_signers                 = (known after apply)
      + wait_for_deployment             = true

      + default_cache_behavior {
          + allowed_methods        = [
              + "GET",
              + "HEAD",
            ]
          + cached_methods         = [
              + "GET",
              + "HEAD",
            ]
          + compress               = true
          + default_ttl            = 0
          + max_ttl                = 0
          + min_ttl                = 0
          + target_origin_id       = "s3"
          + trusted_key_groups     = (known after apply)
          + trusted_signers        = (known after apply)
          + viewer_protocol_policy = "redirect-to-https"

          + forwarded_values {
              + headers                 = (known after apply)
              + query_string            = false
              + query_string_cache_keys = (known after apply)

              + cookies {
                  + forward           = "none"
                  + whitelisted_names = (known after apply)
                }
            }

          + grpc_config (known after apply)
        }

      + origin {
          + connection_attempts         = 3
          + connection_timeout          = 10
          + domain_name                 = "example-bucket.s3.amazonaws.com"
          + origin_id                   = "s3"
          + response_completion_timeout = (known after apply)
            # (2 unchanged attributes hidden)
        }

      + restrictions {
          + geo_restriction {
              + locations        = (known after apply)
              + restriction_type = "none"
            }
        }

      + viewer_certificate {
          + cloudfront_default_certificate = true
          + minimum_protocol_version       = "TLSv1.2_2025"
        }
    }

  # module.cloudfront_example.aws_cloudfront_origin_access_control.this["s3"] will be created
  + resource "aws_cloudfront_origin_access_control" "this" {
      + arn                               = (known after apply)
      + description                       = "Origin Access Control for s3"
      + etag                              = (known after apply)
      + id                                = (known after apply)
      + name                              = "s3"
      + origin_access_control_origin_type = "s3"
      + signing_behavior                  = "always"
      + signing_protocol                  = "sigv4"
    }

  # module.cloudfront_example.aws_cloudfront_response_headers_policy.this["my-custom-rhp"] will be created
  + resource "aws_cloudfront_response_headers_policy" "this" {
      + arn     = (known after apply)
      + comment = "My custom RHP"
      + etag    = (known after apply)
      + id      = (known after apply)
      + name    = "my-custom-rhp"

      + cors_config {
          + access_control_allow_credentials = false
          + access_control_max_age_sec       = 600
          + origin_override                  = true

          + access_control_allow_headers {
              + items = [
                  + "*",
                ]
            }

          + access_control_allow_methods {
              + items = [
                  + "GET",
                  + "HEAD",
                ]
            }

          + access_control_allow_origins {
              + items = [
                  + "*",
                ]
            }
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.
  • Attempt 2: response_headers_policy_name "my-custom-rhp" only works if the policy already exists (managed or pre-created).
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
symbols:
  + create

Terraform planned the following actions, but then encountered a problem:

  # module.cloudfront_example.aws_cloudfront_origin_access_control.this["s3"] will be created
  + resource "aws_cloudfront_origin_access_control" "this" {
      + arn                               = (known after apply)
      + description                       = "Origin Access Control for s3"
      + etag                              = (known after apply)
      + id                                = (known after apply)
      + name                              = "s3"
      + origin_access_control_origin_type = "s3"
      + signing_behavior                  = "always"
      + signing_protocol                  = "sigv4"
    }

  # module.cloudfront_example.aws_cloudfront_origin_access_control.this["s3"] will be created
  + resource "aws_cloudfront_origin_access_control" "this" {
      + arn                               = (known after apply)
      + description                       = "Origin Access Control for s3"
      + etag                              = (known after apply)
      + id                                = (known after apply)
      + name                              = "s3"
      + origin_access_control_origin_type = "s3"
      + signing_behavior                  = "always"
      + signing_protocol                  = "sigv4"
    }

  # module.cloudfront_example.aws_cloudfront_response_headers_policy.this["my-custom-rhp"] will be created
  + resource "aws_cloudfront_response_headers_policy" "this" {
      + arn     = (known after apply)
      + comment = "My custom RHP"
      + etag    = (known after apply)
      + id      = (known after apply)
      + name    = "my-custom-rhp"

      + cors_config {
          + access_control_allow_credentials = false
          + access_control_max_age_sec       = 600
          + origin_override                  = true

          + access_control_allow_headers {
              + items = [
                  + "*",
                ]
            }

          + access_control_allow_methods {
              + items = [
                  + "GET",
                  + "HEAD",
                ]
            }

          + access_control_allow_origins {
              + items = [
                  + "*",
                ]
            }
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.
╷
│ Error: no matching CloudFront Response Headers Policy (my-custom-rhp)
│ 
│   with module.cloudfront_example.data.aws_cloudfront_response_headers_policy.this["my-custom-rhp"],
│   on .terraform/modules/cloudfront_example/main.tf line 553, in data "aws_cloudfront_response_headers_policy" "this":
│  553: data "aws_cloudfront_response_headers_policy" "this" {
│ 
╵

Additional context

  • The module code (main.tf) resolves response_headers_policy_name via data.aws_cloudfront_response_headers_policy.this, which presumes the policy exists beforehand, unlike the example suggestion to create it “dynamically below.”
  • Also, the example comments response_header_policy = "cors_policy", but main.tf does not reference a response_header_policy attribute; only response_headers_policy_id / response_headers_policy_name are wired. This adds to the confusion when trying to create-and-attach in a single apply.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions