Skip to content

Commit 3b1303f

Browse files
authored
feat: Viewer mTLS Support (#194)
1 parent 262912b commit 3b1303f

File tree

22 files changed

+918
-18
lines changed

22 files changed

+918
-18
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ module "cdn" {
149149
## Examples
150150

151151
- [Complete](https://github.com/terraform-aws-modules/terraform-aws-cloudfront/tree/master/examples/complete) - Complete example which creates AWS CloudFront distribution and integrates it with other [terraform-aws-modules](https://github.com/terraform-aws-modules) to create additional resources: S3 buckets, Lambda Functions, CloudFront Functions, VPC Origins, ACM Certificate, Route53 Records.
152+
- [mTLS](https://github.com/terraform-aws-modules/terraform-aws-cloudfront/tree/master/examples/mtls) - mTLS example which creates AWS CloudFront distribution with viewer mTLS support.
152153

153154
<!-- BEGIN_TF_DOCS -->
154155
## Requirements
@@ -172,6 +173,7 @@ No modules.
172173

173174
| Name | Type |
174175
|------|------|
176+
| [aws_cloudfront_connection_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_connection_function) | resource |
175177
| [aws_cloudfront_distribution.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource |
176178
| [aws_cloudfront_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_function) | resource |
177179
| [aws_cloudfront_monitoring_subscription.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_monitoring_subscription) | resource |
@@ -193,8 +195,14 @@ No modules.
193195
| <a name="input_anycast_ip_list_id"></a> [anycast\_ip\_list\_id](#input\_anycast\_ip\_list\_id) | ID of the Anycast static IP list that is associated with the distribution | `string` | `null` | no |
194196
| <a name="input_cloudfront_functions"></a> [cloudfront\_functions](#input\_cloudfront\_functions) | Map of CloudFront Function configurations. Key is used as default function name if 'name' not specified | <pre>map(object({<br/> name = optional(string)<br/> runtime = optional(string, "cloudfront-js-2.0")<br/> comment = optional(string)<br/> publish = optional(bool)<br/> code = string<br/> key_value_store_associations = optional(list(string))<br/> }))</pre> | `null` | no |
195197
| <a name="input_comment"></a> [comment](#input\_comment) | Any comments you want to include about the distribution | `string` | `null` | no |
198+
| <a name="input_connection_function_association_id"></a> [connection\_function\_association\_id](#input\_connection\_function\_association\_id) | Identifier of the connection function to associate with the distribution | `string` | `null` | no |
199+
| <a name="input_connection_function_code"></a> [connection\_function\_code](#input\_connection\_function\_code) | The code of the CloudFront connection function | `string` | `null` | no |
200+
| <a name="input_connection_function_config"></a> [connection\_function\_config](#input\_connection\_function\_config) | Configuration block for the CloudFront connection function | <pre>object({<br/> comment = string<br/> runtime = string<br/> key_value_store_association = optional(object({<br/> key_value_store_arn = string<br/> }))<br/> })</pre> | `null` | no |
201+
| <a name="input_connection_function_name"></a> [connection\_function\_name](#input\_connection\_function\_name) | The name of the CloudFront connection function | `string` | `null` | no |
202+
| <a name="input_connection_function_publish"></a> [connection\_function\_publish](#input\_connection\_function\_publish) | Whether to publish the function to the LIVE stage after creation or update. Defaults to false | `bool` | `null` | no |
196203
| <a name="input_continuous_deployment_policy_id"></a> [continuous\_deployment\_policy\_id](#input\_continuous\_deployment\_policy\_id) | Identifier of a continuous deployment policy. This argument should only be set on a production distribution | `string` | `null` | no |
197204
| <a name="input_create"></a> [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no |
205+
| <a name="input_create_connection_function"></a> [create\_connection\_function](#input\_create\_connection\_function) | Controls whether to create a CloudFront connection function | `bool` | `false` | no |
198206
| <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 |
199207
| <a name="input_custom_error_response"></a> [custom\_error\_response](#input\_custom\_error\_response) | One or more custom error response elements | <pre>list(object({<br/> error_caching_min_ttl = optional(number)<br/> error_code = number<br/> response_code = optional(number)<br/> response_page_path = optional(string)<br/> }))</pre> | `null` | no |
200208
| <a name="input_default_cache_behavior"></a> [default\_cache\_behavior](#input\_default\_cache\_behavior) | The default cache behavior for this distribution | <pre>object({<br/> allowed_methods = optional(list(string), ["GET", "HEAD", "OPTIONS"])<br/> cache_policy_id = optional(string)<br/> cache_policy_name = optional(string)<br/> cached_methods = optional(list(string), ["GET", "HEAD"])<br/> compress = optional(bool, true)<br/> default_ttl = optional(number)<br/> field_level_encryption_id = optional(string)<br/> forwarded_values = optional(object({<br/> cookies = object({<br/> forward = optional(string, "none")<br/> whitelisted_names = optional(list(string))<br/> })<br/> headers = optional(list(string))<br/> query_string = optional(bool, false)<br/> query_string_cache_keys = optional(list(string))<br/> }),<br/> {<br/> cookies = {<br/> forward = "none"<br/> }<br/> query_string = false<br/> }<br/> )<br/> function_association = optional(map(object({<br/> event_type = optional(string)<br/> function_arn = optional(string)<br/> function_key = optional(string)<br/> })))<br/> grpc_config = optional(object({<br/> enabled = optional(bool)<br/> }))<br/> lambda_function_association = optional(map(object({<br/> event_type = optional(string)<br/> include_body = optional(bool)<br/> lambda_arn = string<br/> })))<br/> max_ttl = optional(number)<br/> min_ttl = optional(number)<br/> origin_request_policy_id = optional(string)<br/> origin_request_policy_name = optional(string)<br/> realtime_log_config_arn = optional(string)<br/> response_headers_policy_id = optional(string)<br/> response_headers_policy_key = optional(string)<br/> response_headers_policy_name = optional(string)<br/> smooth_streaming = optional(bool)<br/> target_origin_id = string<br/> trusted_key_groups = optional(list(string))<br/> trusted_signers = optional(list(string))<br/> viewer_protocol_policy = optional(string, "https-only")<br/> })</pre> | n/a | yes |
@@ -217,6 +225,7 @@ No modules.
217225
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no |
218226
| <a name="input_v2_logging"></a> [v2\_logging](#input\_v2\_logging) | Configuration block for v2 logging destination | <pre>object({<br/> # Destination<br/> delivery_destination_configuration = optional(object({<br/> destination_resource_arn = optional(string)<br/> }))<br/> delivery_destination_type = optional(string)<br/> name = string<br/> output_format = optional(string)<br/> # Delivery<br/> field_delimiter = optional(string)<br/> record_fields = optional(list(string))<br/> s3_delivery_configuration = optional(object({<br/> enable_hive_compatible_path = optional(bool)<br/> suffix_path = optional(string)<br/> }))<br/> })</pre> | `null` | no |
219227
| <a name="input_viewer_certificate"></a> [viewer\_certificate](#input\_viewer\_certificate) | The SSL configuration for this distribution | <pre>object({<br/> acm_certificate_arn = optional(string)<br/> cloudfront_default_certificate = optional(bool)<br/> iam_certificate_id = optional(string)<br/> minimum_protocol_version = optional(string, "TLSv1.2_2025")<br/> ssl_support_method = optional(string)<br/> })</pre> | `{}` | no |
228+
| <a name="input_viewer_mtls_config"></a> [viewer\_mtls\_config](#input\_viewer\_mtls\_config) | The viewer mTLS configuration for this distribution | <pre>object({<br/> mode = optional(string)<br/> trust_store_config = optional(object({<br/> trust_store_id = string<br/> advertise_trust_store_ca_names = optional(bool)<br/> ignore_certificate_expiry = optional(bool)<br/> }))<br/> })</pre> | `null` | no |
220229
| <a name="input_vpc_origin"></a> [vpc\_origin](#input\_vpc\_origin) | Map of CloudFront VPC origins | <pre>map(object({<br/> arn = string<br/> http_port = number<br/> https_port = number<br/> name = optional(string)<br/> origin_protocol_policy = string<br/> origin_ssl_protocols = object({<br/> items = optional(list(string), ["TLSv1.2"])<br/> quantity = optional(number, 1)<br/> })<br/> timeouts = optional(object({<br/> create = optional(string)<br/> update = optional(string)<br/> delete = optional(string)<br/> }))<br/> tags = optional(map(string), {})<br/> }))</pre> | `null` | no |
221230
| <a name="input_wait_for_deployment"></a> [wait\_for\_deployment](#input\_wait\_for\_deployment) | If enabled, the resource will wait for the distribution status to change from InProgress to Deployed. Setting this to false will skip the process | `bool` | `null` | no |
222231
| <a name="input_web_acl_id"></a> [web\_acl\_id](#input\_web\_acl\_id) | If you're using AWS WAF to filter CloudFront requests, the Id of the AWS WAF web ACL that is associated with the distribution. The WAF Web ACL must exist in the WAF Global (CloudFront) region and the credentials configuring this argument must have waf:GetWebACL permissions assigned. If using WAFv2, provide the ARN of the web ACL | `string` | `null` | no |
@@ -240,6 +249,11 @@ No modules.
240249
| <a name="output_cloudfront_origin_access_controls"></a> [cloudfront\_origin\_access\_controls](#output\_cloudfront\_origin\_access\_controls) | The origin access controls created |
241250
| <a name="output_cloudfront_response_headers_policies"></a> [cloudfront\_response\_headers\_policies](#output\_cloudfront\_response\_headers\_policies) | The response headers policies created |
242251
| <a name="output_cloudfront_vpc_origins"></a> [cloudfront\_vpc\_origins](#output\_cloudfront\_vpc\_origins) | The IDS of the VPC origin created |
252+
| <a name="output_connection_function_arn"></a> [connection\_function\_arn](#output\_connection\_function\_arn) | ARN of the connection function |
253+
| <a name="output_connection_function_etag"></a> [connection\_function\_etag](#output\_connection\_function\_etag) | ETag of the connection function |
254+
| <a name="output_connection_function_id"></a> [connection\_function\_id](#output\_connection\_function\_id) | ID of the connection function |
255+
| <a name="output_connection_function_live_stage_etag"></a> [connection\_function\_live\_stage\_etag](#output\_connection\_function\_live\_stage\_etag) | ETag of the function's LIVE stage. Will be empty if the function has not been published |
256+
| <a name="output_connection_function_status"></a> [connection\_function\_status](#output\_connection\_function\_status) | Status of the connection function |
243257
<!-- END_TF_DOCS -->
244258

245259
## Authors

examples/mtls/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# mTLS with CloudFront Distribution
2+
3+
Configuration in this directory creates CloudFront distribution with viewer mTLS support.
4+
5+
<!-- BEGIN_TF_DOCS -->
6+
## Requirements
7+
8+
| Name | Version |
9+
|------|---------|
10+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
11+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.28 |
12+
| <a name="requirement_tls"></a> [tls](#requirement\_tls) | >= 4.0 |
13+
14+
## Providers
15+
16+
| Name | Version |
17+
|------|---------|
18+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 6.28 |
19+
| <a name="provider_tls"></a> [tls](#provider\_tls) | >= 4.0 |
20+
21+
## Modules
22+
23+
| Name | Source | Version |
24+
|------|--------|---------|
25+
| <a name="module_acm"></a> [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 4.0 |
26+
| <a name="module_ca_certificates"></a> [ca\_certificates](#module\_ca\_certificates) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 |
27+
| <a name="module_cloudfront"></a> [cloudfront](#module\_cloudfront) | ../../ | n/a |
28+
| <a name="module_records"></a> [records](#module\_records) | terraform-aws-modules/route53/aws//modules/records | ~> 5.0 |
29+
| <a name="module_s3"></a> [s3](#module\_s3) | terraform-aws-modules/s3-bucket/aws | ~> 5.0 |
30+
| <a name="module_trust_store"></a> [trust\_store](#module\_trust\_store) | ../../modules/trust_store | n/a |
31+
32+
## Resources
33+
34+
| Name | Type |
35+
|------|------|
36+
| [aws_s3_object.ca_certificates](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource |
37+
| [aws_s3_object.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource |
38+
| [tls_cert_request.client](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/cert_request) | resource |
39+
| [tls_locally_signed_cert.client](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/locally_signed_cert) | resource |
40+
| [tls_private_key.ca](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource |
41+
| [tls_private_key.client](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource |
42+
| [tls_self_signed_cert.ca](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/self_signed_cert) | resource |
43+
| [aws_iam_policy_document.s3_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
44+
| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |
45+
46+
## Inputs
47+
48+
| Name | Description | Type | Default | Required |
49+
|------|-------------|------|---------|:--------:|
50+
| <a name="input_domain"></a> [domain](#input\_domain) | The domain name to use when deploying the CloudFront distribution | `string` | `"terraform-aws-modules.modules.tf"` | no |
51+
52+
## Outputs
53+
54+
| Name | Description |
55+
|------|-------------|
56+
| <a name="output_ca_certificate_pem"></a> [ca\_certificate\_pem](#output\_ca\_certificate\_pem) | The CA certificate in PEM format |
57+
| <a name="output_client_certificate_pem"></a> [client\_certificate\_pem](#output\_client\_certificate\_pem) | The client certificate in PEM format |
58+
| <a name="output_client_private_key_pem"></a> [client\_private\_key\_pem](#output\_client\_private\_key\_pem) | The client private key in PEM format |
59+
| <a name="output_cloudfront_distribution_domain"></a> [cloudfront\_distribution\_domain](#output\_cloudfront\_distribution\_domain) | The domain name of the CloudFront distribution |
60+
| <a name="output_cloudfront_distribution_id"></a> [cloudfront\_distribution\_id](#output\_cloudfront\_distribution\_id) | The ID of the CloudFront distribution |
61+
| <a name="output_connection_function_arn"></a> [connection\_function\_arn](#output\_connection\_function\_arn) | ARN of the connection function |
62+
| <a name="output_connection_function_etag"></a> [connection\_function\_etag](#output\_connection\_function\_etag) | ETag of the connection function |
63+
| <a name="output_connection_function_id"></a> [connection\_function\_id](#output\_connection\_function\_id) | ID of the connection function |
64+
| <a name="output_connection_function_live_stage_etag"></a> [connection\_function\_live\_stage\_etag](#output\_connection\_function\_live\_stage\_etag) | ETag of the function's LIVE stage. Will be empty if the function has not been published |
65+
| <a name="output_connection_function_status"></a> [connection\_function\_status](#output\_connection\_function\_status) | Status of the connection function |
66+
| <a name="output_trust_store_arn"></a> [trust\_store\_arn](#output\_trust\_store\_arn) | The ARN of the CloudFront trust store |
67+
| <a name="output_trust_store_etag"></a> [trust\_store\_etag](#output\_trust\_store\_etag) | ETAG of the CloudFront trust store |
68+
| <a name="output_trust_store_id"></a> [trust\_store\_id](#output\_trust\_store\_id) | The ID of the CloudFront trust store |
69+
| <a name="output_trust_store_number_of_ca_certificates"></a> [trust\_store\_number\_of\_ca\_certificates](#output\_trust\_store\_number\_of\_ca\_certificates) | Number of CA certificates in the trust store |
70+
<!-- END_TF_DOCS -->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function connectionHandler(connection) {
2+
// Only process if a certificate was presented
3+
if (!connection.clientCertificate) {
4+
console.log("No certificate presented");
5+
connection.deny();
6+
}
7+
8+
// Check the subject field for specific organization
9+
const subject = connection.clientCertificate.certificates.leaf.subject;
10+
if (!subject.includes("O=Example Inc")) {
11+
console.log("Certificate not from authorized organization");
12+
connection.deny();
13+
} else {
14+
// All checks passed
15+
console.log("Certificate validation passed");
16+
connection.allow();
17+
}
18+
}

examples/mtls/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>CloudFront mTLS Example</title>
5+
</head>
6+
<body>
7+
<h1>CloudFront mTLS Example</h1>
8+
<p>This page is served through CloudFront with mutual TLS (mTLS) authentication.</p>
9+
<p>The mTLS configuration validates client certificates against a trust store containing CA certificates.</p>
10+
</body>
11+
</html>

0 commit comments

Comments
 (0)