diff --git a/README.md b/README.md index f21c87e..f091805 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_autoscaling_schedule.ecs_infrastructure_time_based_custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_schedule) | resource | | [aws_autoscaling_schedule.ecs_infrastructure_time_based_max](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_schedule) | resource | | [aws_autoscaling_schedule.ecs_infrastructure_time_based_min](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_schedule) | resource | +| [aws_cloudformation_stack.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudformation_stack) | resource | | [aws_cloudfront_cache_policy.custom_s3_buckets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_cache_policy) | resource | | [aws_cloudfront_distribution.custom_s3_buckets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource | | [aws_cloudfront_distribution.infrastructure_ecs_cluster_service_cloudfront](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource | @@ -202,6 +203,7 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_route_table.infrastructure_public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | | [aws_route_table_association.infrastructure_private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | | [aws_route_table_association.infrastructure_public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | +| [aws_s3_bucket.cloudformation_custom_stack_template_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket.infrastructure_ecs_cluster_service_alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | @@ -213,30 +215,35 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_s3_bucket_lifecycle_configuration.infrastructure_ecs_cluster_service_alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_lifecycle_configuration.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_lifecycle_configuration.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_logging.cloudformation_custom_stack_template_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_logging.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_logging.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_logging.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_logging.infrastructure_ecs_cluster_service_environment_files](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_ownership_controls.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_ownership_controls.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | +| [aws_s3_bucket_policy.cloudformation_custom_stack_template_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.infrastructure_ecs_cluster_service_alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.infrastructure_ecs_cluster_service_environment_files](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | +| [aws_s3_bucket_public_access_block.cloudformation_custom_stack_template_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.infrastructure_ecs_cluster_service_alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.infrastructure_ecs_cluster_service_environment_files](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.cloudformation_custom_stack_template_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.infrastructure_ecs_cluster_service_alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.infrastructure_ecs_cluster_service_environment_files](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_versioning.cloudformation_custom_stack_template_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_versioning.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_versioning.infrastructure_ecs_cluster_service_alb_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_versioning.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | @@ -282,6 +289,7 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_elb_service_account.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/elb_service_account) | data source | | [aws_route53_zone.root](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | | [aws_s3_object.ecs_cluster_service_buildspec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/s3_object) | data source | +| [external_external.s3_presigned_url](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | | [external_external.ssm_dhmc_setting](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | ## Inputs @@ -290,12 +298,14 @@ This project creates and manages resources within an AWS account for infrastruct |------|-------------|------|---------|:--------:| | [aws\_profile\_name\_route53\_root](#input\_aws\_profile\_name\_route53\_root) | AWS Profile name which is configured for the account in which the root Route53 Hosted Zone exists. | `string` | n/a | yes | | [aws\_region](#input\_aws\_region) | AWS region in which to launch resources | `string` | n/a | yes | +| [custom\_cloudformation\_stacks](#input\_custom\_cloudformation\_stacks) | Map of CloudFormation stacks to deploy
{
stack-name = {
s3\_template\_store\_key: The filename of a CloudFormation template that is stored within the S3 bucket, created by the `enable_cloudformatian_s3_template_store`
template\_body: (Optional - use of s3\_template\_store\_key is preferred) The CloudFormation template body
parameters: The CloudFormation template parameters ({ parameter-name = parameter-value, ... })
on\_failure: What to do on failure, either 'DO\_NOTHING', 'ROLLBACK' or 'DELETE'
capabilities: A list of capabilities. Valid values: `CAPABILITY_NAMED_IAM`, `CAPABILITY_IAM`, `CAPABILITY_AUTO_EXPAND`
}
} |
map(object({
s3_template_store_key = optional(string, null)
template_body = optional(string, null)
parameters = optional(map(string), null)
on_failure = optional(string, null)
capabilities = optional(list(string), null)
}))
| n/a | yes | | [custom\_route53\_hosted\_zones](#input\_custom\_route53\_hosted\_zones) | Map of Route53 Hosted Zone configurations to create
{
example.com = {
ns\_records: Map of NS records to create ({ "domain.example.com" = { values = ["ns1.example.com", "ns2.example.com"], ttl = 300 })
a\_records: Map of A records to create ({ "domain.example.com" = { values = ["1.2.3.4", "5.6.7.8"], ttl = 300 })
alias\_records: Map of ALIAS records to create ({ "domain.example.com" = { value = "example.cloudfront.com", zone\_id = "Z2FDTNDATAQYW2" })
cname\_records: Map of CNAME records to create ({ "domain.example.com" = { values = ["external1.example.com", "external2.example.com"], ttl = 60 })
mx\_records: Map of MX records to create ({ "example.com" = { values = ["1 mail.example.com", "5 mail2.example.com"], ttl = 60 })
txt\_records: Map of TXT records to create ({ "example.com" = { values = ["v=spf1 include:spf.example.com -all"], ttl = 60 })
}
} |
map(object({
ns_records = optional(map(object({
values = list(string)
ttl = optional(number, 300)
})), null)
a_records = optional(map(object({
values = list(string)
ttl = optional(number, 300)
})), null)
alias_records = optional(map(object({
value = string
zone_id = string
})), null)
cname_records = optional(map(object({
values = list(string)
ttl = optional(number, 300)
})), null)
mx_records = optional(map(object({
values = list(string)
ttl = optional(number, 300)
})), null)
txt_records = optional(map(object({
values = list(string)
ttl = optional(number, 300)
})), null)
}))
| n/a | yes | | [custom\_s3\_buckets](#input\_custom\_s3\_buckets) | Map of S3 buckets to create, and conditionally serve via CloudFront. The S3 configuration will follow AWS best practices (eg. Private, ACLS disabled, SSE, Versioning, Logging). The bucket must be emptied before attempting deletion/destruction."
{
bucket-name = {
create\_dedicated\_kms\_key: Conditionally create a KMS key specifically for this bucket's server side encryption (rather than using the Infrastructure's KMS key). It's recommended to use this if the S3 bucket will be accessed from external AWS accounts.
transition\_to\_ia\_days: Conditionally transition objects to 'Standard Infrequent Access' storage in N days
transition\_to\_glacier\_days: Conditionally transition objects to 'Glacier' storage in N days
cloudfront\_dedicated\_distribution: Conditionally create a CloudFront distribution to serve objects from the S3 bucket.
cloudfront\_s3\_root: Sets the S3 document root when being served from CloudFront. By default this will be '/'. If `cloudfront_infrastructure_ecs_cluster_service_path` has been set, this helps by modifying the request from `/sub-directory-path` to `/` by use of a CloudFront function.
cloudfront\_infrastructure\_ecs\_cluster\_service: Conditionally create an Origin on a CloudFront distribution that is serving the given Infrastructure ECS Cluster Service name
cloudfront\_infrastructure\_ecs\_cluster\_service\_path: If `cloudfront_infrastructure_ecs_cluster_service`, set this to the path that objects will be served from.
}
} |
map(object({
create_dedicated_kms_key = optional(bool, null)
transition_to_ia_days = optional(number, null)
transition_to_glacier_days = optional(number, null)
cloudfront_dedicated_distribution = optional(bool, null)
cloudfront_s3_root = optional(string, null)
cloudfront_infrastructure_ecs_cluster_service = optional(string, null)
cloudfront_infrastructure_ecs_cluster_service_path = optional(string, null)
}))
| n/a | yes | | [ecs\_cluster\_efs\_directories](#input\_ecs\_cluster\_efs\_directories) | ECS cluster EFS directories to create | `list(string)` | n/a | yes | | [ecs\_cluster\_efs\_infrequent\_access\_transition](#input\_ecs\_cluster\_efs\_infrequent\_access\_transition) | ECS cluser EFS IA transiton in days. Set to 0 to disable IA transition. | `number` | n/a | yes | | [ecs\_cluster\_efs\_performance\_mode](#input\_ecs\_cluster\_efs\_performance\_mode) | ECS cluser EFS performance mode | `string` | n/a | yes | | [ecs\_cluster\_efs\_throughput\_mode](#input\_ecs\_cluster\_efs\_throughput\_mode) | ECS cluser EFS throughput mode | `string` | n/a | yes | +| [enable\_cloudformatian\_s3\_template\_store](#input\_enable\_cloudformatian\_s3\_template\_store) | Creates an S3 bucket to store custom CloudFormation templates, which can then be referenced in `custom_cloudformation_stacks`. A user with RW access to the bucket is also created. | `bool` | n/a | yes | | [enable\_infrastructure\_ecs\_cluster](#input\_enable\_infrastructure\_ecs\_cluster) | Enable creation of infrastructure ECS cluster, to place ECS services | `bool` | n/a | yes | | [enable\_infrastructure\_ecs\_cluster\_efs](#input\_enable\_infrastructure\_ecs\_cluster\_efs) | Conditionally create and mount EFS to the ECS cluster instances | `bool` | n/a | yes | | [enable\_infrastructure\_ecs\_cluster\_services\_alb\_logs](#input\_enable\_infrastructure\_ecs\_cluster\_services\_alb\_logs) | Enable Infrastructure ECS cluster services ALB logs | `bool` | n/a | yes | diff --git a/cloudformation-custom-stack-s3-template-store.tf b/cloudformation-custom-stack-s3-template-store.tf new file mode 100644 index 0000000..972c900 --- /dev/null +++ b/cloudformation-custom-stack-s3-template-store.tf @@ -0,0 +1,67 @@ +resource "aws_s3_bucket" "cloudformation_custom_stack_template_store" { + count = local.enable_cloudformatian_s3_template_store ? 1 : 0 + + bucket = "${local.resource_prefix_hash}-cloudformation-custom-stack-templates" +} + +resource "aws_s3_bucket_policy" "cloudformation_custom_stack_template_store" { + count = local.enable_cloudformatian_s3_template_store ? 1 : 0 + + bucket = aws_s3_bucket.cloudformation_custom_stack_template_store[0].id + policy = templatefile( + "${path.module}/policies/s3-bucket-policy.json.tpl", + { + statement = < 0 && local.infrastructure_kms_encryption ? local.aws_account_id : "" + account_id = (local.infrastructure_vpc_flow_logs_s3_with_athena || local.enable_cloudformatian_s3_template_store || contains([for service in local.infrastructure_ecs_cluster_services : service["cloudfront_access_logging_enabled"]], true)) || length(local.custom_s3_buckets) > 0 && local.infrastructure_kms_encryption ? local.aws_account_id : "" region = local.aws_region } )} diff --git a/locals.tf b/locals.tf index 12a8567..b37f01c 100644 --- a/locals.tf +++ b/locals.tf @@ -14,7 +14,8 @@ locals { enable_infrastructure_logs_bucket = ( local.infrastructure_vpc_flow_logs_s3_with_athena || length(local.infrastructure_ecs_cluster_services) != 0 || - length(local.custom_s3_buckets) != 0 + length(local.custom_s3_buckets) != 0 || + local.enable_cloudformatian_s3_template_store ) logs_bucket_source_arns = concat( local.infrastructure_vpc_flow_logs_s3_with_athena ? ["arn:aws:logs:${local.aws_region}:${local.aws_account_id}:*"] : [], @@ -194,6 +195,13 @@ locals { custom_s3_buckets = var.custom_s3_buckets + enable_cloudformatian_s3_template_store = var.enable_cloudformatian_s3_template_store != null ? var.enable_cloudformatian_s3_template_store : false + custom_cloudformation_stacks = var.custom_cloudformation_stacks + + s3_object_presign = local.enable_cloudformatian_s3_template_store ? toset([ + for k, v in local.custom_cloudformation_stacks : "${aws_s3_bucket.cloudformation_custom_stack_template_store[0].id}/${v["s3_template_store_key"]}" if v["s3_template_store_key"] != null + ]) : [] + default_tags = { Project = local.project_name, Infrastructure = local.infrastructure_name, diff --git a/variables.tf b/variables.tf index 3e7e6ed..f595e13 100644 --- a/variables.tf +++ b/variables.tf @@ -615,3 +615,30 @@ variable "custom_s3_buckets" { cloudfront_infrastructure_ecs_cluster_service_path = optional(string, null) })) } + +variable "enable_cloudformatian_s3_template_store" { + description = "Creates an S3 bucket to store custom CloudFormation templates, which can then be referenced in `custom_cloudformation_stacks`. A user with RW access to the bucket is also created." + type = bool +} + +variable "custom_cloudformation_stacks" { + description = <