From 338eee616aee2059ec430cd69e97dfcdc71a2be2 Mon Sep 17 00:00:00 2001 From: Alexanderamiri Date: Fri, 23 Oct 2026 16:09:40 +0200 Subject: [PATCH 1/2] fix(ci): create team CI roles only for registry teams sync-registered-teams listed every GitHub org team, so non-app teams like owners and developer got AWS CI and deploy roles. Use teams/*.yaml in javaBin/registry, the documented source of truth. Co-Authored-By: Claude Opus 5.5 (1M context) --- CLAUDE.md | 8 +++--- scripts/sync-registered-teams.py | 26 ++++++++++++------- .../platform/registered-teams.auto.tfvars | 4 ++- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ae9cf2b..f9260c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -168,7 +168,7 @@ terraform/state/ | `password-set` | Self-service password-set for new hero accounts (Function URL) | | `budget-enforcer` | Scales ECS services to zero when team exceeds 200% budget | | `resource-tagger` | EventBridge-triggered, auto-tags created-by + commit on new resources | -| `ci-broker` | Validates repo ∈ app.yaml team, vends short-lived team role credentials | +| `ci-broker` | Verifies the job's GitHub OIDC token (repo from signed claims), checks repo ∈ app.yaml team, vends short-lived team role credentials | | `app-auth-sync` | Creates/deletes Cognito app clients requested by app.yaml `auth:` (SSM request → client + credentials) | ### Scripts @@ -178,7 +178,8 @@ terraform/state/ | `scripts/generate-modules.py` | CI: validates app.yaml, generates fmt-clean module blocks into terraform/ (tests: `scripts/tests/`) | | `scripts/registry.py` | Module sources + boilerplate templates for generate-modules.py | | `scripts/resolve-tool-version.sh` | CI: resolve node/java version from the app repo's version files | -| `scripts/sync-registered-teams.py` | CI: GitHub org teams → `registered-teams.auto.tfvars` (needs App token) | +| `scripts/sync-registered-teams.py` | CI: registry `teams/*.yaml` → `registered-teams.auto.tfvars` (needs App token) | +| `scripts/github-oidc-token.sh` | CI: mint a GitHub OIDC token for the broker / apply gate | | `scripts/provision-teams.py` | CI: fetch team YAMLs from registry, invoke team-provisioner Lambda | | `scripts/provision-groups.py` | CI: resolve members.yaml + access.yaml, invoke team-provisioner Lambda | | `scripts/sync-members.py` | CI: sync approved heroes from Google Sheets into members.yaml | @@ -202,6 +203,7 @@ terraform/state/ | SSM params (platform) | `/javabin/{namespace}/{name}` | | SSM params (app) | `/javabin/apps/{team}/{service}/{name}` | | ECS cluster | `javabin-platform` | +| ECS execution roles | `javabin-ecs-exec-{team}` (platform-owned, reads only `/javabin/apps/{team}/*`) | | ECR repos | `{team}-{service}` | | SNS topics | `javabin-{alerts,security,budget-enforcement}` | | Log groups | `/ecs/{team}/{service}` | @@ -287,7 +289,7 @@ The SA JSON key is at `/javabin/platform/google-admin-sa`, the impersonation tar ### Known Issues - **ECS deploy stabilization**: platform-test-app task registers but service fails health check - **Cognito**: pools deployed; internal pool's `GoogleSAML` IdP was created outside Terraform -- **CI broker trusts the payload repo name**: code running inside an app job (e.g. a Terraform `external` data source in tf-plan) can mint a new OIDC token for the broker role and name another repo. Fix: verify a GitHub OIDC token in the Lambda instead of a payload field +- **App databases share one network path**: service-rds allows ingress from the shared ECS tasks SG, so every team's tasks can reach every team's DB port (password still required). Fix: per-app security groups - **Team provisioner Lambda**: All sync functions working (Google/GitHub/Budget/Cognito/Identity Center). Password-set flow deployed. - **`registered_app_repos` manually managed**: Being replaced with team-scoped IAM roles (repo→team resolved via GitHub API at runtime) - **Cost allocation tags pending activation**: `repo`, `created-by`, `commit` tags need activation in Billing console (requires billing data to appear first) diff --git a/scripts/sync-registered-teams.py b/scripts/sync-registered-teams.py index d1c608f..713dc6d 100644 --- a/scripts/sync-registered-teams.py +++ b/scripts/sync-registered-teams.py @@ -1,19 +1,22 @@ #!/usr/bin/env python3 -"""Sync registered teams from GitHub org teams. +"""Sync registered teams from the registry. -Queries the GitHub API for all teams in the javaBin org and writes -registered-teams.auto.tfvars. This drives team-scoped IAM role creation. +Lists teams/*.yaml in javaBin/registry and writes +registered-teams.auto.tfvars. This drives team-scoped IAM role creation +(CI roles, ECS execution roles). -Source of truth: GitHub teams (created by team-provisioner from registry). +Source of truth: the registry. GitHub org teams also include non-app teams +(owners, developer, ...) that must not get AWS CI roles. Usage: sync-registered-teams.py -Requires: GITHUB_TOKEN env var (with org team read access). +Requires: GITHUB_TOKEN env var (read access to javaBin/registry). """ import json import logging import os +import re import sys import urllib.request @@ -45,10 +48,15 @@ def github_get(path, token): return results +REGISTRY_REPO = os.environ.get("REGISTRY_REPO", "registry") +TEAM_NAME = re.compile(r"^[a-z]{1,12}$") + + def get_teams(token): - """Get all org teams, excluding platform-internal ones.""" - teams = github_get(f"/orgs/{GITHUB_ORG}/teams", token) - slugs = sorted(t["slug"] for t in teams if t["slug"] not in EXCLUDED_TEAMS) + """Registry team names (teams/.yaml), excluding platform-internal ones.""" + files = github_get(f"/repos/{GITHUB_ORG}/{REGISTRY_REPO}/contents/teams", token) + names = [f["name"][:-5] for f in files if f["type"] == "file" and f["name"].endswith(".yaml")] + slugs = sorted(n for n in names if TEAM_NAME.match(n) and n not in EXCLUDED_TEAMS) logger.info("Found %d teams (excluding %s)", len(slugs), ", ".join(EXCLUDED_TEAMS)) for slug in slugs: logger.info(" %s", slug) @@ -59,7 +67,7 @@ def write_tfvars(teams, output_path): """Write the registered-teams.auto.tfvars file.""" lines = [ "# GENERATED by sync-registered-teams.py — do not edit manually", - "# Source of truth: GitHub teams in javaBin org", + "# Source of truth: teams/*.yaml in javaBin/registry", "", "registered_teams = [", ] diff --git a/terraform/platform/registered-teams.auto.tfvars b/terraform/platform/registered-teams.auto.tfvars index bcc0487..9ce27b9 100644 --- a/terraform/platform/registered-teams.auto.tfvars +++ b/terraform/platform/registered-teams.auto.tfvars @@ -1,6 +1,8 @@ # GENERATED by sync-registered-teams.py — do not edit manually -# Source of truth: GitHub teams in javaBin org +# Source of truth: teams/*.yaml in javaBin/registry registered_teams = [ + "cupcake", + "jzwatch", "testteam", ] From c9ed777fd039de249943d7f7f0f78df6a88960f6 Mon Sep 17 00:00:00 2001 From: Alexanderamiri Date: Fri, 23 Oct 2026 16:30:34 +0200 Subject: [PATCH 2/2] docs: keep only this PR's CLAUDE.md change Co-Authored-By: Claude Opus 5.5 (1M context) --- CLAUDE.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f9260c6..1c8ec74 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -168,7 +168,7 @@ terraform/state/ | `password-set` | Self-service password-set for new hero accounts (Function URL) | | `budget-enforcer` | Scales ECS services to zero when team exceeds 200% budget | | `resource-tagger` | EventBridge-triggered, auto-tags created-by + commit on new resources | -| `ci-broker` | Verifies the job's GitHub OIDC token (repo from signed claims), checks repo ∈ app.yaml team, vends short-lived team role credentials | +| `ci-broker` | Validates repo ∈ app.yaml team, vends short-lived team role credentials | | `app-auth-sync` | Creates/deletes Cognito app clients requested by app.yaml `auth:` (SSM request → client + credentials) | ### Scripts @@ -179,7 +179,6 @@ terraform/state/ | `scripts/registry.py` | Module sources + boilerplate templates for generate-modules.py | | `scripts/resolve-tool-version.sh` | CI: resolve node/java version from the app repo's version files | | `scripts/sync-registered-teams.py` | CI: registry `teams/*.yaml` → `registered-teams.auto.tfvars` (needs App token) | -| `scripts/github-oidc-token.sh` | CI: mint a GitHub OIDC token for the broker / apply gate | | `scripts/provision-teams.py` | CI: fetch team YAMLs from registry, invoke team-provisioner Lambda | | `scripts/provision-groups.py` | CI: resolve members.yaml + access.yaml, invoke team-provisioner Lambda | | `scripts/sync-members.py` | CI: sync approved heroes from Google Sheets into members.yaml | @@ -203,7 +202,6 @@ terraform/state/ | SSM params (platform) | `/javabin/{namespace}/{name}` | | SSM params (app) | `/javabin/apps/{team}/{service}/{name}` | | ECS cluster | `javabin-platform` | -| ECS execution roles | `javabin-ecs-exec-{team}` (platform-owned, reads only `/javabin/apps/{team}/*`) | | ECR repos | `{team}-{service}` | | SNS topics | `javabin-{alerts,security,budget-enforcement}` | | Log groups | `/ecs/{team}/{service}` | @@ -289,7 +287,7 @@ The SA JSON key is at `/javabin/platform/google-admin-sa`, the impersonation tar ### Known Issues - **ECS deploy stabilization**: platform-test-app task registers but service fails health check - **Cognito**: pools deployed; internal pool's `GoogleSAML` IdP was created outside Terraform -- **App databases share one network path**: service-rds allows ingress from the shared ECS tasks SG, so every team's tasks can reach every team's DB port (password still required). Fix: per-app security groups +- **CI broker trusts the payload repo name**: code running inside an app job (e.g. a Terraform `external` data source in tf-plan) can mint a new OIDC token for the broker role and name another repo. Fix: verify a GitHub OIDC token in the Lambda instead of a payload field - **Team provisioner Lambda**: All sync functions working (Google/GitHub/Budget/Cognito/Identity Center). Password-set flow deployed. - **`registered_app_repos` manually managed**: Being replaced with team-scoped IAM roles (repo→team resolved via GitHub API at runtime) - **Cost allocation tags pending activation**: `repo`, `created-by`, `commit` tags need activation in Billing console (requires billing data to appear first)