diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl index 3632b7b..0bde5bd 100644 --- a/.terraform.lock.hcl +++ b/.terraform.lock.hcl @@ -22,7 +22,7 @@ provider "registry.terraform.io/hashicorp/archive" { provider "registry.terraform.io/hashicorp/aws" { version = "5.31.0" - constraints = ">= 5.24.0" + constraints = ">= 5.30.0" hashes = [ "h1:2eauBmfftzGMpzFQn9aHSXiyaO3Ve5cnihmXcKGGpgU=", "h1:5LaKSMz2FKj/QDz2a+06SHFRu59dMHrarLd1ODzhPMQ=", @@ -55,3 +55,22 @@ provider "registry.terraform.io/hashicorp/aws" { "zh:e3127ebd2cb0374cd1808f911e6bffe2f4ac4d84317061381242353f3a7bc27d", ] } + +provider "registry.terraform.io/hashicorp/external" { + version = "2.3.2" + hashes = [ + "h1:cy50n4q+Ir4GYppAfuYhQbBJVxMZbJUlIvM6FVK2axs=", + "zh:020bf652739ecd841d696e6c1b85ce7dd803e9177136df8fb03aa08b87365389", + "zh:0c7ea5a1cbf2e01a8627b8a84df69c93683f39fe947b288e958e72b9d12a827f", + "zh:25a68604c7d6aa736d6e99225051279eaac3a7cf4cab33b00ff7eae7096166f6", + "zh:34f46d82ca34604f6522de3b36eda19b7ad3be1e38947afc6ac31656eab58c8a", + "zh:6959f8f2f3de93e61e0abb90dbec41e28a66daec1607c46f43976bd6da50bcfd", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a81e5d65a343da9caa6f1d17ae0aced9faecb36b4f8554bd445dbd4f8be21ab6", + "zh:b1d3f1557214d652c9120862ce27e9a7b61cb5aec5537a28240a5a37bf0b1413", + "zh:b71588d006471ae2d4a7eca2c51d69fd7c5dec9b088315599b794e2ad0cc5e90", + "zh:cfdaae4028b644dff3530c77b49d31f7e6f4c4e2a9e5c8ac6a88e383c80c9e9c", + "zh:dbde15154c2eb38a5f54d0e7646bc67510004179696f3cc2bc1d877cecacf83b", + "zh:fb681b363f83fb5f64dfa6afbf32d100d0facd2a766cf3493b8ddb0398e1b0f7", + ] +} diff --git a/README.md b/README.md index 106087f..4b0ff43 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This project creates and manages resources within an AWS account for infrastruct | [terraform](#requirement\_terraform) | >= 1.6.5 | | [archive](#requirement\_archive) | >= 2.4.1 | | [aws](#requirement\_aws) | >= 5.30.0 | +| [external](#requirement\_external) | >= 2.3.2 | ## Providers @@ -21,6 +22,7 @@ This project creates and manages resources within an AWS account for infrastruct | [archive](#provider\_archive) | 2.4.1 | | [aws](#provider\_aws) | 5.31.0 | | [aws.awsroute53root](#provider\_aws.awsroute53root) | 5.31.0 | +| [external](#provider\_external) | 2.3.2 | ## Resources @@ -51,6 +53,8 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_iam_policy.infrastructure_ecs_cluster_autoscaling_lifecycle_termination_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.infrastructure_ecs_cluster_autoscaling_lifecycle_termination_sns_publish](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.infrastructure_ecs_cluster_ec2_ecs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.infrastructure_ecs_cluster_pass_role_ssm_dhmc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.infrastructure_ecs_cluster_ssm_service_setting_rw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.ecs_cluster_infrastructure_draining_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.infrastructure_ecs_cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.infrastructure_ecs_cluster_autoscaling_lifecycle_termination](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | @@ -63,6 +67,8 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_autoscaling_lifecycle_termination_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_autoscaling_lifecycle_termination_sns_publish](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_ec2_ecs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_pass_role_ssm_dhmc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_ssm_service_setting_rw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_internet_gateway.infrastructure_public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource | | [aws_kms_alias.infrastructure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | | [aws_kms_key.infrastructure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | @@ -116,6 +122,7 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_ami.ecs_cluster_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_route53_zone.root](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | +| [external_external.ssm_dhmc_setting](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | ## Inputs diff --git a/data.tf b/data.tf index 921742f..5ecbf9b 100644 --- a/data.tf +++ b/data.tf @@ -26,3 +26,16 @@ data "aws_ami" "ecs_cluster_ami" { ] } } + +# aws_ssm_service_setting doesn't yet have a data source, so we need to use +# a script to retrieve SSM service settings +# https://github.com/hashicorp/terraform-provider-aws/issues/25170 +data "external" "ssm_dhmc_setting" { + count = local.enable_infrastructure_ecs_cluster ? 1 : 0 + + program = ["/bin/bash", "external-data-scripts/get-ssm-service-setting.sh"] + + query = { + setting_id = "arn:aws:ssm:${local.aws_region}:${local.aws_account_id}:servicesetting/ssm/managed-instance/default-ec2-instance-management-role" + } +} diff --git a/ecs-cluster-infrastructure.tf b/ecs-cluster-infrastructure.tf index 8a06833..d91e3c1 100644 --- a/ecs-cluster-infrastructure.tf +++ b/ecs-cluster-infrastructure.tf @@ -138,6 +138,43 @@ resource "aws_iam_role_policy_attachment" "infrastructure_ecs_cluster_ec2_ecs" { policy_arn = aws_iam_policy.infrastructure_ecs_cluster_ec2_ecs[0].arn } +resource "aws_iam_policy" "infrastructure_ecs_cluster_ssm_service_setting_rw" { + count = local.infrastructure_ecs_cluster_enable_ssm_dhmc ? 1 : 0 + + name = "${local.resource_prefix}-ssm-service-setting-rw" + policy = templatefile( + "${path.root}/policies/ssm-service-setting-rw.json.tpl", + { ssm_service_setting_arn = data.external.ssm_dhmc_setting[0].result.arn } + ) +} + +resource "aws_iam_role_policy_attachment" "infrastructure_ecs_cluster_ssm_service_setting_rw" { + count = local.infrastructure_ecs_cluster_enable_ssm_dhmc ? 1 : 0 + + role = aws_iam_role.infrastructure_ecs_cluster[0].name + policy_arn = aws_iam_policy.infrastructure_ecs_cluster_ssm_service_setting_rw[0].arn +} + +resource "aws_iam_policy" "infrastructure_ecs_cluster_pass_role_ssm_dhmc" { + count = local.infrastructure_ecs_cluster_enable_ssm_dhmc ? 1 : 0 + + name = "${local.resource_prefix}-pass-role-ssm-dhmc" + policy = templatefile( + "${path.root}/policies/pass-role.json.tpl", + { + role_arn = "arn:aws:iam::${local.aws_account_id}:role/${data.external.ssm_dhmc_setting[0].result.setting_value}", + service = "ssm.amazonaws.com" + } + ) +} + +resource "aws_iam_role_policy_attachment" "infrastructure_ecs_cluster_pass_role_ssm_dhmc" { + count = local.infrastructure_ecs_cluster_enable_ssm_dhmc ? 1 : 0 + + role = aws_iam_role.infrastructure_ecs_cluster[0].name + policy_arn = aws_iam_policy.infrastructure_ecs_cluster_pass_role_ssm_dhmc[0].arn +} + resource "aws_iam_instance_profile" "infrastructure_ecs_cluster" { count = local.enable_infrastructure_ecs_cluster ? 1 : 0 @@ -294,6 +331,12 @@ resource "aws_autoscaling_group" "infrastructure_ecs_cluster" { "WarmPoolTotalCapacity", "WarmPoolWarmedCapacity", ] + + depends_on = [ + aws_iam_role_policy_attachment.infrastructure_ecs_cluster_ec2_ecs, + aws_iam_role_policy_attachment.infrastructure_ecs_cluster_ssm_service_setting_rw, + aws_iam_role_policy_attachment.infrastructure_ecs_cluster_pass_role_ssm_dhmc, + ] } resource "aws_sns_topic" "infrastructure_ecs_cluster_autoscaling_lifecycle_termination" { diff --git a/external-data-scripts/get-ssm-service-setting.sh b/external-data-scripts/get-ssm-service-setting.sh new file mode 100755 index 0000000..5bc6a77 --- /dev/null +++ b/external-data-scripts/get-ssm-service-setting.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e +set -o pipefail + +eval "$(jq -r '@sh "SSM_SERVICE_SETTING_ID=\(.setting_id)"')" + +SERVICE_SETTING="$(aws ssm get-service-setting --setting-id "$SSM_SERVICE_SETTING_ID" | jq -cr '.ServiceSetting')" + +jq -ncr --argjson service_setting "$SERVICE_SETTING" \ + '$service_setting | { + setting_id: .SettingId, + setting_value: .SettingValue, + last_modified_date: .LastModifiedDate, + last_modified_user: .LastModifiedUser, + arn: .ARN, + status: .Status + }' diff --git a/locals.tf b/locals.tf index ca45ce5..d99f294 100644 --- a/locals.tf +++ b/locals.tf @@ -112,6 +112,7 @@ locals { infrastructure_ecs_cluster_autoscaling_time_based_custom = { for custom in toset(var.infrastructure_ecs_cluster_autoscaling_time_based_custom) : "${custom["min"]}-${custom["max"]} ${custom["cron"]}" => custom } + infrastructure_ecs_cluster_enable_ssm_dhmc = local.enable_infrastructure_ecs_cluster ? data.external.ssm_dhmc_setting[0].result.setting_value != "$None" : "" infrastructure_ecs_cluster_user_data = base64encode( templatefile("ec2-userdata/ecs-instance.tpl", { docker_storage_volume_device_name = local.infrastructure_ecs_cluster_ebs_docker_storage_volume_device_name, diff --git a/policies/pass-role.json.tpl b/policies/pass-role.json.tpl new file mode 100644 index 0000000..2d3d8f4 --- /dev/null +++ b/policies/pass-role.json.tpl @@ -0,0 +1,19 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:PassRole" + ], + "Resource": "${role_arn}", + "Condition": { + "StringEquals": { + "iam:PassedToService": [ + "${service}" + ] + } + } + } + ] +} diff --git a/policies/ssm-service-setting-rw.json.tpl b/policies/ssm-service-setting-rw.json.tpl new file mode 100644 index 0000000..3d0547f --- /dev/null +++ b/policies/ssm-service-setting-rw.json.tpl @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ssm:GetServiceSetting", + "ssm:ResetServiceSetting", + "ssm:UpdateServiceSetting" + ], + "Resource": "${ssm_service_setting_arn}" + } + ] +} diff --git a/versions.tf b/versions.tf index de5eeef..76f3905 100644 --- a/versions.tf +++ b/versions.tf @@ -9,5 +9,9 @@ terraform { source = "hashicorp/archive" version = ">= 2.4.1" } + external = { + source = "hashicorp/external" + version = ">= 2.3.2" + } } }