Skip to content

Scope the CI/CD policy to each project's own ECR repositories and ECS services - #241

Merged
ale210 merged 2 commits into
mainfrom
202-scope-cicd-policy-per-project
Sep 13, 2026
Merged

ale210 merged 2 commits into
mainfrom
202-scope-cicd-policy-per-project

Conversation

@ale210

@ale210 ale210 commented Sep 13, 2026

Copy link
Copy Markdown
Member

Part 1 of 2 for #202.

Each project's CI/CD role gets its own policy, incubator-cicd-<project>, in modules/cicd_integration. Its ECR push and ECS deploy statements only allow resources whose project tag matches the project. Before this, all four roles shared incubator_builder, which allowed push to every repository and UpdateService on every service in incubator-prod.

The shared incubator_builder policy stays in place, unattached. It is deleted in a follow-up PR. The attachments referenced it by a hardcoded ARN, so Terraform has no dependency to order a detach before the delete within one apply.

Expected plan: 4 policies to add, 4 aws_iam_role_policy_attachment replaced (policy_arn forces replacement), nothing else changed or destroyed. During the apply each role is briefly without permissions.

Checked before opening: terraform validate. I also ran the IAM policy simulator against every live ECR repository and ECS service with its real project tag: each project is allowed only on its own resources, and the other projects' and untagged resources are denied (288 checks, 0 failures).

