fix: tighten IAM trust scope, ECR immutability, drop unused id-token - #12
Merged
Conversation
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.
Terraform Plan ✅Show Plandata.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: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/mainyrefs/tags/v*.El rol de Terraform (
github-actions-terraform) mantienerepo:*porque el plan necesita ejecutarse en PRs para generar el comentario con el diff.2. ECR tags — MUTABLE → IMMUTABLE
Un tag
sha-f89c21cahora 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: writeEl 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
iam.tfyvariables.tf. El apply se ejecuta al merge.zarf.yamly el chart (sin cambios en esos ficheros, debería pasar).