Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
33 changes: 30 additions & 3 deletions terraform/modules/service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,24 @@ No requirements.
| <a name="input_memory"></a> [memory](#input\_memory) | Amount (in MiB) of memory used by the task. | `number` | n/a | yes |
| <a name="input_platform"></a> [platform](#input\_platform) | Object representing the CDAP plaform module. | <pre>object({<br/> app = string<br/> env = string<br/> kms_alias_primary = object({ target_key_arn = string })<br/> primary_region = object({ name = string })<br/> private_subnets = map(object({ id = string }))<br/> service = string<br/> })</pre> | n/a | yes |
| <a name="input_task_role_arn"></a> [task\_role\_arn](#input\_task\_role\_arn) | ARN of the role that allows the application code in tasks to make calls to AWS services. | `string` | n/a | yes |
| <a name="input_alb_health_check"></a> [alb\_health\_check](#input\_alb\_health\_check) | Health check configuration for the ALB target group.<br/><br/>path - HTTP path to probe (default: /health)<br/>port - Port to probe. Use "traffic-port" to match the target group port<br/>matcher - HTTP response codes considered healthy (default: "200-299")<br/>interval - Seconds between health checks (default: 30)<br/>timeout - Seconds before a check times out (default: 5)<br/>healthy\_threshold - Consecutive successes to mark healthy (default: 2)<br/>unhealthy\_threshold - Consecutive failures to mark unhealthy (default: 3) | <pre>object({<br/> path = optional(string, "/health")<br/> port = optional(string, "traffic-port")<br/> protocol = optional(string, "HTTP")<br/> matcher = optional(string, "200-299")<br/> interval = optional(number, 30)<br/> timeout = optional(number, 5)<br/> healthy_threshold = optional(number, 2)<br/> unhealthy_threshold = optional(number, 3)<br/> })</pre> | `{}` | no |
| <a name="input_alb_port_name"></a> [alb\_port\_name](#input\_alb\_port\_name) | Name of the port mapping to route ALB traffic to. Must match a name in var.port\_mappings. Required when alb\_listener\_arn is set. | `string` | `null` | no |
| <a name="input_container_environment"></a> [container\_environment](#input\_container\_environment) | The environment variables to pass to the container | <pre>list(object({<br/> name = string<br/> value = string<br/> }))</pre> | `null` | no |
| <a name="input_container_secrets"></a> [container\_secrets](#input\_container\_secrets) | The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide | <pre>list(object({<br/> name = string<br/> valueFrom = string<br/> }))</pre> | `null` | no |
| <a name="input_cpu_architecture"></a> [cpu\_architecture](#input\_cpu\_architecture) | The cpu architecture needed. | `string` | `"ARM64"` | no |
| <a name="input_deployment_circuit_breaker"></a> [deployment\_circuit\_breaker](#input\_deployment\_circuit\_breaker) | Deployment circuit breaker configuration. Stops a failing deployment. Set rollback = true to automatically revert to the previous task definition on failure. | <pre>object({<br/> enable = optional(bool, true)<br/> rollback = optional(bool, false)<br/> })</pre> | `{}` | no |
| <a name="input_desired_count"></a> [desired\_count](#input\_desired\_count) | Number of instances of the task definition to place and keep running. | `number` | `1` | no |
| <a name="input_enable_ecs_service_connect"></a> [enable\_ecs\_service\_connect](#input\_enable\_ecs\_service\_connect) | Enables ECS Service Connect so other services in the namespace can reach this one. | `bool` | `false` | no |
| <a name="input_execution_role_arn"></a> [execution\_role\_arn](#input\_execution\_role\_arn) | ARN of the role that grants Fargate agents permission to make AWS API calls to pull images for containers, get SSM params in the task definition, etc. Defaults to creation of a new role. | `string` | `null` | no |
| <a name="input_force_new_deployment"></a> [force\_new\_deployment](#input\_force\_new\_deployment) | When *changed* to `true`, trigger a new deployment of the ECS Service even when a deployment wouldn't otherwise be triggered by other changes. **Note**: This has no effect when the value is `false`, changed to `false`, or set to `true` between consecutive applies. | `bool` | `false` | no |
| <a name="input_health_check"></a> [health\_check](#input\_health\_check) | Health check that monitors the service. | <pre>object({<br/> command = list(string),<br/> interval = optional(number),<br/> retries = optional(number),<br/> startPeriod = optional(number),<br/> timeout = optional(number)<br/> })</pre> | `null` | no |
| <a name="input_health_check_grace_period_seconds"></a> [health\_check\_grace\_period\_seconds](#input\_health\_check\_grace\_period\_seconds) | Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers. | `number` | `null` | no |
| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | Load balancer(s) for use by the AWS ECS service. | <pre>list(object({<br/> target_group_arn = string<br/> container_name = string<br/> container_port = number<br/> }))</pre> | `[]` | no |
| <a name="input_ignore_desired_count_changes"></a> [ignore\_desired\_count\_changes](#input\_ignore\_desired\_count\_changes) | When true, Terraform will not revert desired\_count to the configured value on apply.<br/>Enable this when using Application Auto Scaling to manage task count at runtime. | `bool` | `false` | no |
| <a name="input_mount_points"></a> [mount\_points](#input\_mount\_points) | The mount points for data volumes in your container | <pre>list(object({<br/> containerPath = optional(string)<br/> readOnly = optional(bool)<br/> sourceVolume = optional(string)<br/> }))</pre> | `null` | no |
| <a name="input_port_mappings"></a> [port\_mappings](#input\_port\_mappings) | The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort | <pre>list(object({<br/> appProtocol = optional(string)<br/> containerPort = optional(number)<br/> containerPortRange = optional(string)<br/> hostPort = optional(number)<br/> name = optional(string)<br/> protocol = optional(string)<br/> }))</pre> | `null` | no |
| <a name="input_security_groups"></a> [security\_groups](#input\_security\_groups) | List of security groups to associate with the service. | `list(string)` | `[]` | no |
| <a name="input_service_connect_namespace"></a> [service\_connect\_namespace](#input\_service\_connect\_namespace) | AWS Cloud Map namespace ARN for Service Connect. Must be associated with the ECS cluster. | `string` | `null` | no |
| <a name="input_service_connect_port"></a> [service\_connect\_port](#input\_service\_connect\_port) | Defaults to the first containerPort in port\_mappings. Override this for port remapping (e.g. expose on :80 while container listens on :8080). | `number` | `null` | no |
| <a name="input_service_name_override"></a> [service\_name\_override](#input\_service\_name\_override) | Desired service name for the service tag on the aws ecs service. Defaults to var.platform.app-var.platform.env-var.platform.service. | `string` | `null` | no |
| <a name="input_subnets"></a> [subnets](#input\_subnets) | Optional list of subnets associated with the service. Defaults to private subnets as specified by the platform module. | `list(string)` | `null` | no |
| <a name="input_volumes"></a> [volumes](#input\_volumes) | Configuration block for volumes that containers in your task may use | <pre>list(object({<br/> configure_at_launch = optional(bool)<br/> efs_volume_configuration = optional(object({<br/> authorization_config = optional(object({<br/> access_point_id = optional(string)<br/> iam = optional(string)<br/> }))<br/> file_system_id = string<br/> root_directory = optional(string)<br/> }))<br/> host_path = optional(string)<br/> name = string<br/> }))</pre> | `null` | no |
Expand All @@ -148,9 +155,24 @@ No modules.
|------|------|
| [aws_ecs_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource |
| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource |
| [aws_iam_policy.service_connect_kms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_policy.service_connect_pca](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_policy.service_connect_secrets_manager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.service-connect](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.service_connect](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy.service_connect](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy_attachment.service-connect](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_lb_listener_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource |
| [aws_lb_target_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource |
| [aws_iam_policy_document.execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.kms](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.service_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.service_connect](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.service_connect_pca](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.service_connect_secrets_manager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_ram_resource_share.pace_ca](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ram_resource_share) | data source |

<!--WARNING: GENERATED CONTENT with terraform-docs, e.g.
'terraform-docs --config "$(git rev-parse --show-toplevel)/.terraform-docs.yml" .'
Expand All @@ -161,6 +183,11 @@ No modules.

| Name | Description |
|------|-------------|
| <a name="output_service"></a> [service](#output\_service) | The ecs service for the given inputs. |
| <a name="output_task_definition"></a> [task\_definition](#output\_task\_definition) | The ecs task definition for the given inputs. |
| <a name="output_ecs_service_id"></a> [ecs\_service\_id](#output\_ecs\_service\_id) | ID of the ECS service. |
| <a name="output_ecs_service_name"></a> [ecs\_service\_name](#output\_ecs\_service\_name) | Full name of the ECS service. |
| <a name="output_listener_rule_arn"></a> [listener\_rule\_arn](#output\_listener\_rule\_arn) | ARN of the ALB listener rule (if ALB integration is enabled). |
| <a name="output_service"></a> [service](#output\_service) | The ECS service resource. |
| <a name="output_service_connect_role_arn"></a> [service\_connect\_role\_arn](#output\_service\_connect\_role\_arn) | ARN of the Service Connect IAM role (if Service Connect is enabled). |
| <a name="output_target_group_arn"></a> [target\_group\_arn](#output\_target\_group\_arn) | ARN of the ALB target group (if ALB integration is enabled). |
| <a name="output_task_definition"></a> [task\_definition](#output\_task\_definition) | The ECS task definition resource. |
<!-- END_TF_DOCS -->
134 changes: 134 additions & 0 deletions terraform/modules/service/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# --------------------------------
# Task Role IAM handled externally
#---------------------------------

# --------------------
# Execution Role IAM
#----------------------

resource "aws_iam_role" "execution" {
count = var.execution_role_arn != null ? 0 : 1
name = "${local.service_name_full}-execution"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
},
]
})
}

resource "aws_iam_role_policy" "execution" {
count = var.execution_role_arn != null ? 0 : 1
name = "${aws_ecs_task_definition.this.family}-execution"
role = aws_iam_role.execution[0].name
policy = data.aws_iam_policy_document.execution[0].json
}

data "aws_iam_policy_document" "execution" {
count = var.execution_role_arn != null ? 0 : 1
statement {
actions = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ssm:GetParameters"
]
resources = ["*"]
}

statement {
actions = [
"kms:Decrypt"
]
resources = [var.platform.kms_alias_primary.target_key_arn]
effect = "Allow"
}
}

# -------------------------
# Service Connect Role IAM
#---------------------------

resource "aws_iam_role_policy_attachment" "service-connect" {
role = aws_iam_role.service_connect.name
policy_arn = aws_iam_role_policy.service_connect.arn
}

resource "aws_iam_role" "service_connect" {
count = var.enable_ecs_service_connect ? 1 : 0
name = "${local.service_name_full}-service-connect"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ecs-tasks.amazonaws.com" }
}]
})
}

# encrypted certificate lifecycle and storage
resource "aws_iam_role_policy" "service_connect" {
count = var.enable_ecs_service_connect ? 1 : 0
name = "${local.service_name_full}-service-connect"
role = aws_iam_role.service_connect[0].name
policy = data.aws_iam_policy_document.service_connect.json
}

data "aws_iam_policy_document" "service_connect" {
statement {
sid = "AllowPCAUse"
actions = [
"acm-pca:GetCertificate",
"acm-pca:GetCertificateAuthorityCertificate",
"acm-pca:DescribeCertificateAuthority",
"acm-pca:IssueCertificate"
]
resources = [data.aws_ram_resource_share.pace_ca.resource_arns]
}

statement {
sid = "AllowCertManagement"
actions = [
"acm:ExportCertificate",
"acm:DescribeCertificate",
"acm:GetCertificate"
]
resources = ["arn:aws:acm:${var.platform.primary_region.name}:${var.platform.account_id}:certificate/*"]
}

statement {
actions = [
"secretsmanager:CreateSecret",
"secretsmanager:TagResource",
"secretsmanager:DescribeSecret",
"secretsmanager:UpdateSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:DeleteSecret",
"secretsmanager:RotateSecret",
"secretsmanager:UpdateSecretVersionStage"
]
resources = ["arn:aws:secretsmanager:${var.platform.primary_region.name}:${data.aws_caller_identity.current.account_id}:secret:ecs-sc!*"]
}

statement {
sid = "AllowKMSDecrypt"
actions = [
"kms:Decrypt",
"kms:GenerateDataKey"
]
resources = [var.platform.kms_alias_primary.target_key_arn]
}
}
23 changes: 23 additions & 0 deletions terraform/modules/service/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
locals {
service_name = coalesce(var.service_name_override, var.platform.service)
service_name_full = "${var.platform.app}-${var.platform.env}-${var.platform.service}"

# Build a name β†’ containerPort lookup from port_mappings
port_map = {
for pm in coalesce(var.port_mappings, []) :
pm.name => pm.containerPort
if pm.name != null && pm.containerPort != null
}

sc_port_name = coalesce(
var.service_connect_port_name,
try([for pm in coalesce(var.port_mappings, []) : pm.name if pm.name != null][0], null)
)

# ALB integration is active when a listener ARN is provided
enable_alb_integration = var.alb_listener_arn != null

# Resolve the ALB target port by name β€” caller must provide alb_port_name if using ALB
alb_container_port = local.enable_alb_integration ? local.port_map[var.alb_port_name] : null
}

Loading
Loading