Skip to content

Move permission boundary to terraform/org/ (human-applied)#82

Merged
Alexanderamiri merged 1 commit into
mainfrom
refactor/move-boundary-to-org
Mar 17, 2026
Merged

Move permission boundary to terraform/org/ (human-applied)#82
Alexanderamiri merged 1 commit into
mainfrom
refactor/move-boundary-to-org

Conversation

@Alexanderamiri
Copy link
Copy Markdown
Member

Summary

The permission boundary's self-protection (DenyBoundaryTampering) prevents the CI pipeline from modifying it — the ci-infra role carries the boundary and is blocked from iam:CreatePolicyVersion on the boundary policy itself.

This PR adds the boundary resource to terraform/org/ (human-applied with admin credentials) and marks the platform/iam/boundary.tf copy for removal after state migration.

Migration steps (manual, with --profile javabin)

Step 1: Merge this PR (CI plan will show no infra changes since the platform copy is unchanged)

Step 2: Import into org state

cd terraform/org
terraform init
terraform import aws_iam_policy.developer_boundary \
  arn:aws:iam::553637109631:policy/javabin-developer-boundary
terraform plan   # verify no changes

Step 3: Remove from platform state

cd terraform/platform
terraform state rm module.iam.aws_iam_policy.developer_boundary

Step 4: Separate PR to replace platform/iam/boundary.tf resource with data source

Why not remove the platform copy now?

If we remove it before the state migration, CI will plan a destroy of the boundary policy, which would break all IAM roles in the account. The platform copy is kept as a safety net until the state move is complete.

Test plan

  • CI plan shows no changes (platform boundary unchanged)
  • Manual: import into org state succeeds
  • Manual: org plan shows no changes after import
  • Manual: state rm from platform succeeds
  • Follow-up PR: replace with data source

The boundary's self-protection (DenyBoundaryTampering) prevents the CI
pipeline from modifying it. Move the resource to terraform/org/ which
is human-applied with admin credentials.

Migration steps (must be done manually with --profile javabin):

  Step 1: Import boundary into org state
    cd terraform/org
    terraform import aws_iam_policy.developer_boundary \
      arn:aws:iam::553637109631:policy/javabin-developer-boundary

  Step 2: Apply org to verify no changes
    terraform plan   # should show no changes
    terraform apply

  Step 3: Remove boundary from platform state
    cd terraform/platform
    terraform state rm module.iam.aws_iam_policy.developer_boundary

  Step 4: Replace resource with data source in platform/iam/boundary.tf
    (separate PR after state migration)

The platform/iam/boundary.tf resource is kept temporarily to prevent CI
from destroying it. It will be replaced with a data source in step 4.
@Alexanderamiri Alexanderamiri enabled auto-merge (squash) March 17, 2026 21:33
@github-actions
Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 0 to add, 1 to change, 0 to destroy.

Plan output

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # module.iam.aws_iam_policy.developer_boundary will be updated in-place
  ~ resource "aws_iam_policy" "developer_boundary" {
        id               = "arn:aws:iam::553637109631:policy/javabin-developer-boundary"
        name             = "javabin-developer-boundary"
      ~ policy           = jsonencode(
          ~ {
              ~ Statement = [
                    # (7 unchanged elements hidden)
                    {
                        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",
                        ]
                        Effect   = "Deny"
                        Resource = "*"
                        Sid      = "DenyPlatformNetworking"
                    },
                  + {
                      + Action    = [
                          + "ec2:DeleteSecurityGroup",
                          + "ec2:AuthorizeSecurityGroupIngress",
                          + "ec2:RevokeSecurityGroupIngress",
                          + "ec2:AuthorizeSecurityGroupEgress",
                          + "ec2:RevokeSecurityGroupEgress",
                          + "ec2:ModifySecurityGroupRules",
                        ]
                      + Condition = {
                          + StringLike = {
                              + "ec2:ResourceTag/Name" = "javabin-*"
                            }
                        }
                      + Effect    = "Deny"
                      + Resource  = "arn:aws:ec2:eu-central-1:553637109631:security-group/*"
                      + Sid       = "DenyPlatformSecurityGroups"
                    },
                    {
                        Action   = [
                            "ecs:DeleteCluster",
                            "ecs:UpdateCluster",
                        ]
                        Effect   = "Deny"
                        Resource = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
                        Sid      = "DenyPlatformECSCluster"
                    },
                    # (4 unchanged elements hidden)
                ]
                # (1 unchanged attribute hidden)
            }
        )
        tags             = {
            "Name" = "javabin-developer-boundary"
        }
        # (6 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

