Skip to content

Commit f06304f

Browse files
committed
Add cloudfront response header policy support
1 parent fc1010c commit f06304f

File tree

6 files changed

+324
-3
lines changed

6 files changed

+324
-3
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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_policy"></a> [response\_headers\_policy](#input\_response\_headers\_policy) | Map of CloudFront response headers policies with their configurations | <pre>map(object({<br/> name = optional(string)<br/> comment = optional(string)<br/><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/><br/> custom_headers_config = optional(object({<br/> items = list(object({<br/> header = string<br/> override = bool<br/> value = string<br/> }))<br/> }))<br/><br/> remove_headers_config = optional(object({<br/> items = list(object({<br/> header = string<br/> }))<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/><br/> server_timing_headers_config = optional(object({<br/> enabled = bool<br/> sampling_rate = number<br/> }))<br/> }))</pre> | `{}` | 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,9 @@ 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 |
196+
| <a name="output_cloudfront_response_headers_policy_etags"></a> [cloudfront\_response\_headers\_policy\_etags](#output\_cloudfront\_response\_headers\_policy\_etags) | The ETags of the response headers policies created |
197+
| <a name="output_cloudfront_response_headers_policy_ids"></a> [cloudfront\_response\_headers\_policy\_ids](#output\_cloudfront\_response\_headers\_policy\_ids) | The IDs of the response headers policies created |
192198
| <a name="output_cloudfront_vpc_origin_ids"></a> [cloudfront\_vpc\_origin\_ids](#output\_cloudfront\_vpc\_origin\_ids) | The IDS of the VPC origin created |
193199
<!-- END_TF_DOCS -->
194200

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_policy = {
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
######

main.tf

Lines changed: 144 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,150 @@
11
locals {
2-
create_origin_access_identity = var.create_origin_access_identity && length(keys(var.origin_access_identities)) > 0
3-
create_origin_access_control = var.create_origin_access_control && length(keys(var.origin_access_control)) > 0
4-
create_vpc_origin = var.create_vpc_origin && length(keys(var.vpc_origin)) > 0
2+
create_origin_access_identity = var.create_origin_access_identity && length(keys(var.origin_access_identities)) > 0
3+
create_origin_access_control = var.create_origin_access_control && length(keys(var.origin_access_control)) > 0
4+
create_vpc_origin = var.create_vpc_origin && length(keys(var.vpc_origin)) > 0
5+
create_response_headers_policy = var.create_response_headers_policy && length(keys(var.response_headers_policy)) > 0
56
}
67

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

outputs.tf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,18 @@ 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 = local.create_response_headers_policy ? { for k, v in aws_cloudfront_response_headers_policy.this : k => v } : {}
94+
}
95+
96+
output "cloudfront_response_headers_policy_ids" {
97+
description = "The IDs of the response headers policies created"
98+
value = local.create_response_headers_policy ? { for k, v in aws_cloudfront_response_headers_policy.this : k => v.id } : {}
99+
}
100+
101+
output "cloudfront_response_headers_policy_etags" {
102+
description = "The ETags of the response headers policies created"
103+
value = local.create_response_headers_policy ? { for k, v in aws_cloudfront_response_headers_policy.this : k => v.etag } : {}
104+
}

variables.tf

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,85 @@ variable "vpc_origin_timeouts" {
210210
type = map(string)
211211
default = {}
212212
}
213+
214+
variable "create_response_headers_policy" {
215+
description = "Controls if CloudFront response headers policies should be created"
216+
type = bool
217+
default = false
218+
}
219+
220+
variable "response_headers_policy" {
221+
description = "Map of CloudFront response headers policies with their configurations"
222+
type = map(object({
223+
name = optional(string)
224+
comment = optional(string)
225+
226+
cors_config = optional(object({
227+
access_control_allow_credentials = bool
228+
origin_override = bool
229+
access_control_allow_headers = object({
230+
items = list(string)
231+
})
232+
access_control_allow_methods = object({
233+
items = list(string)
234+
})
235+
access_control_allow_origins = object({
236+
items = list(string)
237+
})
238+
access_control_expose_headers = optional(object({
239+
items = list(string)
240+
}))
241+
access_control_max_age_sec = optional(number)
242+
}))
243+
244+
custom_headers_config = optional(object({
245+
items = list(object({
246+
header = string
247+
override = bool
248+
value = string
249+
}))
250+
}))
251+
252+
remove_headers_config = optional(object({
253+
items = list(object({
254+
header = string
255+
}))
256+
}))
257+
258+
security_headers_config = optional(object({
259+
content_security_policy = optional(object({
260+
content_security_policy = string
261+
override = bool
262+
}))
263+
content_type_options = optional(object({
264+
override = bool
265+
}))
266+
frame_options = optional(object({
267+
frame_option = string
268+
override = bool
269+
}))
270+
referrer_policy = optional(object({
271+
referrer_policy = string
272+
override = bool
273+
}))
274+
strict_transport_security = optional(object({
275+
access_control_max_age_sec = number
276+
override = bool
277+
include_subdomains = optional(bool)
278+
preload = optional(bool)
279+
}))
280+
xss_protection = optional(object({
281+
mode_block = bool
282+
override = bool
283+
protection = bool
284+
report_uri = optional(string)
285+
}))
286+
}))
287+
288+
server_timing_headers_config = optional(object({
289+
enabled = bool
290+
sampling_rate = number
291+
}))
292+
}))
293+
default = {}
294+
}

0 commit comments

Comments
 (0)