From 3c662300c5c261b59bad0830a40c543a0dd981e9 Mon Sep 17 00:00:00 2001 From: Alexanderamiri Date: Sat, 31 Oct 2026 13:02:12 +0100 Subject: [PATCH] fix(iam): accept immutable OIDC subjects on ci-infra, ci-infra-plan and ci-registry The platform and registry roles only matched repo:javaBin/:..., so switching either repo to GitHub's immutable subject format would lock CI out. Accept repo:javaBin@95047/@:... too, pinned to the repo IDs via new github_platform_repo_id / github_registry_repo_id variables, with the same ref/environment restrictions. Co-Authored-By: Claude Opus 5.5 --- docs/platform-modules.md | 4 +++- terraform/platform/iam/main.tf | 31 ++++++++++++++++++++++++----- terraform/platform/iam/variables.tf | 12 +++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/docs/platform-modules.md b/docs/platform-modules.md index 4b958ea..209b1da 100644 --- a/docs/platform-modules.md +++ b/docs/platform-modules.md @@ -56,11 +56,13 @@ GitHub OIDC provider (data source), CI roles, permission boundary, ECS execution | `javabin-ci-team-{team}` | CI broker and apply gate Lambdas (not OIDC) | Team TF plan + apply, scoped to the team by the developer boundary | | `javabin-ci-deploy-{team}` | CI broker Lambda (not OIDC) | ECR push, ECS deploy | | `javabin-ci-override-approver` | Board members, pinned to `approve-override.yml` | SSM override tokens | -| `javabin-ci-registry` | Registry repo | Team provisioning dispatch | +| `javabin-ci-registry` | Registry repo, `main` only | Team provisioning dispatch | | `javabin-ecs-execution` | ECS tasks | Pull images, write logs, read secrets | **Key design:** `job_workflow_ref` condition prevents app repos from writing rogue workflows. +**OIDC subject formats:** every OIDC trust policy accepts both the legacy subject (`repo:javaBin/:...`) and GitHub's immutable subject (`repo:javaBin@95047/@:...`), which new repos get by default and can't opt out of. The org-wide roles (`ci-app-broker`, `ci-override-approver`, `ci-apply-gate`) pin the org ID (`github_org_id`). The platform and registry roles also pin the repo ID (`github_platform_repo_id`, `github_registry_repo_id` in `iam/variables.tf`); look one up with `gh api repos/javaBin/ --jq .id`. + ## compute ECS Fargate cluster + CI ECR repositories. diff --git a/terraform/platform/iam/main.tf b/terraform/platform/iam/main.tf index a876816..fe9f75b 100644 --- a/terraform/platform/iam/main.tf +++ b/terraform/platform/iam/main.tf @@ -11,6 +11,16 @@ data "aws_iam_openid_connect_provider" "github" { url = "https://token.actions.githubusercontent.com" } +# GitHub's immutable OIDC subject replaces "/" with +# "@/@". New repos get it by default and can't +# opt out; existing repos can opt in. The infra roles below accept both forms, +# with the immutable one pinned to the exact repo ID so a renamed or +# recreated repo with the same name can't match. +locals { + platform_immutable_repo = "${var.github_org}@${var.github_org_id}/platform@${var.github_platform_repo_id}" + registry_immutable_repo = "${var.github_org}@${var.github_org_id}/registry@${var.github_registry_repo_id}" +} + ################################################################################ # Permission Boundary Reference # @@ -22,7 +32,8 @@ data "aws_iam_openid_connect_provider" "github" { ################################################################################ # 1a. javabin-ci-infra-plan — Platform repo plan + review (read-only) # -# Trust: GitHub OIDC, repo:javaBin/platform on ANY ref (PRs need plan too). +# Trust: GitHub OIDC, repo:javaBin/platform on ANY ref (PRs need plan too), +# legacy or immutable (repo:javaBin@95047/platform@) subject. # Permissions: ReadOnlyAccess + specific writes for plan operations: # - S3: upload plan artifacts # - DynamoDB: Terraform state locking @@ -50,8 +61,12 @@ resource "aws_iam_role" "ci_infra_plan" { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" } StringLike = { - # Any ref — PRs and main both need plan - "token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/platform:*" + # Any ref — PRs and main both need plan. The second form is + # GitHub's immutable subject, pinned to the platform repo ID. + "token.actions.githubusercontent.com:sub" = [ + "repo:${var.github_org}/platform:*", + "repo:${local.platform_immutable_repo}:*", + ] } } } @@ -179,6 +194,8 @@ resource "aws_iam_role" "ci_infra" { "token.actions.githubusercontent.com:sub" = [ "repo:${var.github_org}/platform:ref:refs/heads/main", "repo:${var.github_org}/platform:environment:production", + "repo:${local.platform_immutable_repo}:ref:refs/heads/main", + "repo:${local.platform_immutable_repo}:environment:production", ] # The job_workflow_ref pin is what ties this role to main: only # platform-ci.yml as committed on main can assume it, whatever @@ -820,7 +837,8 @@ resource "aws_iam_role_policy" "ci_override_approver" { ################################################################################ # 5. javabin-ci-registry — Registry repo team provisioning # -# Trust: GitHub OIDC pinned to javaBin/registry on main branch. +# Trust: GitHub OIDC pinned to javaBin/registry on main branch (legacy or +# immutable subject, the latter pinned to the registry repo ID). # Permissions: ONLY lambda:InvokeFunction on the team-provisioner Lambda. # This lets the registry invoke the Lambda directly on merge, without # dispatching through the platform repo. @@ -844,7 +862,10 @@ resource "aws_iam_role" "ci_registry" { "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com" } StringLike = { - "token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/registry:ref:refs/heads/main" + "token.actions.githubusercontent.com:sub" = [ + "repo:${var.github_org}/registry:ref:refs/heads/main", + "repo:${local.registry_immutable_repo}:ref:refs/heads/main", + ] } } } diff --git a/terraform/platform/iam/variables.tf b/terraform/platform/iam/variables.tf index 0dc2c7c..4f981c1 100644 --- a/terraform/platform/iam/variables.tf +++ b/terraform/platform/iam/variables.tf @@ -31,6 +31,18 @@ variable "github_org_id" { default = "95047" } +variable "github_platform_repo_id" { + description = "GitHub repository ID of /platform, pinned in the immutable OIDC subject of the ci-infra and ci-infra-plan roles" + type = string + default = "1175354559" +} + +variable "github_registry_repo_id" { + description = "GitHub repository ID of /registry, pinned in the immutable OIDC subject of the ci-registry role" + type = string + default = "1175355770" +} + variable "apply_gate_role_arn" { description = "ARN of the apply-gate Lambda role (for team role trust policy)" type = string