Skip to content

fix: tighten IAM trust scope, ECR immutability, drop unused id-token - #12

Merged
RuBiCK merged 1 commit into
mainfrom
fix/iam-trust-scope-and-ecr-hardening
Jun 16, 2026
Merged

fix: tighten IAM trust scope, ECR immutability, drop unused id-token#12
RuBiCK merged 1 commit into
mainfrom
fix/iam-trust-scope-and-ecr-hardening

Conversation

@RuBiCK

@RuBiCK RuBiCK commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Qué cambia

Tres mejoras de seguridad e higiene sin cambios funcionales en la app.

1. Trust policy del rol ECR — scope restringido a main + tags

Antes: cualquier branch, PR o workflow del repo podía asumir github-actions-ecr.
Ahora: solo refs/heads/main y refs/tags/v*.

values = [
  "repo:RuBiCK/airgapped-test:ref:refs/heads/main",
  "repo:RuBiCK/airgapped-test:ref:refs/tags/v*",
]

El rol de Terraform (github-actions-terraform) mantiene repo:* porque el plan necesita ejecutarse en PRs para generar el comentario con el diff.

2. ECR tags — MUTABLE → IMMUTABLE

Un tag sha-f89c21c ahora siempre apunta a la misma imagen. Intentar sobrescribirlo es un error explícito de ECR en lugar de sobrescritura silenciosa.

3. Deploy job — quitar id-token: write

El deploy job no configura credenciales AWS (Zarf trabaja del bundle, no necesita ECR). El permiso OIDC era correcto solo en teoría, no en uso.

CI afectado

  • Terraform workflow → hará plan de los cambios en iam.tf y variables.tf. El apply se ejecuta al merge.
  • Zarf lint → valida zarf.yaml y el chart (sin cambios en esos ficheros, debería pasar).
  • Build & Push → sin cambios, no se dispara desde una PR.

Three hardening changes:

1. iam.tf — split trust policy in two:
   - github-actions-ecr: only main branch + v* tags can assume it.
     PRs and feature branches build locally but cannot push to ECR.
   - github-actions-terraform: keeps repo:* scope so terraform plan
     still runs on PRs (needed for the PR comment with the plan).

2. variables.tf — ecr_image_tag_mutability: MUTABLE → IMMUTABLE.
   A sha-based tag now always refers to the same image; overwriting
   a deployed tag becomes an explicit ECR error instead of silent data loss.

3. zarf-package.yml deploy job — remove id-token: write permission.
   The deploy job has no AWS credentials step and Zarf needs none,
   so the OIDC token grant was unused and misleading.
@github-actions

Copy link
Copy Markdown

Terraform Plan ✅

Show Plan
data.aws_caller_identity.current: Reading...
aws_ecr_repository.app["airgapped-demo/frontend"]: Refreshing state... [id=airgapped-demo/frontend]
aws_ecr_repository.app["airgapped-demo/backend"]: Refreshing state... [id=airgapped-demo/backend]
aws_iam_openid_connect_provider.github: Refreshing state... [id=arn:aws:iam::889918307320:oidc-provider/token.actions.githubusercontent.com]
data.aws_caller_identity.current: Read complete after 0s [id=889918307320]
data.aws_iam_policy_document.terraform_permissions: Reading...
data.aws_iam_policy_document.terraform_permissions: Read complete after 0s [id=213597817]
data.aws_iam_policy_document.github_terraform_trust: Reading...
data.aws_iam_policy_document.github_trust: Reading...
data.aws_iam_policy_document.github_trust: Read complete after 0s [id=1350404321]
data.aws_iam_policy_document.github_terraform_trust: Read complete after 0s [id=3290923756]
aws_iam_role.github_ecr: Refreshing state... [id=github-actions-ecr]
aws_iam_role.github_terraform: Refreshing state... [id=github-actions-terraform]
aws_ecr_lifecycle_policy.app["airgapped-demo/frontend"]: Refreshing state... [id=airgapped-demo/frontend]
aws_ecr_lifecycle_policy.app["airgapped-demo/backend"]: Refreshing state... [id=airgapped-demo/backend]
aws_iam_role_policy_attachment.github_ecr: Refreshing state... [id=github-actions-ecr-20260610143204456500000001]
aws_iam_role_policy.github_terraform: Refreshing state... [id=github-actions-terraform:terraform-permissions]

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:

  # aws_ecr_repository.app["airgapped-demo/backend"] will be updated in-place
  ~ resource "aws_ecr_repository" "app" {
        id                   = "airgapped-demo/backend"
      ~ image_tag_mutability = "MUTABLE" -> "IMMUTABLE"
        name                 = "airgapped-demo/backend"
        tags                 = {
            "managed_by" = "terraform"
            "project"    = "airgapped-demo"
        }
        # (4 unchanged attributes hidden)

        # (2 unchanged blocks hidden)
    }

  # aws_ecr_repository.app["airgapped-demo/frontend"] will be updated in-place
  ~ resource "aws_ecr_repository" "app" {
        id                   = "airgapped-demo/frontend"
      ~ image_tag_mutability = "MUTABLE" -> "IMMUTABLE"
        name                 = "airgapped-demo/frontend"
        tags                 = {
            "managed_by" = "terraform"
            "project"    = "airgapped-demo"
        }
        # (4 unchanged attributes hidden)

        # (2 unchanged blocks hidden)
    }

  # aws_iam_role.github_ecr will be updated in-place
  ~ resource "aws_iam_role" "github_ecr" {
      ~ assume_role_policy    = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Condition = {
                          ~ StringLike   = {
                              ~ "token.actions.githubusercontent.com:sub" = "repo:RuBiCK/airgapped-test:*" -> [
                                  + "repo:RuBiCK/airgapped-test:ref:refs/heads/main",
                                  + "repo:RuBiCK/airgapped-test:ref:refs/tags/v*",
                                ]
                            }
                            # (1 unchanged attribute hidden)
                        }
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        id                    = "github-actions-ecr"
        name                  = "github-actions-ecr"
        tags                  = {
            "managed_by" = "terraform"
            "project"    = "airgapped-demo"
        }
        # (11 unchanged attributes hidden)
    }

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

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

Saved the plan to: tfplan

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

Workflow: Terraform · Commit: 2137aeab811bc9e6a984ca603e1a648ff8e34cb9

@RuBiCK
RuBiCK merged commit 67d5660 into main Jun 16, 2026
3 checks passed
@RuBiCK
RuBiCK deleted the fix/iam-trust-scope-and-ecr-hardening branch June 16, 2026 19:17
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