LLM Review

Risk: 🟢 LOW

Adding security group modification restrictions to the developer boundary policy to prevent unauthorized changes to platform security groups.

  • 🔒 [security] Adding new Deny statement for security group modifications (DeleteSecurityGroup, AuthorizeSecurityGroupIngress/Egress, RevokeSecurityGroupIngress/Egress, ModifySecurityGroupRules) on resources tagged with 'javabin-*'. This is a positive security hardening measure that prevents developers from modifying critical platform security groups.
  • [routine] Single in-place update to IAM policy document. No resources are being created, destroyed, or replaced. This is a non-breaking change that only adds additional restrictions.
  • 🔒 [security] The new restriction uses resource tagging (ec2:ResourceTag/Name = 'javabin-*') as a condition, which is a best practice for scope-limiting deny policies and prevents accidental over-restriction.

@Alexanderamiri Alexanderamiri merged commit 87e10ca into main Mar 17, 2026
3 checks passed
@Alexanderamiri Alexanderamiri deleted the refactor/move-boundary-to-org branch March 17, 2026 21:33
Alexanderamiri added a commit that referenced this pull request Mar 17, 2026
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.
Alexanderamiri added a commit that referenced this pull request Mar 17, 2026
## Summary

Completes the boundary migration from #82. The state migration has been
done manually:

1. Imported boundary into `terraform/org/` state
2. Removed from `terraform/platform/` state via `terraform state rm`

This PR replaces the resource with a `data.aws_iam_policy` data source
so platform CI reads the existing boundary policy instead of trying to
recreate it.

All references updated: `aws_iam_policy.developer_boundary.arn` →
`data.aws_iam_policy.developer_boundary.arn`

## Test plan
- [ ] CI plan shows 1 resource removed (the boundary) and data source
read — no destroy
- [ ] All roles still reference the boundary ARN correctly
- [ ] Future boundary changes go through `terraform/org/`
(human-applied)
Alexanderamiri added a commit that referenced this pull request May 9, 2026
## Summary

The permission boundary's self-protection (`DenyBoundaryTampering`)
prevents the CI pipeline from modifying it — the `ci-infra` role carries
the boundary and is blocked from `iam:CreatePolicyVersion` on the
boundary policy itself.

This PR adds the boundary resource to `terraform/org/` (human-applied
with admin credentials) and marks the `platform/iam/boundary.tf` copy
for removal after state migration.

## Migration steps (manual, with `--profile javabin`)

**Step 1**: Merge this PR (CI plan will show no infra changes since the
platform copy is unchanged)

**Step 2**: Import into org state
```bash
cd terraform/org
terraform init
terraform import aws_iam_policy.developer_boundary \
  arn:aws:iam::553637109631:policy/javabin-developer-boundary
terraform plan   # verify no changes
```

**Step 3**: Remove from platform state
```bash
cd terraform/platform
terraform state rm module.iam.aws_iam_policy.developer_boundary
```

**Step 4**: Separate PR to replace `platform/iam/boundary.tf` resource
with data source

## Why not remove the platform copy now?

If we remove it before the state migration, CI will plan a **destroy**
of the boundary policy, which would break all IAM roles in the account.
The platform copy is kept as a safety net until the state move is
complete.

## Test plan
- [ ] CI plan shows no changes (platform boundary unchanged)
- [ ] Manual: import into org state succeeds
- [ ] Manual: org plan shows no changes after import
- [ ] Manual: state rm from platform succeeds
- [ ] Follow-up PR: replace with data source
Alexanderamiri added a commit that referenced this pull request May 9, 2026
## Summary

Completes the boundary migration from #82. The state migration has been
done manually:

1. Imported boundary into `terraform/org/` state
2. Removed from `terraform/platform/` state via `terraform state rm`

This PR replaces the resource with a `data.aws_iam_policy` data source
so platform CI reads the existing boundary policy instead of trying to
recreate it.

All references updated: `aws_iam_policy.developer_boundary.arn` →
`data.aws_iam_policy.developer_boundary.arn`

## Test plan
- [ ] CI plan shows 1 resource removed (the boundary) and data source
read — no destroy
- [ ] All roles still reference the boundary ARN correctly
- [ ] Future boundary changes go through `terraform/org/`
(human-applied)
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