From c44dbde7c92ff8b15752e2903c6e1c5efbe318d4 Mon Sep 17 00:00:00 2001 From: rankin-tr <43282033+rankin-tr@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:56:24 -0700 Subject: [PATCH] Add support for origin-access-control (#319) * add `origin_access_control` feature * update docs * update example * fix terratest resource creation * fix: tf lint formatting * add version constraint to local TF provider --------- Co-authored-by: Matt Gowie Co-authored-by: Jeremy White --- README.md | 11 +++- docs/terraform.md | 11 +++- examples/complete/main.tf | 9 ++-- examples/complete/outputs.tf | 5 ++ examples/complete/s3-origins.tf | 7 +-- main.tf | 93 +++++++++++++++++++++++++++------ modules/lambda@edge/versions.tf | 4 ++ outputs.tf | 5 ++ variables.tf | 36 +++++++++++-- 9 files changed, 151 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 4b242139..66286c63 100644 --- a/README.md +++ b/README.md @@ -425,6 +425,7 @@ Available targets: | Name | Type | |------|------| | [aws_cloudfront_distribution.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource | +| [aws_cloudfront_origin_access_control.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control) | resource | | [aws_cloudfront_origin_access_identity.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_identity) | resource | | [aws_s3_bucket.origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket_acl.origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | @@ -436,9 +437,11 @@ Available targets: | [aws_s3_bucket_versioning.origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [random_password.referer](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | | [time_sleep.wait_for_aws_s3_bucket_settings](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.combined](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.deployment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.s3_origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.s3_origin_access_control](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.s3_origin_access_identity](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.s3_ssl_only](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.s3_website_origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | @@ -467,6 +470,7 @@ Available targets: | [cloudfront\_access\_log\_include\_cookies](#input\_cloudfront\_access\_log\_include\_cookies) | Set true to include cookies in Cloudfront Access Logs | `bool` | `false` | no | | [cloudfront\_access\_log\_prefix](#input\_cloudfront\_access\_log\_prefix) | Prefix to use for Cloudfront Access Log object keys. Defaults to no prefix. | `string` | `""` | no | | [cloudfront\_access\_logging\_enabled](#input\_cloudfront\_access\_logging\_enabled) | Set true to enable delivery of Cloudfront Access Logs to an S3 bucket | `bool` | `true` | no | +| [cloudfront\_origin\_access\_control\_id](#input\_cloudfront\_origin\_access\_control\_id) | CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). OAC helps you secure your origins, such as for Amazon S3. | `string` | `""` | no | | [cloudfront\_origin\_access\_identity\_iam\_arn](#input\_cloudfront\_origin\_access\_identity\_iam\_arn) | Existing cloudfront origin access identity iam arn that is supplied in the s3 bucket policy | `string` | `""` | no | | [cloudfront\_origin\_access\_identity\_path](#input\_cloudfront\_origin\_access\_identity\_path) | Existing cloudfront origin access identity path used in the cloudfront distribution's s3\_origin\_config content | `string` | `""` | no | | [comment](#input\_comment) | Comment for the CloudFront distribution | `string` | `"Managed by Terraform"` | no | @@ -524,6 +528,8 @@ Available targets: | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [ordered\_cache](#input\_ordered\_cache) | An ordered list of [cache behaviors](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#cache-behavior-arguments) resource for this distribution.
List in order of precedence (first match wins). This is in addition to the default cache policy.
Set `target_origin_id` to `""` to specify the S3 bucket origin created by this module. |
list(object({
target_origin_id = string
path_pattern = string

allowed_methods = list(string)
cached_methods = list(string)
compress = bool
trusted_signers = list(string)
trusted_key_groups = list(string)

cache_policy_id = string
origin_request_policy_id = string
realtime_log_config_arn = optional(string)

viewer_protocol_policy = string
min_ttl = number
default_ttl = number
max_ttl = number
response_headers_policy_id = string

forward_query_string = bool
forward_header_values = list(string)
forward_cookies = string
forward_cookies_whitelisted_names = list(string)

lambda_function_association = list(object({
event_type = string
include_body = bool
lambda_arn = string
}))

function_association = list(object({
event_type = string
function_arn = string
}))
}))
| `[]` | no | +| [origin\_access\_control\_signing\_behavior](#input\_origin\_access\_control\_signing\_behavior) | Specifies which requests CloudFront signs. Specify always for the most common use case. Allowed values: always, never, and no-override. | `string` | `"always"` | no | +| [origin\_access\_type](#input\_origin\_access\_type) | Choose to use `origin_access_control` or `orgin_access_identity` | `string` | `"origin_access_identity"` | no | | [origin\_bucket](#input\_origin\_bucket) | Name of an existing S3 bucket to use as the origin. If this is not provided, it will create a new s3 bucket using `var.name` and other context related inputs | `string` | `null` | no | | [origin\_force\_destroy](#input\_origin\_force\_destroy) | Delete all objects from the bucket so that the bucket can be destroyed without error (e.g. `true` or `false`) | `bool` | `false` | no | | [origin\_groups](#input\_origin\_groups) | List of [Origin Groups](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#origin-group-arguments) to create in the distribution.
The values of `primary_origin_id` and `failover_origin_id` must correspond to origin IDs existing in `var.s3_origins` or `var.custom_origins`.

If `primary_origin_id` is set to `null` or `""`, then the origin id of the origin created by this module will be used in its place.
This is to allow for the use case of making the origin created by this module the primary origin in an origin group. |
list(object({
primary_origin_id = string
failover_origin_id = string
failover_criteria = list(string)
}))
| `[]` | no | @@ -545,7 +551,7 @@ Available targets: | [s3\_access\_log\_prefix](#input\_s3\_access\_log\_prefix) | Prefix to use for S3 Access Log object keys. Defaults to `logs/${module.this.id}` | `string` | `""` | no | | [s3\_access\_logging\_enabled](#input\_s3\_access\_logging\_enabled) | Set `true` to deliver S3 Access Logs to the `s3_access_log_bucket_name` bucket.
Defaults to `false` if `s3_access_log_bucket_name` is empty (the default), `true` otherwise.
Must be set explicitly if the access log bucket is being created at the same time as this module is being invoked. | `bool` | `null` | no | | [s3\_object\_ownership](#input\_s3\_object\_ownership) | Specifies the S3 object ownership control on the origin bucket. Valid values are `ObjectWriter`, `BucketOwnerPreferred`, and 'BucketOwnerEnforced'. | `string` | `"ObjectWriter"` | no | -| [s3\_origins](#input\_s3\_origins) | A list of S3 [origins](https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html#origin-arguments) (in addition to the one created by this module) for this distribution.
S3 buckets configured as websites are `custom_origins`, not `s3_origins`.
Specifying `s3_origin_config.origin_access_identity` as `null` or `""` will have it translated to the `origin_access_identity` used by the origin created by the module. |
list(object({
domain_name = string
origin_id = string
origin_path = string
s3_origin_config = object({
origin_access_identity = string
})
}))
| `[]` | no | +| [s3\_origins](#input\_s3\_origins) | A list of S3 [origins](https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html#origin-arguments) (in addition to the one created by this module) for this distribution.
S3 buckets configured as websites are `custom_origins`, not `s3_origins`.
Specifying `s3_origin_config.origin_access_identity` as `null` or `""` will have it translated to the `origin_access_identity` used by the origin created by the module. |
list(object({
domain_name = string
origin_id = string
origin_path = string
origin_access_control_id = string
s3_origin_config = object({
origin_access_identity = string
})
}))
| `[]` | no | | [s3\_website\_password\_enabled](#input\_s3\_website\_password\_enabled) | If set to true, and `website_enabled` is also true, a password will be required in the `Referrer` field of the
HTTP request in order to access the website, and Cloudfront will be configured to pass this password in its requests.
This will make it much harder for people to bypass Cloudfront and access the S3 website directly via its website endpoint. | `bool` | `false` | no | | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | @@ -563,6 +569,7 @@ Available targets: | Name | Description | |------|-------------| | [aliases](#output\_aliases) | Aliases of the CloudFront distribution. | +| [cf\_access\_control\_id](#output\_cf\_access\_control\_id) | CloudFront Origin Access Control ID | | [cf\_arn](#output\_cf\_arn) | ARN of AWS CloudFront distribution | | [cf\_domain\_name](#output\_cf\_domain\_name) | Domain name corresponding to the distribution | | [cf\_etag](#output\_cf\_etag) | Current version of the distribution's information | diff --git a/docs/terraform.md b/docs/terraform.md index 9364f17a..4e9c4cbe 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -30,6 +30,7 @@ | Name | Type | |------|------| | [aws_cloudfront_distribution.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource | +| [aws_cloudfront_origin_access_control.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control) | resource | | [aws_cloudfront_origin_access_identity.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_identity) | resource | | [aws_s3_bucket.origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket_acl.origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | @@ -41,9 +42,11 @@ | [aws_s3_bucket_versioning.origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [random_password.referer](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | | [time_sleep.wait_for_aws_s3_bucket_settings](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_iam_policy_document.combined](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.deployment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.s3_origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.s3_origin_access_control](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.s3_origin_access_identity](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.s3_ssl_only](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.s3_website_origin](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | @@ -72,6 +75,7 @@ | [cloudfront\_access\_log\_include\_cookies](#input\_cloudfront\_access\_log\_include\_cookies) | Set true to include cookies in Cloudfront Access Logs | `bool` | `false` | no | | [cloudfront\_access\_log\_prefix](#input\_cloudfront\_access\_log\_prefix) | Prefix to use for Cloudfront Access Log object keys. Defaults to no prefix. | `string` | `""` | no | | [cloudfront\_access\_logging\_enabled](#input\_cloudfront\_access\_logging\_enabled) | Set true to enable delivery of Cloudfront Access Logs to an S3 bucket | `bool` | `true` | no | +| [cloudfront\_origin\_access\_control\_id](#input\_cloudfront\_origin\_access\_control\_id) | CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). OAC helps you secure your origins, such as for Amazon S3. | `string` | `""` | no | | [cloudfront\_origin\_access\_identity\_iam\_arn](#input\_cloudfront\_origin\_access\_identity\_iam\_arn) | Existing cloudfront origin access identity iam arn that is supplied in the s3 bucket policy | `string` | `""` | no | | [cloudfront\_origin\_access\_identity\_path](#input\_cloudfront\_origin\_access\_identity\_path) | Existing cloudfront origin access identity path used in the cloudfront distribution's s3\_origin\_config content | `string` | `""` | no | | [comment](#input\_comment) | Comment for the CloudFront distribution | `string` | `"Managed by Terraform"` | no | @@ -129,6 +133,8 @@ | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [ordered\_cache](#input\_ordered\_cache) | An ordered list of [cache behaviors](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#cache-behavior-arguments) resource for this distribution.
List in order of precedence (first match wins). This is in addition to the default cache policy.
Set `target_origin_id` to `""` to specify the S3 bucket origin created by this module. |
list(object({
target_origin_id = string
path_pattern = string

allowed_methods = list(string)
cached_methods = list(string)
compress = bool
trusted_signers = list(string)
trusted_key_groups = list(string)

cache_policy_id = string
origin_request_policy_id = string
realtime_log_config_arn = optional(string)

viewer_protocol_policy = string
min_ttl = number
default_ttl = number
max_ttl = number
response_headers_policy_id = string

forward_query_string = bool
forward_header_values = list(string)
forward_cookies = string
forward_cookies_whitelisted_names = list(string)

lambda_function_association = list(object({
event_type = string
include_body = bool
lambda_arn = string
}))

function_association = list(object({
event_type = string
function_arn = string
}))
}))
| `[]` | no | +| [origin\_access\_control\_signing\_behavior](#input\_origin\_access\_control\_signing\_behavior) | Specifies which requests CloudFront signs. Specify always for the most common use case. Allowed values: always, never, and no-override. | `string` | `"always"` | no | +| [origin\_access\_type](#input\_origin\_access\_type) | Choose to use `origin_access_control` or `orgin_access_identity` | `string` | `"origin_access_identity"` | no | | [origin\_bucket](#input\_origin\_bucket) | Name of an existing S3 bucket to use as the origin. If this is not provided, it will create a new s3 bucket using `var.name` and other context related inputs | `string` | `null` | no | | [origin\_force\_destroy](#input\_origin\_force\_destroy) | Delete all objects from the bucket so that the bucket can be destroyed without error (e.g. `true` or `false`) | `bool` | `false` | no | | [origin\_groups](#input\_origin\_groups) | List of [Origin Groups](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution#origin-group-arguments) to create in the distribution.
The values of `primary_origin_id` and `failover_origin_id` must correspond to origin IDs existing in `var.s3_origins` or `var.custom_origins`.

If `primary_origin_id` is set to `null` or `""`, then the origin id of the origin created by this module will be used in its place.
This is to allow for the use case of making the origin created by this module the primary origin in an origin group. |
list(object({
primary_origin_id = string
failover_origin_id = string
failover_criteria = list(string)
}))
| `[]` | no | @@ -150,7 +156,7 @@ | [s3\_access\_log\_prefix](#input\_s3\_access\_log\_prefix) | Prefix to use for S3 Access Log object keys. Defaults to `logs/${module.this.id}` | `string` | `""` | no | | [s3\_access\_logging\_enabled](#input\_s3\_access\_logging\_enabled) | Set `true` to deliver S3 Access Logs to the `s3_access_log_bucket_name` bucket.
Defaults to `false` if `s3_access_log_bucket_name` is empty (the default), `true` otherwise.
Must be set explicitly if the access log bucket is being created at the same time as this module is being invoked. | `bool` | `null` | no | | [s3\_object\_ownership](#input\_s3\_object\_ownership) | Specifies the S3 object ownership control on the origin bucket. Valid values are `ObjectWriter`, `BucketOwnerPreferred`, and 'BucketOwnerEnforced'. | `string` | `"ObjectWriter"` | no | -| [s3\_origins](#input\_s3\_origins) | A list of S3 [origins](https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html#origin-arguments) (in addition to the one created by this module) for this distribution.
S3 buckets configured as websites are `custom_origins`, not `s3_origins`.
Specifying `s3_origin_config.origin_access_identity` as `null` or `""` will have it translated to the `origin_access_identity` used by the origin created by the module. |
list(object({
domain_name = string
origin_id = string
origin_path = string
s3_origin_config = object({
origin_access_identity = string
})
}))
| `[]` | no | +| [s3\_origins](#input\_s3\_origins) | A list of S3 [origins](https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html#origin-arguments) (in addition to the one created by this module) for this distribution.
S3 buckets configured as websites are `custom_origins`, not `s3_origins`.
Specifying `s3_origin_config.origin_access_identity` as `null` or `""` will have it translated to the `origin_access_identity` used by the origin created by the module. |
list(object({
domain_name = string
origin_id = string
origin_path = string
origin_access_control_id = string
s3_origin_config = object({
origin_access_identity = string
})
}))
| `[]` | no | | [s3\_website\_password\_enabled](#input\_s3\_website\_password\_enabled) | If set to true, and `website_enabled` is also true, a password will be required in the `Referrer` field of the
HTTP request in order to access the website, and Cloudfront will be configured to pass this password in its requests.
This will make it much harder for people to bypass Cloudfront and access the S3 website directly via its website endpoint. | `bool` | `false` | no | | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | @@ -168,6 +174,7 @@ | Name | Description | |------|-------------| | [aliases](#output\_aliases) | Aliases of the CloudFront distribution. | +| [cf\_access\_control\_id](#output\_cf\_access\_control\_id) | CloudFront Origin Access Control ID | | [cf\_arn](#output\_cf\_arn) | ARN of AWS CloudFront distribution | | [cf\_domain\_name](#output\_cf\_domain\_name) | Domain name corresponding to the distribution | | [cf\_etag](#output\_cf\_etag) | Current version of the distribution's information | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index a0f6088b..4214b4a4 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -99,6 +99,8 @@ module "cloudfront_s3_cdn" { s3_access_log_bucket_name = module.s3_bucket.bucket_id s3_access_log_prefix = "logs/s3_access" + origin_access_type = "origin_access_control" + cloudfront_access_logging_enabled = true cloudfront_access_log_prefix = "logs/cf_access" s3_object_ownership = "BucketOwnerPreferred" @@ -107,9 +109,10 @@ module "cloudfront_s3_cdn" { custom_origins = var.additional_custom_origins_enabled ? [local.additional_custom_origin_primary, local.additional_custom_origin_secondary] : [] s3_origins = concat([{ - domain_name = module.s3_bucket.bucket_regional_domain_name - origin_id = module.s3_bucket.bucket_id - origin_path = null + domain_name = module.s3_bucket.bucket_regional_domain_name + origin_id = module.s3_bucket.bucket_id + origin_path = null + origin_access_control_id = null s3_origin_config = { origin_access_identity = null # will get translated to the origin_access_identity used by the origin created by this module. } diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index e21216f7..95df73e5 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -33,6 +33,11 @@ output "cf_identity_iam_arn" { description = "CloudFront Origin Access Identity IAM ARN" } +output "cf_access_control_id" { + value = module.cloudfront_s3_cdn.cf_access_control_id + description = "CloudFront Origin Access Control ID" +} + output "cf_origin_groups" { value = module.cloudfront_s3_cdn.cf_origin_groups description = "List of Origin Groups in the CloudFront distribution." diff --git a/examples/complete/s3-origins.tf b/examples/complete/s3-origins.tf index b2d33a5f..8c5ef61c 100644 --- a/examples/complete/s3-origins.tf +++ b/examples/complete/s3-origins.tf @@ -1,9 +1,10 @@ locals { additional_s3_origins_enabled = local.enabled && var.additional_s3_origins_enabled default_s3_origin_configuration = { - domain_name = null - origin_id = null - origin_path = null + domain_name = null + origin_id = null + origin_path = null + origin_access_control_id = null s3_origin_config = { origin_access_identity = "" } diff --git a/main.tf b/main.tf index 6a19d75d..6a7cee4a 100644 --- a/main.tf +++ b/main.tf @@ -1,3 +1,5 @@ +data "aws_caller_identity" "current" {} + locals { enabled = module.this.enabled @@ -9,7 +11,11 @@ locals { s3_access_logging_enabled = local.enabled && (var.s3_access_logging_enabled == null ? length(var.s3_access_log_bucket_name) > 0 : var.s3_access_logging_enabled) create_cf_log_bucket = local.cloudfront_access_logging_enabled && local.cloudfront_access_log_create_bucket - create_cloudfront_origin_access_identity = local.enabled && length(compact([var.cloudfront_origin_access_identity_iam_arn])) == 0 # "" or null + origin_access_identity_enabled = local.enabled && var.origin_access_type == "origin_access_identity" + origin_access_control_enabled = local.enabled && var.origin_access_type == "origin_access_control" + + create_cloudfront_origin_access_identity = local.origin_access_identity_enabled && length(compact([var.cloudfront_origin_access_identity_iam_arn])) == 0 # "" or null + create_cloudfront_origin_access_control = local.origin_access_control_enabled && length(compact([var.cloudfront_origin_access_control_id])) == 0 # "" or null origin_id = module.this.id origin_path = coalesce(var.origin_path, "/") @@ -29,8 +35,10 @@ locals { # Workaround for requirement that tertiary expression has to have exactly matching objects in both result values origin_bucket = local.origin_bucket_options[local.enabled ? (local.create_s3_origin_bucket ? "new" : "existing") : "disabled"] - # Collect the information for cloudfront_origin_access_identity_iam and shorten the variable names - cf_access_options = { + # Collect the information for cloudfront_origin_access_control and cloudfront_origin_access_identity and shorten the variable names + cf_origin_access_control_id_arn = "arn:${join("", data.aws_partition.current[*].partition)}:cloudfront::${data.aws_caller_identity.current.account_id}:distribution/${var.cloudfront_origin_access_control_id}" + + cf_access_options = var.origin_access_type == "origin_access_identity" ? { new = local.create_cloudfront_origin_access_identity ? { arn = aws_cloudfront_origin_access_identity.default[0].iam_arn path = aws_cloudfront_origin_access_identity.default[0].cloudfront_access_identity_path @@ -39,8 +47,15 @@ locals { arn = var.cloudfront_origin_access_identity_iam_arn path = var.cloudfront_origin_access_identity_path } - } - cf_access = local.cf_access_options[local.create_cloudfront_origin_access_identity ? "new" : "existing"] + } : var.origin_access_type == "origin_access_control" ? { + new = local.create_cloudfront_origin_access_control ? { + arn = "arn:${join("", data.aws_partition.current[*].partition)}:cloudfront::${data.aws_caller_identity.current.account_id}:distribution/${aws_cloudfront_origin_access_control.default[0].id}" + } : null + existing = { + arn = local.cf_origin_access_control_id_arn + } + } : null + cf_access = local.cf_access_options[local.create_cloudfront_origin_access_identity || local.create_cloudfront_origin_access_control ? "new" : "existing"] bucket = local.origin_bucket.bucket bucket_domain_name = var.website_enabled ? local.origin_bucket.website_endpoint : local.origin_bucket.bucket_regional_domain_name @@ -89,10 +104,11 @@ locals { # https://github.com/hashicorp/terraform-provider-template/issues/85 # https://github.com/hashicorp/terraform/issues/26838 locals { - override_policy = replace(replace(replace(var.additional_bucket_policy, + override_policy = replace(replace(replace(replace(var.additional_bucket_policy, "$${origin_path}", local.origin_path), "$${bucket_name}", local.bucket), - "$${cloudfront_origin_access_identity_iam_arn}", local.cf_access.arn) + "$${cloudfront_origin_access_identity_iam_arn}", try(local.cf_access.arn, "")), + "$${cloudfront_origin_access_control_arn}", try(local.cf_access.arn, "")) } data "aws_partition" "current" { @@ -118,6 +134,15 @@ resource "aws_cloudfront_origin_access_identity" "default" { comment = local.origin_id } +resource "aws_cloudfront_origin_access_control" "default" { + count = local.create_cloudfront_origin_access_control ? 1 : 0 + name = local.origin_id + description = local.origin_id + origin_access_control_origin_type = "s3" + signing_behavior = var.origin_access_control_signing_behavior + signing_protocol = "sigv4" +} + resource "random_password" "referer" { count = local.website_password_enabled ? 1 : 0 @@ -125,8 +150,8 @@ resource "random_password" "referer" { special = false } -data "aws_iam_policy_document" "s3_origin" { - count = local.s3_origin_enabled ? 1 : 0 +data "aws_iam_policy_document" "s3_origin_access_identity" { + count = local.s3_origin_enabled && local.origin_access_identity_enabled ? 1 : 0 override_policy_documents = [local.override_policy] @@ -155,6 +180,30 @@ data "aws_iam_policy_document" "s3_origin" { } } +data "aws_iam_policy_document" "s3_origin_access_control" { + count = local.s3_origin_enabled && local.origin_access_control_enabled ? 1 : 0 + + override_policy_documents = [local.override_policy] + + statement { + sid = "S3GetObjectForCloudFrontServicePrincipal" + + actions = ["s3:GetObject"] + resources = ["arn:${join("", data.aws_partition.current[*].partition)}:s3:::${local.bucket}${local.origin_path}*"] + + principals { + type = "Service" + identifiers = ["cloudfront.amazonaws.com"] + } + + condition { + test = "StringEquals" + variable = "AWS:SourceArn" + values = [aws_cloudfront_distribution.default[0].arn] + } + } +} + data "aws_iam_policy_document" "s3_website_origin" { count = local.website_enabled ? 1 : 0 @@ -168,7 +217,7 @@ data "aws_iam_policy_document" "s3_website_origin" { principals { type = "AWS" - identifiers = var.block_origin_public_access_enabled ? compact(flatten([aws_cloudfront_origin_access_identity.default[*].iam_arn, [var.cloudfront_origin_access_identity_iam_arn]])) : ["*"] + identifiers = var.block_origin_public_access_enabled ? compact(flatten([aws_cloudfront_origin_access_identity.default[*].iam_arn, [var.cloudfront_origin_access_identity_iam_arn], [local.cf_origin_access_control_id_arn]])) : ["*"] } dynamic "condition" { for_each = local.website_password_enabled ? ["password"] : [] @@ -228,7 +277,8 @@ data "aws_iam_policy_document" "combined" { count = local.enabled ? 1 : 0 source_policy_documents = compact(concat( - data.aws_iam_policy_document.s3_origin[*].json, + data.aws_iam_policy_document.s3_origin_access_identity[*].json, + data.aws_iam_policy_document.s3_origin_access_control[*].json, data.aws_iam_policy_document.s3_website_origin[*].json, data.aws_iam_policy_document.s3_ssl_only[*].json, values(data.aws_iam_policy_document.deployment)[*].json @@ -314,6 +364,8 @@ resource "aws_s3_bucket_cors_configuration" "origin" { max_age_seconds = var.cors_max_age_seconds } } + + depends_on = [time_sleep.wait_for_aws_s3_bucket_settings] } resource "aws_s3_bucket_acl" "origin" { @@ -419,8 +471,7 @@ resource "aws_cloudfront_distribution" "default" { depends_on = [ aws_s3_bucket.origin, - aws_s3_bucket_ownership_controls.origin, - time_sleep.wait_for_aws_s3_bucket_settings + module.logs ] dynamic "logging_config" { @@ -460,9 +511,11 @@ resource "aws_cloudfront_distribution" "default" { domain_name = local.bucket_domain_name origin_id = local.origin_id origin_path = var.origin_path + # the following enables specifying the origin_access_identity used by the origin created by this module, prior to the module's creation: + origin_access_control_id = local.create_cloudfront_origin_access_control ? aws_cloudfront_origin_access_control.default[0].id : local.origin_access_control_enabled && length(compact([var.cloudfront_origin_access_control_id])) > 0 ? var.cloudfront_origin_access_control_id : null dynamic "s3_origin_config" { - for_each = !var.website_enabled ? [1] : [] + for_each = !var.website_enabled && !local.origin_access_control_enabled ? [1] : [] content { origin_access_identity = local.cf_access.path } @@ -525,9 +578,15 @@ resource "aws_cloudfront_distribution" "default" { domain_name = origin.value.domain_name origin_id = origin.value.origin_id origin_path = lookup(origin.value, "origin_path", "") - s3_origin_config { - # the following enables specifying the origin_access_identity used by the origin created by this module, prior to the module's creation: - origin_access_identity = try(length(origin.value.s3_origin_config.origin_access_identity), 0) > 0 ? origin.value.s3_origin_config.origin_access_identity : local.cf_access.path + # the following enables specifying the origin_access_control used by the origin created by this module, prior to the module's creation: + origin_access_control_id = local.origin_access_control_enabled && try(length(origin.value.s3_origin_config.origin_access_control_id), 0) > 0 ? origin.value.s3_origin_config.origin_access_control_id : local.origin_access_control_enabled ? aws_cloudfront_origin_access_control.default[0].id : null + + dynamic "s3_origin_config" { + for_each = local.origin_access_identity_enabled ? var.s3_origins : [] + content { + # the following enables specifying the origin_access_identity used by the origin created by this module, prior to the module's creation: + origin_access_identity = local.origin_access_identity_enabled && try(length(origin.value.s3_origin_config.origin_access_identity), 0) > 0 ? origin.value.s3_origin_config.origin_access_identity : local.origin_access_identity_enabled ? local.cf_access.path : "" + } } } } diff --git a/modules/lambda@edge/versions.tf b/modules/lambda@edge/versions.tf index 88c9bbaa..0914825a 100644 --- a/modules/lambda@edge/versions.tf +++ b/modules/lambda@edge/versions.tf @@ -14,5 +14,9 @@ terraform { source = "hashicorp/archive" version = ">= 2.2.0" } + local = { + source = "hashicorp/local" + version = ">= 1.2" + } } } diff --git a/outputs.tf b/outputs.tf index bc25079b..9af9905d 100644 --- a/outputs.tf +++ b/outputs.tf @@ -33,6 +33,11 @@ output "cf_identity_iam_arn" { description = "CloudFront Origin Access Identity IAM ARN" } +output "cf_access_control_id" { + value = try(aws_cloudfront_origin_access_control.default[0].id, "") + description = "CloudFront Origin Access Control ID" +} + output "cf_origin_groups" { value = try(flatten(aws_cloudfront_distribution.default[*].origin_group), []) description = "List of Origin Groups in the CloudFront distribution." diff --git a/variables.tf b/variables.tf index a19d3182..0e5bf1a5 100644 --- a/variables.tf +++ b/variables.tf @@ -67,6 +67,25 @@ variable "origin_force_destroy" { description = "Delete all objects from the bucket so that the bucket can be destroyed without error (e.g. `true` or `false`)" } +variable "cloudfront_origin_access_control_id" { + # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html + type = string + default = "" + description = "CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). OAC helps you secure your origins, such as for Amazon S3." +} + +variable "origin_access_control_signing_behavior" { + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control#signing_behavior + type = string + default = "always" + description = "Specifies which requests CloudFront signs. Specify always for the most common use case. Allowed values: always, never, and no-override." + + validation { + condition = var.origin_access_control_signing_behavior == "always" || var.origin_access_control_signing_behavior == "no-override" || var.origin_access_control_signing_behavior == "never" + error_message = "The origin_access_control_signing_behavior must be one of: `always`, `no-override`, or `never`." + } +} + variable "compress" { type = bool default = true @@ -451,9 +470,10 @@ variable "custom_origins" { variable "s3_origins" { type = list(object({ - domain_name = string - origin_id = string - origin_path = string + domain_name = string + origin_id = string + origin_path = string + origin_access_control_id = string s3_origin_config = object({ origin_access_identity = string }) @@ -496,6 +516,16 @@ variable "deployment_actions" { description = "List of actions to permit `deployment_principal_arns` to perform on bucket and bucket prefixes (see `deployment_principal_arns`)" } +variable "origin_access_type" { + type = string + default = "origin_access_identity" + description = "Choose to use `origin_access_control` or `orgin_access_identity`" + validation { + condition = var.origin_access_type == "origin_access_control" || var.origin_access_type == "origin_access_identity" + error_message = "The origin_access_type must be `origin_access_control` or `origin_access_identity`." + } +} + variable "cloudfront_origin_access_identity_iam_arn" { type = string default = ""