From 5df3501128191d12e7d98254f558b51662951fcf Mon Sep 17 00:00:00 2001 From: Alexander Amiri Date: Tue, 17 Mar 2026 22:35:38 +0100 Subject: [PATCH] Replace boundary resource with data source in platform The boundary resource was moved to terraform/org/ in #82 and the state migration is complete (import to org state + state rm from platform). Replace the resource with a data source so CI reads the existing policy instead of trying to recreate it. --- terraform/platform/iam/boundary.tf | 241 +---------------------------- terraform/platform/iam/main.tf | 22 +-- terraform/platform/iam/outputs.tf | 2 +- 3 files changed, 19 insertions(+), 246 deletions(-) diff --git a/terraform/platform/iam/boundary.tf b/terraform/platform/iam/boundary.tf index 65fca8c..9f1cc98 100644 --- a/terraform/platform/iam/boundary.tf +++ b/terraform/platform/iam/boundary.tf @@ -1,239 +1,12 @@ ################################################################################ -# Permission Boundary: javabin-developer-boundary +# Permission Boundary: javabin-developer-boundary (data source) # -# MIGRATION NOTE: This resource is being moved to terraform/org/boundary.tf -# because the boundary's self-protection (DenyBoundaryTampering) prevents -# the CI pipeline from modifying it. See docs/org-runbook.md for migration steps. -# -# Once the state migration is complete, this file will be replaced with a -# data source reference. Until then, keep this resource to avoid CI destroying it. +# The boundary resource lives in terraform/org/boundary.tf (human-applied) +# because the boundary's self-protection prevents CI from modifying it. +# This data source reads the existing policy so platform resources can +# reference its ARN. ################################################################################ -resource "aws_iam_policy" "developer_boundary" { - name = "${var.project}-developer-boundary" - description = "Permission boundary for all non-platform roles. Self-replicating: roles with this boundary can only create roles that also carry it." - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - ######################################################################## - # Allow everything not explicitly denied below - ######################################################################## - { - Sid = "AllowAll" - Effect = "Allow" - Action = "*" - Resource = "*" - }, - - ######################################################################## - # Self-replicating: deny creating/modifying roles without this boundary - ######################################################################## - { - Sid = "DenyRolesWithoutBoundary" - Effect = "Deny" - Action = [ - "iam:CreateRole", - "iam:PutRolePermissionsBoundary" - ] - Resource = "*" - Condition = { - StringNotEquals = { - "iam:PermissionsBoundary" = "arn:aws:iam::${var.aws_account_id}:policy/${var.project}-developer-boundary" - } - } - }, - - ######################################################################## - # Deny creating IAM users and access keys (console/programmatic) - ######################################################################## - { - Sid = "DenyIAMUserCreation" - Effect = "Deny" - Action = [ - "iam:CreateUser", - "iam:CreateLoginProfile", - "iam:UpdateLoginProfile", - "iam:CreateAccessKey", - "iam:DeleteAccountPasswordPolicy", - "iam:CreateVirtualMFADevice", - "iam:DeactivateMFADevice" - ] - Resource = "*" - }, - - ######################################################################## - # Deny modifying or deleting this boundary policy itself - ######################################################################## - { - Sid = "DenyBoundaryTampering" - Effect = "Deny" - Action = [ - "iam:DeletePolicy", - "iam:DeletePolicyVersion", - "iam:CreatePolicyVersion", - "iam:SetDefaultPolicyVersion" - ] - Resource = "arn:aws:iam::${var.aws_account_id}:policy/${var.project}-developer-boundary" - }, - { - Sid = "DenyDeleteRoleBoundary" - Effect = "Deny" - Action = [ - "iam:DeleteRolePermissionsBoundary" - ] - Resource = "*" - }, - - ######################################################################## - # Deny IAM Identity Center, Organizations, SCPs - ######################################################################## - { - Sid = "DenyIdentityCenterAndOrgs" - Effect = "Deny" - Action = [ - "organizations:*", - "sso:*", - "sso-directory:*", - "identitystore:*", - "account:*" - ] - Resource = "*" - }, - - ######################################################################## - # Deny disabling/deleting protective services - ######################################################################## - { - Sid = "DenyProtectiveServicesTampering" - Effect = "Deny" - Action = [ - "guardduty:DeleteDetector", - "guardduty:DeleteMembers", - "guardduty:DisassociateFromMasterAccount", - "guardduty:UpdateDetector", - "config:DeleteConfigurationRecorder", - "config:StopConfigurationRecorder", - "config:DeleteDeliveryChannel", - "config:DeleteRetentionConfiguration", - "securityhub:DisableSecurityHub", - "securityhub:DeleteMembers", - "securityhub:DisassociateFromMasterAccount", - "cloudtrail:DeleteTrail", - "cloudtrail:StopLogging", - "cloudtrail:UpdateTrail", - "cloudtrail:PutEventSelectors" - ] - Resource = "*" - }, - - ######################################################################## - # Deny platform networking: VPC, subnets, IGW, NAT gateway - ######################################################################## - { - Sid = "DenyPlatformNetworking" - Effect = "Deny" - Action = [ - "ec2:CreateVpc", - "ec2:DeleteVpc", - "ec2:ModifyVpcAttribute", - "ec2:CreateSubnet", - "ec2:DeleteSubnet", - "ec2:CreateInternetGateway", - "ec2:DeleteInternetGateway", - "ec2:AttachInternetGateway", - "ec2:DetachInternetGateway", - "ec2:CreateNatGateway", - "ec2:DeleteNatGateway", - "ec2:CreateRouteTable", - "ec2:DeleteRouteTable" - ] - Resource = "*" - }, - - ######################################################################## - # Protect platform security groups - ######################################################################## - { - Sid = "DenyPlatformSecurityGroups" - Effect = "Deny" - Action = [ - "ec2:DeleteSecurityGroup", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:RevokeSecurityGroupIngress", - "ec2:AuthorizeSecurityGroupEgress", - "ec2:RevokeSecurityGroupEgress", - "ec2:ModifySecurityGroupRules", - ] - Resource = "arn:aws:ec2:${var.region}:${var.aws_account_id}:security-group/*" - Condition = { - StringLike = { - "ec2:ResourceTag/Name" = "${var.project}-*" - } - } - }, - - ######################################################################## - # Deny platform ECS cluster, ALB, ACM certs - ######################################################################## - { - Sid = "DenyPlatformECSCluster" - Effect = "Deny" - Action = [ - "ecs:DeleteCluster", - "ecs:UpdateCluster" - ] - Resource = "arn:aws:ecs:${var.region}:${var.aws_account_id}:cluster/${var.project}-platform" - }, - { - Sid = "DenyPlatformALB" - Effect = "Deny" - Action = [ - "elasticloadbalancing:DeleteLoadBalancer", - "elasticloadbalancing:ModifyLoadBalancerAttributes" - ] - Resource = "arn:aws:elasticloadbalancing:${var.region}:${var.aws_account_id}:loadbalancer/app/${var.project}-*" - }, - { - Sid = "DenyPlatformACM" - Effect = "Deny" - Action = [ - "acm:DeleteCertificate" - ] - Resource = "arn:aws:acm:${var.region}:${var.aws_account_id}:certificate/*" - }, - - ######################################################################## - # Deny access to state and CI artifact buckets/tables - ######################################################################## - { - Sid = "DenyStateBuckets" - Effect = "Deny" - Action = [ - "s3:DeleteBucket", - "s3:PutBucketPolicy", - "s3:DeleteBucketPolicy", - "s3:PutBucketVersioning", - "s3:PutEncryptionConfiguration" - ] - Resource = [ - "arn:aws:s3:::${var.project}-terraform-*", - "arn:aws:s3:::${var.project}-ci-*" - ] - }, - { - Sid = "DenyStateTables" - Effect = "Deny" - Action = [ - "dynamodb:DeleteTable", - "dynamodb:UpdateTable" - ] - Resource = "arn:aws:dynamodb:${var.region}:${var.aws_account_id}:table/${var.project}-terraform-*" - } - ] - }) - - tags = { - Name = "${var.project}-developer-boundary" - } +data "aws_iam_policy" "developer_boundary" { + name = "${var.project}-developer-boundary" } diff --git a/terraform/platform/iam/main.tf b/terraform/platform/iam/main.tf index e357bdb..732032b 100644 --- a/terraform/platform/iam/main.tf +++ b/terraform/platform/iam/main.tf @@ -14,9 +14,9 @@ data "aws_iam_openid_connect_provider" "github" { ################################################################################ # Permission Boundary Reference # -# The javabin-developer-boundary policy is defined in boundary.tf (written by -# Agent A). We reference it here so all CI roles can attach it. The resource -# aws_iam_policy.developer_boundary must exist in the same module. +# The boundary policy lives in terraform/org/boundary.tf (human-applied) +# because the boundary's self-protection prevents CI from modifying it. +# We read it via data source in boundary.tf. ################################################################################ ################################################################################ @@ -34,7 +34,7 @@ data "aws_iam_openid_connect_provider" "github" { resource "aws_iam_role" "ci_infra_plan" { name = "${var.project}-ci-infra-plan" - permissions_boundary = aws_iam_policy.developer_boundary.arn + permissions_boundary = data.aws_iam_policy.developer_boundary.arn assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -146,7 +146,7 @@ resource "aws_iam_role_policy" "ci_infra_plan_extras" { resource "aws_iam_role" "ci_infra" { name = "${var.project}-ci-infra" - permissions_boundary = aws_iam_policy.developer_boundary.arn + permissions_boundary = data.aws_iam_policy.developer_boundary.arn assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -265,7 +265,7 @@ resource "aws_iam_role_policy" "ci_infra_deny" { resource "aws_iam_role" "ci_app_broker" { name = "${var.project}-ci-app-broker" - permissions_boundary = aws_iam_policy.developer_boundary.arn + permissions_boundary = data.aws_iam_policy.developer_boundary.arn assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -321,7 +321,7 @@ resource "aws_iam_role" "ci_team" { for_each = toset(var.registered_teams) name = "${var.project}-ci-team-${each.key}" - permissions_boundary = aws_iam_policy.developer_boundary.arn + permissions_boundary = data.aws_iam_policy.developer_boundary.arn assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -531,7 +531,7 @@ resource "aws_iam_role" "ci_deploy" { for_each = toset(var.registered_teams) name = "${var.project}-ci-deploy-${each.key}" - permissions_boundary = aws_iam_policy.developer_boundary.arn + permissions_boundary = data.aws_iam_policy.developer_boundary.arn assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -682,7 +682,7 @@ resource "aws_iam_role_policy" "ci_deploy_ssm" { resource "aws_iam_role" "ci_override_approver" { name = "${var.project}-ci-override-approver" - permissions_boundary = aws_iam_policy.developer_boundary.arn + permissions_boundary = data.aws_iam_policy.developer_boundary.arn assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -737,7 +737,7 @@ resource "aws_iam_role_policy" "ci_override_approver" { resource "aws_iam_role" "ci_registry" { name = "${var.project}-ci-registry" - permissions_boundary = aws_iam_policy.developer_boundary.arn + permissions_boundary = data.aws_iam_policy.developer_boundary.arn assume_role_policy = jsonencode({ Version = "2012-10-17" @@ -801,7 +801,7 @@ resource "aws_iam_role_policy" "ci_registry" { resource "aws_iam_role" "ci_apply_gate" { name = "${var.project}-ci-apply-gate" - permissions_boundary = aws_iam_policy.developer_boundary.arn + permissions_boundary = data.aws_iam_policy.developer_boundary.arn assume_role_policy = jsonencode({ Version = "2012-10-17" diff --git a/terraform/platform/iam/outputs.tf b/terraform/platform/iam/outputs.tf index ff6098e..7ab1a15 100644 --- a/terraform/platform/iam/outputs.tf +++ b/terraform/platform/iam/outputs.tf @@ -35,5 +35,5 @@ output "github_oidc_provider_arn" { output "developer_boundary_arn" { description = "ARN of the javabin-developer-boundary IAM policy" - value = aws_iam_policy.developer_boundary.arn + value = data.aws_iam_policy.developer_boundary.arn }