Skip to content

Commit 8488553

Browse files
feat: Add cloudfront response header policy support (#174)
Co-authored-by: Bryant Biggs <bryantbiggs@gmail.com>
1 parent fc1010c commit 8488553

File tree

11 files changed

+310
-8
lines changed

11 files changed

+310
-8
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/antonbabenko/pre-commit-terraform
3-
rev: v1.103.0
3+
rev: v1.104.0
44
hooks:
55
- id: terraform_fmt
66
- id: terraform_wrapper_module_for_each

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,13 @@ ordered_cache_behavior = [{
107107
| Name | Version |
108108
|------|---------|
109109
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
110-
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.83 |
110+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.100 |
111111

112112
## Providers
113113

114114
| Name | Version |
115115
|------|---------|
116-
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.83 |
116+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.100 |
117117

118118
## Modules
119119

@@ -127,6 +127,7 @@ No modules.
127127
| [aws_cloudfront_monitoring_subscription.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_monitoring_subscription) | resource |
128128
| [aws_cloudfront_origin_access_control.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control) | resource |
129129
| [aws_cloudfront_origin_access_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_identity) | resource |
130+
| [aws_cloudfront_response_headers_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_response_headers_policy) | resource |
130131
| [aws_cloudfront_vpc_origin.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_vpc_origin) | resource |
131132
| [aws_cloudfront_cache_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_cache_policy) | data source |
132133
| [aws_cloudfront_origin_request_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_origin_request_policy) | data source |
@@ -143,6 +144,7 @@ No modules.
143144
| <a name="input_create_monitoring_subscription"></a> [create\_monitoring\_subscription](#input\_create\_monitoring\_subscription) | If enabled, the resource for monitoring subscription will created. | `bool` | `false` | no |
144145
| <a name="input_create_origin_access_control"></a> [create\_origin\_access\_control](#input\_create\_origin\_access\_control) | Controls if CloudFront origin access control should be created | `bool` | `false` | no |
145146
| <a name="input_create_origin_access_identity"></a> [create\_origin\_access\_identity](#input\_create\_origin\_access\_identity) | Controls if CloudFront origin access identity should be created | `bool` | `false` | no |
147+
| <a name="input_create_response_headers_policy"></a> [create\_response\_headers\_policy](#input\_create\_response\_headers\_policy) | Controls if CloudFront response headers policies should be created | `bool` | `false` | no |
146148
| <a name="input_create_vpc_origin"></a> [create\_vpc\_origin](#input\_create\_vpc\_origin) | If enabled, the resource for VPC origin will be created. | `bool` | `false` | no |
147149
| <a name="input_custom_error_response"></a> [custom\_error\_response](#input\_custom\_error\_response) | One or more custom error response elements | `any` | `{}` | no |
148150
| <a name="input_default_cache_behavior"></a> [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution | `any` | `null` | no |
@@ -159,6 +161,7 @@ No modules.
159161
| <a name="input_origin_group"></a> [origin\_group](#input\_origin\_group) | One or more origin\_group for this distribution (multiples allowed). | `any` | `{}` | no |
160162
| <a name="input_price_class"></a> [price\_class](#input\_price\_class) | The price class for this distribution. One of PriceClass\_All, PriceClass\_200, PriceClass\_100 | `string` | `null` | no |
161163
| <a name="input_realtime_metrics_subscription_status"></a> [realtime\_metrics\_subscription\_status](#input\_realtime\_metrics\_subscription\_status) | A flag that indicates whether additional CloudWatch metrics are enabled for a given CloudFront distribution. Valid values are `Enabled` and `Disabled`. | `string` | `"Enabled"` | no |
164+
| <a name="input_response_headers_policies"></a> [response\_headers\_policies](#input\_response\_headers\_policies) | Map of CloudFront response headers policies with their configurations | <pre>map(object({<br/> name = optional(string)<br/> comment = optional(string)<br/> cors_config = optional(object({<br/> access_control_allow_credentials = bool<br/> origin_override = bool<br/> access_control_allow_headers = object({<br/> items = list(string)<br/> })<br/> access_control_allow_methods = object({<br/> items = list(string)<br/> })<br/> access_control_allow_origins = object({<br/> items = list(string)<br/> })<br/> access_control_expose_headers = optional(object({<br/> items = list(string)<br/> }))<br/> access_control_max_age_sec = optional(number)<br/> }))<br/> custom_headers_config = optional(object({<br/> items = list(object({<br/> header = string<br/> override = bool<br/> value = string<br/> }))<br/> }))<br/> remove_headers_config = optional(object({<br/> items = list(object({<br/> header = string<br/> }))<br/> }))<br/> security_headers_config = optional(object({<br/> content_security_policy = optional(object({<br/> content_security_policy = string<br/> override = bool<br/> }))<br/> content_type_options = optional(object({<br/> override = bool<br/> }))<br/> frame_options = optional(object({<br/> frame_option = string<br/> override = bool<br/> }))<br/> referrer_policy = optional(object({<br/> referrer_policy = string<br/> override = bool<br/> }))<br/> strict_transport_security = optional(object({<br/> access_control_max_age_sec = number<br/> override = bool<br/> include_subdomains = optional(bool)<br/> preload = optional(bool)<br/> }))<br/> xss_protection = optional(object({<br/> mode_block = bool<br/> override = bool<br/> protection = bool<br/> report_uri = optional(string)<br/> }))<br/> }))<br/> server_timing_headers_config = optional(object({<br/> enabled = bool<br/> sampling_rate = number<br/> }))<br/> }))</pre> | `null` | no |
162165
| <a name="input_retain_on_delete"></a> [retain\_on\_delete](#input\_retain\_on\_delete) | Disables the distribution instead of deleting it when destroying the resource through Terraform. If this is set, the distribution needs to be deleted manually afterwards. | `bool` | `false` | no |
163166
| <a name="input_staging"></a> [staging](#input\_staging) | Whether the distribution is a staging distribution. | `bool` | `false` | no |
164167
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to assign to the resource. | `map(string)` | `null` | no |
@@ -189,6 +192,7 @@ No modules.
189192
| <a name="output_cloudfront_origin_access_identities"></a> [cloudfront\_origin\_access\_identities](#output\_cloudfront\_origin\_access\_identities) | The origin access identities created |
190193
| <a name="output_cloudfront_origin_access_identity_iam_arns"></a> [cloudfront\_origin\_access\_identity\_iam\_arns](#output\_cloudfront\_origin\_access\_identity\_iam\_arns) | The IAM arns of the origin access identities created |
191194
| <a name="output_cloudfront_origin_access_identity_ids"></a> [cloudfront\_origin\_access\_identity\_ids](#output\_cloudfront\_origin\_access\_identity\_ids) | The IDS of the origin access identities created |
195+
| <a name="output_cloudfront_response_headers_policies"></a> [cloudfront\_response\_headers\_policies](#output\_cloudfront\_response\_headers\_policies) | The response headers policies created |
192196
| <a name="output_cloudfront_vpc_origin_ids"></a> [cloudfront\_vpc\_origin\_ids](#output\_cloudfront\_vpc\_origin\_ids) | The IDS of the VPC origin created |
193197
<!-- END_TF_DOCS -->
194198

examples/complete/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ Note that this example may create resources which cost money. Run `terraform des
2828
| Name | Version |
2929
|------|---------|
3030
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
31-
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.83 |
31+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.100 |
3232
| <a name="requirement_null"></a> [null](#requirement\_null) | >= 2.0 |
3333
| <a name="requirement_random"></a> [random](#requirement\_random) | >= 2.0 |
3434

3535
## Providers
3636

3737
| Name | Version |
3838
|------|---------|
39-
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.83 |
39+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.100 |
4040
| <a name="provider_null"></a> [null](#provider\_null) | >= 2.0 |
4141
| <a name="provider_random"></a> [random](#provider\_random) | >= 2.0 |
4242

examples/complete/main.tf

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ module "cloudfront" {
176176
cache_policy_name = "Managed-CachingOptimized"
177177
origin_request_policy_name = "Managed-UserAgentRefererHeaders"
178178
response_headers_policy_name = "Managed-SimpleCORS"
179+
# using a response header policy you're dynamically creating below
180+
# response_header_policy: "cors_policy"
179181

180182
function_association = {
181183
# Valid keys: viewer-request, viewer-response
@@ -231,6 +233,79 @@ module "cloudfront" {
231233
locations = ["NO", "UA", "US", "GB"]
232234
}
233235

236+
create_response_headers_policy = true
237+
response_headers_policies = {
238+
cors_policy = {
239+
name = "CORSPolicy"
240+
comment = "CORS configuration for API"
241+
242+
cors_config = {
243+
access_control_allow_credentials = true
244+
origin_override = true
245+
246+
access_control_allow_headers = {
247+
items = ["*"]
248+
}
249+
250+
access_control_allow_methods = {
251+
items = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
252+
}
253+
254+
access_control_allow_origins = {
255+
items = ["https://example.com", "https://app.example.com"]
256+
}
257+
258+
access_control_expose_headers = {
259+
items = ["X-Custom-Header", "X-Request-Id"]
260+
}
261+
262+
access_control_max_age_sec = 3600
263+
}
264+
}
265+
custom_headers = {
266+
name = "CustomHeadersPolicy"
267+
comment = "Add custom response headers"
268+
269+
custom_headers_config = {
270+
items = [
271+
{
272+
header = "X-Powered-By"
273+
override = true
274+
value = "MyApp/1.0"
275+
},
276+
{
277+
header = "X-API-Version"
278+
override = false
279+
value = "v2"
280+
},
281+
{
282+
header = "Cache-Control"
283+
override = true
284+
value = "public, max-age=3600"
285+
}
286+
]
287+
}
288+
}
289+
remove_headers = {
290+
name = "RemoveHeadersPolicy"
291+
comment = "Remove unwanted headers from origin"
292+
293+
remove_headers_config = {
294+
items = [
295+
{
296+
header = "x-robots-tag"
297+
},
298+
{
299+
header = "server"
300+
},
301+
{
302+
header = "x-powered-by"
303+
}
304+
]
305+
}
306+
}
307+
}
308+
234309
}
235310

236311
######

examples/complete/versions.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ terraform {
44
required_providers {
55
aws = {
66
source = "hashicorp/aws"
7-
version = ">= 5.83"
7+
version = ">= 5.100"
88
}
99
random = {
1010
source = "hashicorp/random"

main.tf

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,145 @@ locals {
44
create_vpc_origin = var.create_vpc_origin && length(keys(var.vpc_origin)) > 0
55
}
66

7+
resource "aws_cloudfront_response_headers_policy" "this" {
8+
for_each = var.create_response_headers_policy && var.response_headers_policies != null ? var.response_headers_policies : {}
9+
10+
name = try(coalesce(each.value.name, each.key))
11+
comment = each.value.comment
12+
13+
dynamic "cors_config" {
14+
for_each = each.value.cors_config != null ? [each.value.cors_config] : []
15+
16+
content {
17+
access_control_allow_credentials = cors_config.value.access_control_allow_credentials
18+
origin_override = cors_config.value.origin_override
19+
access_control_max_age_sec = cors_config.value.access_control_max_age_sec
20+
21+
access_control_allow_headers {
22+
items = cors_config.value.access_control_allow_headers.items
23+
}
24+
25+
access_control_allow_methods {
26+
items = cors_config.value.access_control_allow_methods.items
27+
}
28+
29+
access_control_allow_origins {
30+
items = cors_config.value.access_control_allow_origins.items
31+
}
32+
33+
dynamic "access_control_expose_headers" {
34+
for_each = cors_config.value.access_control_expose_headers != null ? [cors_config.value.access_control_expose_headers] : []
35+
36+
content {
37+
items = access_control_expose_headers.value.items
38+
}
39+
}
40+
}
41+
}
42+
43+
dynamic "custom_headers_config" {
44+
for_each = each.value.custom_headers_config != null ? [each.value.custom_headers_config] : []
45+
46+
content {
47+
dynamic "items" {
48+
for_each = custom_headers_config.value.items
49+
50+
content {
51+
header = items.value.header
52+
override = items.value.override
53+
value = items.value.value
54+
}
55+
}
56+
}
57+
}
58+
59+
dynamic "remove_headers_config" {
60+
for_each = each.value.remove_headers_config != null ? [each.value.remove_headers_config] : []
61+
62+
content {
63+
dynamic "items" {
64+
for_each = remove_headers_config.value.items
65+
66+
content {
67+
header = items.value.header
68+
}
69+
}
70+
}
71+
}
72+
73+
dynamic "security_headers_config" {
74+
for_each = each.value.security_headers_config != null ? [each.value.security_headers_config] : []
75+
76+
content {
77+
dynamic "content_security_policy" {
78+
for_each = security_headers_config.value.content_security_policy != null ? [security_headers_config.value.content_security_policy] : []
79+
80+
content {
81+
content_security_policy = content_security_policy.value.content_security_policy
82+
override = content_security_policy.value.override
83+
}
84+
}
85+
86+
dynamic "content_type_options" {
87+
for_each = security_headers_config.value.content_type_options != null ? [security_headers_config.value.content_type_options] : []
88+
89+
content {
90+
override = content_type_options.value.override
91+
}
92+
}
93+
94+
dynamic "frame_options" {
95+
for_each = security_headers_config.value.frame_options != null ? [security_headers_config.value.frame_options] : []
96+
97+
content {
98+
frame_option = frame_options.value.frame_option
99+
override = frame_options.value.override
100+
}
101+
}
102+
103+
dynamic "referrer_policy" {
104+
for_each = security_headers_config.value.referrer_policy != null ? [security_headers_config.value.referrer_policy] : []
105+
106+
content {
107+
referrer_policy = referrer_policy.value.referrer_policy
108+
override = referrer_policy.value.override
109+
}
110+
}
111+
112+
dynamic "strict_transport_security" {
113+
for_each = security_headers_config.value.strict_transport_security != null ? [security_headers_config.value.strict_transport_security] : []
114+
115+
content {
116+
access_control_max_age_sec = strict_transport_security.value.access_control_max_age_sec
117+
override = strict_transport_security.value.override
118+
include_subdomains = strict_transport_security.value.include_subdomains
119+
preload = strict_transport_security.value.preload
120+
}
121+
}
122+
123+
dynamic "xss_protection" {
124+
for_each = security_headers_config.value.xss_protection != null ? [security_headers_config.value.xss_protection] : []
125+
126+
content {
127+
mode_block = xss_protection.value.mode_block
128+
override = xss_protection.value.override
129+
protection = xss_protection.value.protection
130+
report_uri = xss_protection.value.report_uri
131+
}
132+
}
133+
}
134+
}
135+
136+
dynamic "server_timing_headers_config" {
137+
for_each = each.value.server_timing_headers_config != null ? [each.value.server_timing_headers_config] : []
138+
139+
content {
140+
enabled = server_timing_headers_config.value.enabled
141+
sampling_rate = server_timing_headers_config.value.sampling_rate
142+
}
143+
}
144+
}
145+
7146
resource "aws_cloudfront_origin_access_identity" "this" {
8147
for_each = local.create_origin_access_identity ? var.origin_access_identities : {}
9148

outputs.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,8 @@ output "cloudfront_vpc_origin_ids" {
8787
description = "The IDS of the VPC origin created"
8888
value = local.create_vpc_origin ? [for v in aws_cloudfront_vpc_origin.this : v.id] : []
8989
}
90+
91+
output "cloudfront_response_headers_policies" {
92+
description = "The response headers policies created"
93+
value = aws_cloudfront_response_headers_policy.this
94+
}

0 commit comments

Comments
 (0)