diff --git a/README.md b/README.md index 064a6b1..911ab3b 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,8 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_iam_policy.infrastructure_ecs_cluster_service_scheduled_task_ecs_run_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.infrastructure_ecs_cluster_service_scheduled_task_pass_role_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.infrastructure_ecs_cluster_service_task_custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.infrastructure_ecs_cluster_service_task_ecs_exec_log_kms_decrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.infrastructure_ecs_cluster_service_task_ecs_exec_log_s3_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.infrastructure_ecs_cluster_service_task_execution_cloudwatch_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.infrastructure_ecs_cluster_service_task_execution_ecr_pull](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.infrastructure_ecs_cluster_service_task_execution_kms_decrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | @@ -197,6 +199,8 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_scheduled_task_ecs_run_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_scheduled_task_pass_role_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_task_custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_task_ecs_exec_log_kms_decrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_task_ecs_exec_log_s3_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_task_execution_cloudwatch_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_task_execution_ecr_pull](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_task_execution_kms_decrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | @@ -410,6 +414,7 @@ This project creates and manages resources within an AWS account for infrastruct | [infrastructure\_ecs\_cluster\_ecs\_asg\_diff\_alert\_threshold](#input\_infrastructure\_ecs\_cluster\_ecs\_asg\_diff\_alert\_threshold) | Threshold (Number of pending tasks) for the ECS cluster's Container Instance / ASG instance diff alert | `number` | n/a | yes | | [infrastructure\_ecs\_cluster\_ecs\_asg\_diff\_metric\_lambda\_log\_retention](#input\_infrastructure\_ecs\_cluster\_ecs\_asg\_diff\_metric\_lambda\_log\_retention) | Log retention for the ECS cluster Container Instance / ASG instance diff metric Lambda | `number` | n/a | yes | | [infrastructure\_ecs\_cluster\_enable\_debug\_mode](#input\_infrastructure\_ecs\_cluster\_enable\_debug\_mode) | Enable debug mode for ECS and Docker on the Infrastructure ECS. This should only be enabled when debugging (Can cause a lot of logs) | `bool` | n/a | yes | +| [infrastructure\_ecs\_cluster\_enable\_execute\_command\_logging](#input\_infrastructure\_ecs\_cluster\_enable\_execute\_command\_logging) | Enable ECS Exec logging for services within the cluster. This will log to the infrastructure logs S3 bucket | `bool` | n/a | yes | | [infrastructure\_ecs\_cluster\_instance\_type](#input\_infrastructure\_ecs\_cluster\_instance\_type) | The instance type for EC2 instances launched in the ECS cluster | `string` | n/a | yes | | [infrastructure\_ecs\_cluster\_max\_instance\_lifetime](#input\_infrastructure\_ecs\_cluster\_max\_instance\_lifetime) | Maximum lifetime in seconds of an instance within the ECS cluster | `number` | n/a | yes | | [infrastructure\_ecs\_cluster\_max\_size](#input\_infrastructure\_ecs\_cluster\_max\_size) | Maximum number of instances for the ECS cluster | `number` | n/a | yes | diff --git a/ecs-cluster-infrastructure-service.tf b/ecs-cluster-infrastructure-service.tf index a776983..46c01f7 100644 --- a/ecs-cluster-infrastructure-service.tf +++ b/ecs-cluster-infrastructure-service.tf @@ -122,6 +122,48 @@ resource "aws_iam_role_policy_attachment" "infrastructure_ecs_cluster_service_ta policy_arn = aws_iam_policy.infrastructure_ecs_cluster_service_task_ssm_create_channels[each.key].arn } +resource "aws_iam_policy" "infrastructure_ecs_cluster_service_task_ecs_exec_log_s3_write" { + for_each = { + for k, v in local.infrastructure_ecs_cluster_services : k => v if v["enable_execute_command"] == true + } + + name = "${local.resource_prefix}-${substr(sha512("ecs-cluster-service-task-${each.key}-ecs-exec-log-s3-write"), 0, 6)}" + description = "${local.resource_prefix}-ecs-cluster-service-task-${each.key}-ecs-exec-log-s3-write" + policy = templatefile("${path.root}/policies/s3-object-write.json.tpl", { + bucket_arn = aws_s3_bucket.infrastructure_logs[0].arn + path = "/ecs-exec/*" + }) +} + +resource "aws_iam_role_policy_attachment" "infrastructure_ecs_cluster_service_task_ecs_exec_log_s3_write" { + for_each = { + for k, v in local.infrastructure_ecs_cluster_services : k => v if v["enable_execute_command"] == true + } + + role = aws_iam_role.infrastructure_ecs_cluster_service_task[each.key].name + policy_arn = aws_iam_policy.infrastructure_ecs_cluster_service_task_ecs_exec_log_s3_write[each.key].arn +} + +resource "aws_iam_policy" "infrastructure_ecs_cluster_service_task_ecs_exec_log_kms_decrypt" { + for_each = { + for k, v in local.infrastructure_ecs_cluster_services : k => v if v["enable_execute_command"] == true && local.infrastructure_kms_encryption + } + + name = "${local.resource_prefix}-${substr(sha512("ecs-cluster-service-task-${each.key}-ecs-exec-log-kms-decrypt"), 0, 6)}" + description = "${local.resource_prefix}-ecs-cluster-service-task-${each.key}-ecs-exec-log-kms-decrypt" + policy = templatefile("${path.root}/policies/kms-decrypt.json.tpl", { + kms_key_arn = aws_kms_key.infrastructure[0].arn + }) +} +resource "aws_iam_role_policy_attachment" "infrastructure_ecs_cluster_service_task_ecs_exec_log_kms_decrypt" { + for_each = { + for k, v in local.infrastructure_ecs_cluster_services : k => v if v["enable_execute_command"] == true && local.infrastructure_kms_encryption + } + + role = aws_iam_role.infrastructure_ecs_cluster_service_task[each.key].name + policy_arn = aws_iam_policy.infrastructure_ecs_cluster_service_task_ecs_exec_log_kms_decrypt[each.key].arn +} + resource "aws_iam_policy" "infrastructure_ecs_cluster_service_task_custom" { for_each = merge([ for service_name, service in local.infrastructure_ecs_cluster_services : { diff --git a/ecs-cluster-infrastructure.tf b/ecs-cluster-infrastructure.tf index 9f61fd9..785132d 100644 --- a/ecs-cluster-infrastructure.tf +++ b/ecs-cluster-infrastructure.tf @@ -7,6 +7,20 @@ resource "aws_ecs_cluster" "infrastructure" { name = "containerInsights" value = "enabled" } + + dynamic "configuration" { + for_each = local.infrastructure_ecs_cluster_enable_execute_command_logging ? [1] : [] + execute_command_configuration { + kms_key_id = local.infrastructure_kms_encryption ? aws_kms_key.infrastructure[0].arn : null + logging = "OVERRIDE" + + log_configuration { + s3_bucket_encryption_enabled = true + s3_bucket_name = aws_s3_bucket.infrastructure_logs[0].id + s3_key_prefix = "ecs-exec" + } + } + } } resource "aws_iam_role" "infrastructure_ecs_cluster" { diff --git a/kms-infrastructure.tf b/kms-infrastructure.tf index 67ddcb3..a91ec14 100644 --- a/kms-infrastructure.tf +++ b/kms-infrastructure.tf @@ -39,6 +39,13 @@ resource "aws_kms_key" "infrastructure" { { log_group_arn = length(local.infrastructure_ecs_cluster_services) > 0 && local.infrastructure_kms_encryption ? "arn:aws:logs:${local.aws_region}:${local.aws_account_id}:log-group:${local.resource_prefix}-infrastructure-ecs-cluster-service-logs-*" : "" } + )}${length(local.infrastructure_ecs_cluster_services) > 0 && local.infrastructure_kms_encryption && local.infrastructure_ecs_cluster_enable_execute_command_logging ? "," : ""} + ${templatefile("${path.root}/policies/kms-key-policy-statements/role-allow-encrypt.json.tpl", + { + role_arns = jsonencode([ + for k, v in local.infrastructure_ecs_cluster_services : aws_iam_role.infrastructure_ecs_cluster_service_task_execution[k].name if v["enable_execute_command"] == true + ]) + } )}${contains([for k, v in local.custom_s3_buckets : (v["cloudfront_dedicated_distribution"] == true || v["cloudfront_infrastructure_ecs_cluster_service"] != null) && v["create_dedicated_kms_key"] == false ? true : false], true) && local.infrastructure_kms_encryption ? "," : ""} ${templatefile("${path.root}/policies/kms-key-policy-statements/cloudfront-distribution-allow.json.tpl", { diff --git a/locals.tf b/locals.tf index b319c8f..0b1cbe0 100644 --- a/locals.tf +++ b/locals.tf @@ -29,7 +29,8 @@ locals { length(local.infrastructure_ecs_cluster_services) != 0 || length(local.custom_s3_buckets) != 0 || local.enable_cloudformatian_s3_template_store || - local.enable_infrastructure_vpc_transfer_s3_bucket + local.enable_infrastructure_vpc_transfer_s3_bucket || + local.infrastructure_ecs_cluster_enable_execute_command_logging ) logs_bucket_s3_source_arns = concat( length(local.infrastructure_ecs_cluster_services) != 0 ? [aws_s3_bucket.infrastructure_ecs_cluster_service_build_pipeline_artifact_store[0].arn] : [], @@ -169,6 +170,7 @@ locals { infrastructure_ecs_cluster_ecs_asg_diff_alert_slack = var.infrastructure_ecs_cluster_ecs_asg_diff_alert_slack infrastructure_ecs_cluster_ecs_asg_diff_alert_opsgenie = var.infrastructure_ecs_cluster_ecs_asg_diff_alert_opsgenie infrastructure_ecs_cluster_enable_debug_mode = var.infrastructure_ecs_cluster_enable_debug_mode + infrastructure_ecs_cluster_enable_execute_command_logging = var.infrastructure_ecs_cluster_enable_execute_command_logging infrastructure_ecs_cluster_wafs = var.infrastructure_ecs_cluster_wafs infrastructure_ecs_cluster_enable_ssm_dhmc = local.enable_infrastructure_ecs_cluster ? data.external.ssm_dhmc_setting[0].result.setting_value != "$None" : false infrastructure_ecs_cluster_user_data = base64encode( diff --git a/policies/kms-key-policy-statements/role-allow-encrypt.json.tpl b/policies/kms-key-policy-statements/role-allow-encrypt.json.tpl new file mode 100644 index 0000000..8c95608 --- /dev/null +++ b/policies/kms-key-policy-statements/role-allow-encrypt.json.tpl @@ -0,0 +1,11 @@ +%{if role_arns != "[]"}{ + "Effect": "Allow", + "Principal": { + "AWS": ${role_arns} + }, + "Action": [ + "kms:GenerateDataKey*", + "kms:Decrypt" + ], + "Resource": "*" +}%{endif} diff --git a/policies/s3-object-write.json.tpl b/policies/s3-object-write.json.tpl new file mode 100644 index 0000000..c8899e7 --- /dev/null +++ b/policies/s3-object-write.json.tpl @@ -0,0 +1,21 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject" + ], + "Resource": [ + "${bucket_arn}${path}" + ] + }, + { + "Effect": "Allow", + "Action": "s3:GetEncryptionConfiguration", + "Resource": [ + "${bucket_arn}" + ] + } + ] +} diff --git a/variables.tf b/variables.tf index bb2dd8f..f88926f 100644 --- a/variables.tf +++ b/variables.tf @@ -418,6 +418,11 @@ variable "infrastructure_ecs_cluster_enable_debug_mode" { type = bool } +variable "infrastructure_ecs_cluster_enable_execute_command_logging" { + description = "Enable ECS Exec logging for services within the cluster. This will log to the infrastructure logs S3 bucket" + type = bool +} + variable "infrastructure_ecs_cluster_wafs" { description = "Map of WAF ACLs to craete, which can be used with service CloudFront distributions" type = map(object({