After merge: confirm the attachments, run the people-depot and CivicTechJobs deploys, and test a cross-project denial with a real role (see #202).

@github-actions

github-actions Bot commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Terraform plan in terraform
With backend config files: terraform/prod.backend.tfvars

Plan: 8 to add, 0 to change, 4 to destroy.
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+   create
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # module.civic-tech-jobs.module.civic_tech_jobs_cicd.aws_iam_policy.builder will be created
+   resource "aws_iam_policy" "builder" {
+       arn              = (known after apply)
+       attachment_count = (known after apply)
+       description      = "CI/CD for civic-tech-jobs: push images and redeploy services tagged with project civic-tech-jobs"
+       id               = (known after apply)
+       name             = "incubator-cicd-civic-tech-jobs"
+       name_prefix      = (known after apply)
+       path             = "/"
+       policy           = jsonencode(
            {
+               Statement = [
+                   {
+                       Action   = "ecr:GetAuthorizationToken"
+                       Effect   = "Allow"
+                       Resource = "*"
+                       Sid      = "EcrAuthToken"
                    },
+                   {
+                       Action    = [
+                           "ecr:CompleteLayerUpload",
+                           "ecr:UploadLayerPart",
+                           "ecr:InitiateLayerUpload",
+                           "ecr:GetDownloadUrlForLayer",
+                           "ecr:BatchCheckLayerAvailability",
+                           "ecr:PutImage",
+                           "ecr:BatchGetImage",
                        ]
+                       Condition = {
+                           StringEquals = {
+                               "aws:ResourceTag/project" = "civic-tech-jobs"
                            }
                        }
+                       Effect    = "Allow"
+                       Resource  = "arn:aws:ecr:us-west-2:035866691871:repository/*"
+                       Sid       = "EcrPush"
                    },
+                   {
+                       Action    = [
+                           "ecs:UpdateService",
+                           "ecs:DescribeServices",
                        ]
+                       Condition = {
+                           StringEquals = {
+                               "aws:ResourceTag/project" = "civic-tech-jobs"
                            }
                        }
+                       Effect    = "Allow"
+                       Resource  = "arn:aws:ecs:us-west-2:035866691871:service/incubator-prod/*"
+                       Sid       = "DeployService"
                    },
                ]
+               Version   = "2012-10-17"
            }
        )
+       policy_id        = (known after apply)
+       tags             = {
+           "project" = "civic-tech-jobs"
        }
+       tags_all         = {
+           "managed-by" = "terraform-incubator"
+           "project"    = "civic-tech-jobs"
        }
    }

  # module.civic-tech-jobs.module.civic_tech_jobs_cicd.aws_iam_role_policy_attachment.this must be replaced
-/+ resource "aws_iam_role_policy_attachment" "this" {
!~      id         = "*********************************************************" -> (known after apply)
!~      policy_arn = "arn:aws:iam::035866691871:policy/incubator_builder" -> (known after apply) # forces replacement
#        (1 unchanged attribute hidden)
    }

  # module.home-unite-us.module.cicd_integration.aws_iam_policy.builder will be created
+   resource "aws_iam_policy" "builder" {
+       arn              = (known after apply)
+       attachment_count = (known after apply)
+       description      = "CI/CD for home-unite-us: push images and redeploy services tagged with project home-unite-us"
+       id               = (known after apply)
+       name             = "incubator-cicd-home-unite-us"
+       name_prefix      = (known after apply)
+       path             = "/"
+       policy           = jsonencode(
            {
+               Statement = [
+                   {
+                       Action   = "ecr:GetAuthorizationToken"
+                       Effect   = "Allow"
+                       Resource = "*"
+                       Sid      = "EcrAuthToken"
                    },
+                   {
+                       Action    = [
+                           "ecr:CompleteLayerUpload",
+                           "ecr:UploadLayerPart",
+                           "ecr:InitiateLayerUpload",
+                           "ecr:GetDownloadUrlForLayer",
+                           "ecr:BatchCheckLayerAvailability",
+                           "ecr:PutImage",
+                           "ecr:BatchGetImage",
                        ]
+                       Condition = {
+                           StringEquals = {
+                               "aws:ResourceTag/project" = "home-unite-us"
                            }
                        }
+                       Effect    = "Allow"
+                       Resource  = "arn:aws:ecr:us-west-2:035866691871:repository/*"
+                       Sid       = "EcrPush"
                    },
+                   {
+                       Action    = [
+                           "ecs:UpdateService",
+                           "ecs:DescribeServices",
                        ]
+                       Condition = {
+                           StringEquals = {
+                               "aws:ResourceTag/project" = "home-unite-us"
                            }
                        }
+                       Effect    = "Allow"
+                       Resource  = "arn:aws:ecs:us-west-2:035866691871:service/incubator-prod/*"
+                       Sid       = "DeployService"
                    },
                ]
+               Version   = "2012-10-17"
            }
        )
+       policy_id        = (known after apply)
+       tags             = {
+           "project" = "home-unite-us"
        }
+       tags_all         = {
+           "managed-by" = "terraform-incubator"
+           "project"    = "home-unite-us"
        }
    }

  # module.home-unite-us.module.cicd_integration.aws_iam_role_policy_attachment.this must be replaced
-/+ resource "aws_iam_role_policy_attachment" "this" {
!~      id         = "*******************************************************" -> (known after apply)
!~      policy_arn = "arn:aws:iam::035866691871:policy/incubator_builder" -> (known after apply) # forces replacement
#        (1 unchanged attribute hidden)
    }

  # module.people-depot.module.people_depot_cicd.aws_iam_policy.builder will be created
+   resource "aws_iam_policy" "builder" {
+       arn              = (known after apply)
+       attachment_count = (known after apply)
+       description      = "CI/CD for people-depot: push images and redeploy services tagged with project people-depot"
+       id               = (known after apply)
+       name             = "incubator-cicd-people-depot"
+       name_prefix      = (known after apply)
+       path             = "/"
+       policy           = jsonencode(
            {
+               Statement = [
+                   {
+                       Action   = "ecr:GetAuthorizationToken"
+                       Effect   = "Allow"
+                       Resource = "*"
+                       Sid      = "EcrAuthToken"
                    },
+                   {
+                       Action    = [
+                           "ecr:CompleteLayerUpload",
+                           "ecr:UploadLayerPart",
+                           "ecr:InitiateLayerUpload",
+                           "ecr:GetDownloadUrlForLayer",
+                           "ecr:BatchCheckLayerAvailability",
+                           "ecr:PutImage",
+                           "ecr:BatchGetImage",
                        ]
+                       Condition = {
+                           StringEquals = {
+                               "aws:ResourceTag/project" = "people-depot"
                            }
                        }
+                       Effect    = "Allow"
+                       Resource  = "arn:aws:ecr:us-west-2:035866691871:repository/*"
+                       Sid       = "EcrPush"
                    },
+                   {
+                       Action    = [
+                           "ecs:UpdateService",
+                           "ecs:DescribeServices",
                        ]
+                       Condition = {
+                           StringEquals = {
+                               "aws:ResourceTag/project" = "people-depot"
                            }
                        }
+                       Effect    = "Allow"
+                       Resource  = "arn:aws:ecs:us-west-2:035866691871:service/incubator-prod/*"
+                       Sid       = "DeployService"
                    },
                ]
+               Version   = "2012-10-17"
            }
        )
+       policy_id        = (known after apply)
+       tags             = {
+           "project" = "people-depot"
        }
+       tags_all         = {
+           "managed-by" = "terraform-incubator"
+           "project"    = "people-depot"
        }
    }

  # module.people-depot.module.people_depot_cicd.aws_iam_role_policy_attachment.this must be replaced
-/+ resource "aws_iam_role_policy_attachment" "this" {
!~      id         = "******************************************************" -> (known after apply)
!~      policy_arn = "arn:aws:iam::035866691871:policy/incubator_builder" -> (known after apply) # forces replacement
#        (1 unchanged attribute hidden)
    }

  # module.vrms.module.cicd.aws_iam_policy.builder will be created
+   resource "aws_iam_policy" "builder" {
+       arn              = (known after apply)
+       attachment_count = (known after apply)
+       description      = "CI/CD for vrms: push images and redeploy services tagged with project vrms"
+       id               = (known after apply)
+       name             = "incubator-cicd-vrms"
+       name_prefix      = (known after apply)
+       path             = "/"
+       policy           = jsonencode(
            {
+               Statement = [
+                   {
+                       Action   = "ecr:GetAuthorizationToken"
+                       Effect   = "Allow"
+                       Resource = "*"
+                       Sid      = "EcrAuthToken"
                    },
+                   {
+                       Action    = [
+                           "ecr:CompleteLayerUpload",
+                           "ecr:UploadLayerPart",
+                           "ecr:InitiateLayerUpload",
+                           "ecr:GetDownloadUrlForLayer",
+                           "ecr:BatchCheckLayerAvailability",
+                           "ecr:PutImage",
+                           "ecr:BatchGetImage",
                        ]
+                       Condition = {
+                           StringEquals = {
+                               "aws:ResourceTag/project" = "vrms"
                            }
                        }
+                       Effect    = "Allow"
+                       Resource  = "arn:aws:ecr:us-west-2:035866691871:repository/*"
+                       Sid       = "EcrPush"
                    },
+                   {
+                       Action    = [
+                           "ecs:UpdateService",
+                           "ecs:DescribeServices",
                        ]
+                       Condition = {
+                           StringEquals = {
+                               "aws:ResourceTag/project" = "vrms"
                            }
                        }
+                       Effect    = "Allow"
+                       Resource  = "arn:aws:ecs:us-west-2:035866691871:service/incubator-prod/*"
+                       Sid       = "DeployService"
                    },
                ]
+               Version   = "2012-10-17"
            }
        )
+       policy_id        = (known after apply)
+       tags             = {
+           "project" = "vrms"
        }
+       tags_all         = {
+           "managed-by" = "terraform-incubator"
+           "project"    = "vrms"
        }
    }

  # module.vrms.module.cicd.aws_iam_role_policy_attachment.this must be replaced
-/+ resource "aws_iam_role_policy_attachment" "this" {
!~      id         = "**********************************************" -> (known after apply)
!~      policy_arn = "arn:aws:iam::035866691871:policy/incubator_builder" -> (known after apply) # forces replacement
#        (1 unchanged attribute hidden)
    }

Plan: 8 to add, 0 to change, 4 to destroy.

✅ Plan applied in Terraform apply (OIDC) #93

@ale210
ale210 merged commit deb8096 into main Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant