From e9e6833ca278976604d71ec8438bf727df99c8d5 Mon Sep 17 00:00:00 2001 From: Alexander Amiri Date: Tue, 17 Mar 2026 22:57:56 +0100 Subject: [PATCH] Team-prefixed resource naming + boundary enforcement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace javabin- prefix with {team}- prefix on all app resource names. Team is now part of the resource name, enabling ARN-based IAM scoping for services that don't support tag-based conditions (CloudWatch Logs, metrics, SG rules). Module changes (10 modules): - Add team variable, replace ${var.project}-${var.name} with ${var.team}-${var.name} for all resource names - Log groups: /ecs/{name} → /ecs/{team}/{name} (hierarchical) - SSM params: /javabin/apps/{service}/{name} → /javabin/apps/{team}/{service}/{name} - Remove project variable from modules that only used it for naming Registry changes: - Wire team: yaml:team to all module vars - Replace project with team for naming vars Boundary enforcement (terraform/org/, human-applied): - DenyNonTeamPrefixedCreation: blocks creating S3/DynamoDB/SQS/SNS/RDS/ ECS/ECR/Logs/SSM resources unless name matches ${aws:PrincipalTag/team}-* - Uses NotResource with policy variables for clean deny-unless-match Developer permission set (terraform/org/): - Split into tag-scoped reads (StringEquals) and ARN-scoped reads - Logs scoped to /ecs/${team}/* via ARN pattern - SSM scoped to /javabin/apps/${team}/* via ARN pattern Platform resources unchanged (keep javabin-* prefix). New resources get team prefix going forward (Option A migration). --- scripts/registry.py | 20 +++++---- terraform/modules/ecr-repo/main.tf | 4 +- terraform/modules/ecr-repo/variables.tf | 5 +++ terraform/modules/ecs-service/main.tf | 6 +-- terraform/modules/ecs-service/variables.tf | 5 +++ terraform/modules/service-alarm/main.tf | 16 +++---- terraform/modules/service-alarm/variables.tf | 4 +- terraform/modules/service-bucket/main.tf | 4 +- terraform/modules/service-bucket/variables.tf | 6 +-- terraform/modules/service-database/main.tf | 4 +- .../modules/service-database/variables.tf | 6 +-- terraform/modules/service-queue/main.tf | 8 ++-- terraform/modules/service-queue/variables.tf | 6 +-- terraform/modules/service-rds/main.tf | 20 ++++----- terraform/modules/service-rds/variables.tf | 5 +++ terraform/modules/service-role/main.tf | 6 +-- terraform/modules/service-role/variables.tf | 6 +-- terraform/modules/service-routing/main.tf | 4 +- .../modules/service-routing/variables.tf | 6 +-- terraform/modules/service-secret/main.tf | 4 +- terraform/modules/service-secret/variables.tf | 7 +++- terraform/org/boundary.tf | 42 +++++++++++++++++++ terraform/org/identity-center.tf | 37 ++++++++++++---- 23 files changed, 159 insertions(+), 72 deletions(-) diff --git a/scripts/registry.py b/scripts/registry.py index cc6d768..3fa7cff 100644 --- a/scripts/registry.py +++ b/scripts/registry.py @@ -74,6 +74,7 @@ "cardinality": "singleton", "vars": { "name": "yaml:name", + "team": "yaml:team", }, "rename": "ecr", "output_map": { @@ -93,7 +94,7 @@ "cardinality": "singleton", "vars": { "name": "yaml:name", - "project": f"const:{PROJECT}", + "team": "yaml:team", "vpc_id": "ref:platform.vpc_id", "port": "yaml:compute.port|default:8000", "health_check_path": "yaml:compute.health_check|default:/health", @@ -124,7 +125,7 @@ "cardinality": "singleton", "vars": { "name": "yaml:name", - "project": f"const:{PROJECT}", + "team": "yaml:team", "region": "env:AWS_REGION", "aws_account_id": "env:AWS_ACCOUNT_ID", "permissions_boundary_arn": "ref:platform.developer_boundary_arn", @@ -156,6 +157,7 @@ "cardinality": "singleton", "vars": { "name": "yaml:name", + "team": "yaml:team", "cluster_id": "ref:platform.ecs_cluster_id", "image": "expr:${ref:ecr.repository_url}:latest", "cpu": "yaml:compute.cpu|default:512", @@ -191,7 +193,7 @@ "condition": "yaml:alarms.enabled|default:true", "vars": { "name": "yaml:name", - "project": f"const:{PROJECT}", + "team": "yaml:team", "service": "yaml:name", "cluster_name": "ref:platform.ecs_cluster_name", "sns_topic_arns": "list:data.aws_sns_topic.alerts.arn", @@ -232,7 +234,7 @@ "instance_key": "name", "vars": { "name": "item:name", - "project": "yaml:name", + "team": "yaml:team", "aws_account_id": "env:AWS_ACCOUNT_ID", "service": "yaml:name", "versioning": "item:versioning|default:true", @@ -266,7 +268,7 @@ "engine_filter": "dynamodb", "vars": { "name": "item:name", - "project": "yaml:name", + "team": "yaml:team", "service": "yaml:name", "hash_key": "item:hash_key|default:id", "hash_key_type": "item:hash_key_type|default:S", @@ -302,7 +304,8 @@ "engine_filter": "postgres", "vars": { "name": "item:name", - "project": "yaml:name", + "team": "yaml:team", + "project": f"const:{PROJECT}", "engine_version": "item:engine_version|default:16", "instance_class": "item:instance_class|default:db.t3.micro", "allocated_storage": "item:allocated_storage|default:20", @@ -335,7 +338,8 @@ "instance_key": "name", "vars": { "name": "item:name", - "project": "yaml:name", + "team": "yaml:team", + "project": f"const:{PROJECT}", "service": "yaml:name", "description": "item:description|default:", }, @@ -358,7 +362,7 @@ "instance_key": "name", "vars": { "name": "item:name", - "project": "yaml:name", + "team": "yaml:team", "service": "yaml:name", "visibility_timeout_seconds": "item:visibility_timeout|default:30", "retention_seconds": "item:retention_seconds|default:345600", diff --git a/terraform/modules/ecr-repo/main.tf b/terraform/modules/ecr-repo/main.tf index c54904b..80b99e8 100644 --- a/terraform/modules/ecr-repo/main.tf +++ b/terraform/modules/ecr-repo/main.tf @@ -3,7 +3,7 @@ ################################################################################ resource "aws_ecr_repository" "this" { - name = var.name + name = "${var.team}-${var.name}" image_tag_mutability = "MUTABLE" image_scanning_configuration { @@ -15,7 +15,7 @@ resource "aws_ecr_repository" "this" { } tags = { - Name = var.name + Name = "${var.team}-${var.name}" } } diff --git a/terraform/modules/ecr-repo/variables.tf b/terraform/modules/ecr-repo/variables.tf index af895f8..1bfa0a4 100644 --- a/terraform/modules/ecr-repo/variables.tf +++ b/terraform/modules/ecr-repo/variables.tf @@ -3,6 +3,11 @@ variable "name" { type = string } +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" + type = string +} + variable "scan_on_push" { description = "Enable image scanning on push" type = bool diff --git a/terraform/modules/ecs-service/main.tf b/terraform/modules/ecs-service/main.tf index baa9226..75ff66f 100644 --- a/terraform/modules/ecs-service/main.tf +++ b/terraform/modules/ecs-service/main.tf @@ -3,7 +3,7 @@ ################################################################################ resource "aws_cloudwatch_log_group" "this" { - name = "/ecs/${var.name}" + name = "/ecs/${var.team}/${var.name}" retention_in_days = 30 } @@ -12,7 +12,7 @@ resource "aws_cloudwatch_log_group" "this" { ################################################################################ resource "aws_ecs_task_definition" "this" { - family = var.name + family = "${var.team}-${var.name}" requires_compatibilities = ["FARGATE"] network_mode = "awsvpc" cpu = var.cpu @@ -79,7 +79,7 @@ resource "aws_ecs_task_definition" "this" { ################################################################################ resource "aws_ecs_service" "this" { - name = var.name + name = "${var.team}-${var.name}" cluster = var.cluster_id task_definition = aws_ecs_task_definition.this.arn desired_count = var.desired_count diff --git a/terraform/modules/ecs-service/variables.tf b/terraform/modules/ecs-service/variables.tf index 1ef68a5..967f2fd 100644 --- a/terraform/modules/ecs-service/variables.tf +++ b/terraform/modules/ecs-service/variables.tf @@ -3,6 +3,11 @@ variable "name" { type = string } +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" + type = string +} + variable "cluster_id" { description = "ECS cluster ID" type = string diff --git a/terraform/modules/service-alarm/main.tf b/terraform/modules/service-alarm/main.tf index bb56b6f..5199752 100644 --- a/terraform/modules/service-alarm/main.tf +++ b/terraform/modules/service-alarm/main.tf @@ -3,7 +3,7 @@ ################################################################################ resource "aws_cloudwatch_metric_alarm" "cpu_high" { - alarm_name = "${var.project}-${var.name}-cpu-high" + alarm_name = "${var.team}-${var.name}-cpu-high" comparison_operator = "GreaterThanThreshold" evaluation_periods = 3 metric_name = "CPUUtilization" @@ -21,13 +21,13 @@ resource "aws_cloudwatch_metric_alarm" "cpu_high" { } tags = { - Name = "${var.project}-${var.name}-cpu-high" + Name = "${var.team}-${var.name}-cpu-high" service = var.service } } resource "aws_cloudwatch_metric_alarm" "memory_high" { - alarm_name = "${var.project}-${var.name}-memory-high" + alarm_name = "${var.team}-${var.name}-memory-high" comparison_operator = "GreaterThanThreshold" evaluation_periods = 3 metric_name = "MemoryUtilization" @@ -45,14 +45,14 @@ resource "aws_cloudwatch_metric_alarm" "memory_high" { } tags = { - Name = "${var.project}-${var.name}-memory-high" + Name = "${var.team}-${var.name}-memory-high" service = var.service } } resource "aws_cloudwatch_metric_alarm" "unhealthy_targets" { count = var.enable_alb_alarms ? 1 : 0 - alarm_name = "${var.project}-${var.name}-unhealthy-targets" + alarm_name = "${var.team}-${var.name}-unhealthy-targets" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 metric_name = "UnHealthyHostCount" @@ -70,14 +70,14 @@ resource "aws_cloudwatch_metric_alarm" "unhealthy_targets" { } tags = { - Name = "${var.project}-${var.name}-unhealthy-targets" + Name = "${var.team}-${var.name}-unhealthy-targets" service = var.service } } resource "aws_cloudwatch_metric_alarm" "target_5xx" { count = var.enable_alb_alarms ? 1 : 0 - alarm_name = "${var.project}-${var.name}-5xx" + alarm_name = "${var.team}-${var.name}-5xx" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 metric_name = "HTTPCode_Target_5XX_Count" @@ -96,7 +96,7 @@ resource "aws_cloudwatch_metric_alarm" "target_5xx" { } tags = { - Name = "${var.project}-${var.name}-5xx" + Name = "${var.team}-${var.name}-5xx" service = var.service } } diff --git a/terraform/modules/service-alarm/variables.tf b/terraform/modules/service-alarm/variables.tf index e98d60f..a4b7e1c 100644 --- a/terraform/modules/service-alarm/variables.tf +++ b/terraform/modules/service-alarm/variables.tf @@ -3,8 +3,8 @@ variable "name" { type = string } -variable "project" { - description = "Project name prefix" +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" type = string } diff --git a/terraform/modules/service-bucket/main.tf b/terraform/modules/service-bucket/main.tf index b1fa577..3a6db24 100644 --- a/terraform/modules/service-bucket/main.tf +++ b/terraform/modules/service-bucket/main.tf @@ -3,10 +3,10 @@ ################################################################################ resource "aws_s3_bucket" "this" { - bucket = "${var.project}-${var.name}-${var.aws_account_id}" + bucket = "${var.team}-${var.name}-${var.aws_account_id}" tags = { - Name = "${var.project}-${var.name}" + Name = "${var.team}-${var.name}" service = var.service } } diff --git a/terraform/modules/service-bucket/variables.tf b/terraform/modules/service-bucket/variables.tf index 6ba182c..bc721bc 100644 --- a/terraform/modules/service-bucket/variables.tf +++ b/terraform/modules/service-bucket/variables.tf @@ -1,10 +1,10 @@ variable "name" { - description = "Bucket purpose (e.g. 'data', 'uploads'). Bucket name: {project}-{name}-{account_id}" + description = "Bucket purpose (e.g. 'data', 'uploads'). Bucket name: {team}-{name}-{account_id}" type = string } -variable "project" { - description = "Project name prefix" +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" type = string } diff --git a/terraform/modules/service-database/main.tf b/terraform/modules/service-database/main.tf index 29561b6..0b741ed 100644 --- a/terraform/modules/service-database/main.tf +++ b/terraform/modules/service-database/main.tf @@ -3,7 +3,7 @@ ################################################################################ resource "aws_dynamodb_table" "this" { - name = "${var.project}-${var.name}" + name = "${var.team}-${var.name}" billing_mode = var.billing_mode hash_key = var.hash_key @@ -36,7 +36,7 @@ resource "aws_dynamodb_table" "this" { } tags = { - Name = "${var.project}-${var.name}" + Name = "${var.team}-${var.name}" service = var.service } } diff --git a/terraform/modules/service-database/variables.tf b/terraform/modules/service-database/variables.tf index 460bf01..3415eb0 100644 --- a/terraform/modules/service-database/variables.tf +++ b/terraform/modules/service-database/variables.tf @@ -1,10 +1,10 @@ variable "name" { - description = "Table purpose (e.g. 'sessions', 'cache'). Table name: {project}-{name}" + description = "Table purpose (e.g. 'sessions', 'cache'). Table name: {team}-{name}" type = string } -variable "project" { - description = "Project name prefix" +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" type = string } diff --git a/terraform/modules/service-queue/main.tf b/terraform/modules/service-queue/main.tf index 328a442..2060ac9 100644 --- a/terraform/modules/service-queue/main.tf +++ b/terraform/modules/service-queue/main.tf @@ -3,17 +3,17 @@ ################################################################################ resource "aws_sqs_queue" "dlq" { - name = "${var.project}-${var.name}-dlq" + name = "${var.team}-${var.name}-dlq" message_retention_seconds = var.dlq_retention_seconds tags = { - Name = "${var.project}-${var.name}-dlq" + Name = "${var.team}-${var.name}-dlq" service = var.service } } resource "aws_sqs_queue" "this" { - name = "${var.project}-${var.name}" + name = "${var.team}-${var.name}" visibility_timeout_seconds = var.visibility_timeout_seconds message_retention_seconds = var.retention_seconds @@ -23,7 +23,7 @@ resource "aws_sqs_queue" "this" { }) tags = { - Name = "${var.project}-${var.name}" + Name = "${var.team}-${var.name}" service = var.service } } diff --git a/terraform/modules/service-queue/variables.tf b/terraform/modules/service-queue/variables.tf index 0698384..d3af0bb 100644 --- a/terraform/modules/service-queue/variables.tf +++ b/terraform/modules/service-queue/variables.tf @@ -1,10 +1,10 @@ variable "name" { - description = "Queue purpose (e.g. 'tasks', 'events'). Queue name: {project}-{name}" + description = "Queue purpose (e.g. 'tasks', 'events'). Queue name: {team}-{name}" type = string } -variable "project" { - description = "Project name prefix" +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" type = string } diff --git a/terraform/modules/service-rds/main.tf b/terraform/modules/service-rds/main.tf index e2c935c..dfe0127 100644 --- a/terraform/modules/service-rds/main.tf +++ b/terraform/modules/service-rds/main.tf @@ -3,21 +3,21 @@ ################################################################################ resource "aws_db_subnet_group" "this" { - name = "${var.name}-db-subnet" + name = "${var.team}-${var.name}-db-subnet" subnet_ids = var.subnet_ids tags = merge(var.tags, { - Name = "${var.name}-db-subnet" + Name = "${var.team}-${var.name}-db-subnet" }) } resource "aws_security_group" "this" { - name = "${var.name}-rds-sg" + name = "${var.team}-${var.name}-rds-sg" description = "Allow PostgreSQL access from ECS tasks" vpc_id = var.vpc_id tags = merge(var.tags, { - Name = "${var.name}-rds-sg" + Name = "${var.team}-${var.name}-rds-sg" }) } @@ -42,13 +42,13 @@ resource "random_password" "master" { } resource "aws_ssm_parameter" "master_password" { - name = "/${var.project}/apps/${var.name}/db-master-password" - description = "RDS master password for ${var.name}" + name = "/${var.project}/apps/${var.team}/${var.name}/db-master-password" + description = "RDS master password for ${var.team}-${var.name}" type = "SecureString" value = random_password.master.result tags = merge(var.tags, { - Name = "${var.name}-db-master-password" + Name = "${var.team}-${var.name}-db-master-password" }) lifecycle { @@ -61,7 +61,7 @@ resource "aws_ssm_parameter" "master_password" { ################################################################################ resource "aws_db_instance" "this" { - identifier = var.name + identifier = "${var.team}-${var.name}" engine = "postgres" engine_version = var.engine_version instance_class = var.instance_class @@ -81,10 +81,10 @@ resource "aws_db_instance" "this" { deletion_protection = var.deletion_protection skip_final_snapshot = false - final_snapshot_identifier = "${var.name}-final" + final_snapshot_identifier = "${var.team}-${var.name}-final" tags = merge(var.tags, { - Name = var.name + Name = "${var.team}-${var.name}" }) lifecycle { diff --git a/terraform/modules/service-rds/variables.tf b/terraform/modules/service-rds/variables.tf index a90b2aa..941bffe 100644 --- a/terraform/modules/service-rds/variables.tf +++ b/terraform/modules/service-rds/variables.tf @@ -3,6 +3,11 @@ variable "name" { type = string } +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" + type = string +} + variable "project" { description = "Project name prefix (for SSM parameter path)" type = string diff --git a/terraform/modules/service-role/main.tf b/terraform/modules/service-role/main.tf index e34d4b6..19e3a74 100644 --- a/terraform/modules/service-role/main.tf +++ b/terraform/modules/service-role/main.tf @@ -3,7 +3,7 @@ ################################################################################ resource "aws_iam_role" "this" { - name = "${var.project}-${var.name}" + name = "${var.team}-${var.name}" permissions_boundary = var.permissions_boundary_arn assume_role_policy = jsonencode({ @@ -18,7 +18,7 @@ resource "aws_iam_role" "this" { }) tags = { - Name = "${var.project}-${var.name}" + Name = "${var.team}-${var.name}" } } @@ -38,7 +38,7 @@ resource "aws_iam_role_policy" "logs" { "logs:CreateLogStream", "logs:PutLogEvents" ] - Resource = "arn:aws:logs:${var.region}:${var.aws_account_id}:log-group:/ecs/${var.name}:*" + Resource = "arn:aws:logs:${var.region}:${var.aws_account_id}:log-group:/ecs/${var.team}/${var.name}:*" }] }) } diff --git a/terraform/modules/service-role/variables.tf b/terraform/modules/service-role/variables.tf index addefc7..18d258c 100644 --- a/terraform/modules/service-role/variables.tf +++ b/terraform/modules/service-role/variables.tf @@ -1,10 +1,10 @@ variable "name" { - description = "Service name (e.g. 'moresleep'). Role name: {project}-{name}" + description = "Service name (e.g. 'moresleep'). Role name: {team}-{name}" type = string } -variable "project" { - description = "Platform project name (e.g. 'javabin')" +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" type = string } diff --git a/terraform/modules/service-routing/main.tf b/terraform/modules/service-routing/main.tf index 4ca1344..39dd2a5 100644 --- a/terraform/modules/service-routing/main.tf +++ b/terraform/modules/service-routing/main.tf @@ -3,7 +3,7 @@ ################################################################################ resource "aws_lb_target_group" "this" { - name = "${var.project}-${var.name}" + name = "${var.team}-${var.name}" port = var.port protocol = "HTTP" vpc_id = var.vpc_id @@ -22,7 +22,7 @@ resource "aws_lb_target_group" "this" { } tags = { - Name = "${var.project}-${var.name}" + Name = "${var.team}-${var.name}" } } diff --git a/terraform/modules/service-routing/variables.tf b/terraform/modules/service-routing/variables.tf index 8d307bb..3a9b7c5 100644 --- a/terraform/modules/service-routing/variables.tf +++ b/terraform/modules/service-routing/variables.tf @@ -1,10 +1,10 @@ variable "name" { - description = "Service name (e.g. 'moresleep'). Target group name: {project}-{name}" + description = "Service name (e.g. 'moresleep'). Target group name: {team}-{name}" type = string } -variable "project" { - description = "Platform project name (e.g. 'javabin')" +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" type = string } diff --git a/terraform/modules/service-secret/main.tf b/terraform/modules/service-secret/main.tf index e55322a..3e68435 100644 --- a/terraform/modules/service-secret/main.tf +++ b/terraform/modules/service-secret/main.tf @@ -3,13 +3,13 @@ ################################################################################ resource "aws_ssm_parameter" "this" { - name = "/${var.project}/apps/${var.service}/${var.name}" + name = "/${var.project}/apps/${var.team}/${var.service}/${var.name}" description = var.description type = "SecureString" value = "PLACEHOLDER" tags = { - Name = "${var.project}-${var.name}" + Name = "${var.team}-${var.name}" service = var.service } diff --git a/terraform/modules/service-secret/variables.tf b/terraform/modules/service-secret/variables.tf index 77dc003..833874a 100644 --- a/terraform/modules/service-secret/variables.tf +++ b/terraform/modules/service-secret/variables.tf @@ -1,5 +1,10 @@ variable "name" { - description = "Secret purpose (e.g. 'slack-token'). Parameter name: /{project}/apps/{service}/{name}" + description = "Secret purpose (e.g. 'slack-token'). Parameter name: /{project}/apps/{team}/{service}/{name}" + type = string +} + +variable "team" { + description = "Team name, used as resource name prefix for team-scoped isolation" type = string } diff --git a/terraform/org/boundary.tf b/terraform/org/boundary.tf index e5d7181..5bca20e 100644 --- a/terraform/org/boundary.tf +++ b/terraform/org/boundary.tf @@ -238,6 +238,48 @@ resource "aws_iam_policy" "developer_boundary" { "dynamodb:UpdateTable" ] Resource = "arn:aws:dynamodb:${var.region}:${var.aws_account_id}:table/${var.project}-terraform-*" + }, + + ######################################################################## + # Enforce team-prefixed naming on resource creation + # + # Teams can only create resources with names matching their team prefix. + # Uses ${aws:PrincipalTag/team} policy variable in Resource ARNs. + # The AllowAll above permits all actions; this deny blocks creates + # that don't match the team naming convention. + # + # Not enforced for: security groups (ARN uses ID, not name), + # EC2 networking (already denied above), CloudWatch metrics (not resources). + ######################################################################## + { + Sid = "DenyNonTeamPrefixedCreation" + Effect = "Deny" + Action = [ + "s3:CreateBucket", + "dynamodb:CreateTable", + "sqs:CreateQueue", + "sns:CreateTopic", + "rds:CreateDBInstance", + "rds:CreateDBSubnetGroup", + "ecs:CreateService", + "ecs:RegisterTaskDefinition", + "ecr:CreateRepository", + "logs:CreateLogGroup", + "ssm:PutParameter", + ] + NotResource = [ + "arn:aws:s3:::$${aws:PrincipalTag/team}-*", + "arn:aws:dynamodb:${var.region}:${var.aws_account_id}:table/$${aws:PrincipalTag/team}-*", + "arn:aws:sqs:${var.region}:${var.aws_account_id}:$${aws:PrincipalTag/team}-*", + "arn:aws:sns:${var.region}:${var.aws_account_id}:$${aws:PrincipalTag/team}-*", + "arn:aws:rds:${var.region}:${var.aws_account_id}:db:$${aws:PrincipalTag/team}-*", + "arn:aws:rds:${var.region}:${var.aws_account_id}:subgrp:$${aws:PrincipalTag/team}-*", + "arn:aws:ecs:${var.region}:${var.aws_account_id}:service/*/$${aws:PrincipalTag/team}-*", + "arn:aws:ecs:${var.region}:${var.aws_account_id}:task-definition/$${aws:PrincipalTag/team}-*:*", + "arn:aws:ecr:${var.region}:${var.aws_account_id}:repository/$${aws:PrincipalTag/team}-*", + "arn:aws:logs:${var.region}:${var.aws_account_id}:log-group:/ecs/$${aws:PrincipalTag/team}/*", + "arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${var.project}/apps/$${aws:PrincipalTag/team}/*", + ] } ] }) diff --git a/terraform/org/identity-center.tf b/terraform/org/identity-center.tf index c56f1f9..d2d412a 100644 --- a/terraform/org/identity-center.tf +++ b/terraform/org/identity-center.tf @@ -82,6 +82,7 @@ resource "aws_ssoadmin_permission_set_inline_policy" "developer" { inline_policy = jsonencode({ Version = "2012-10-17" Statement = [ + # Tag-scoped reads (resources that support aws:ResourceTag) { Sid = "ReadOwnTeamResources" Effect = "Allow" @@ -103,21 +104,41 @@ resource "aws_ssoadmin_permission_set_inline_policy" "developer" { "sqs:ListQueues", "ssm:DescribeParameters", "ssm:GetParametersByPath", - "logs:GetLogEvents", - "logs:FilterLogEvents", - "logs:DescribeLogGroups", - "logs:DescribeLogStreams", - "cloudwatch:GetMetricData", "cloudwatch:DescribeAlarms", - "cloudwatch:ListMetrics", ] Resource = "*" Condition = { - StringEqualsIfExists = { + StringEquals = { "aws:ResourceTag/team" = "$${aws:PrincipalTag/team}" } } }, + # ARN-scoped reads (resources that don't support tag-based IAM conditions) + { + Sid = "ReadOwnTeamLogs" + Effect = "Allow" + Action = [ + "logs:GetLogEvents", + "logs:FilterLogEvents", + "logs:DescribeLogStreams", + ] + Resource = "arn:aws:logs:${var.region}:${var.aws_account_id}:log-group:/ecs/$${aws:PrincipalTag/team}/*" + }, + { + Sid = "DescribeLogGroups" + Effect = "Allow" + Action = ["logs:DescribeLogGroups"] + Resource = "*" + }, + { + Sid = "ReadMetrics" + Effect = "Allow" + Action = [ + "cloudwatch:GetMetricData", + "cloudwatch:ListMetrics", + ] + Resource = "*" + }, { Sid = "ReadPlatformSharedResources" Effect = "Allow" @@ -149,7 +170,7 @@ resource "aws_ssoadmin_permission_set_inline_policy" "developer" { "ssm:GetParameters", "ssm:GetParametersByPath", ] - Resource = "arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${var.project}/platform-apps/*" + Resource = "arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter/${var.project}/apps/$${aws:PrincipalTag/team}/*" }, ] })