Skip to content

sec(iac): rename-proof OIDC deploy trust + branch-restricted environments for the split platform repo #2

Closed

Description

@cristim

Problem

The CI/CD deploy trust is keyed on the repository name (var.github_repo) in terraform/environments/aws/ci-cd-permissions/role.tf:92-144, terraform/environments/azure/ci-cd-permissions/sp.tf:47,58,73 and terraform/environments/gcp/ci-cd-permissions/github_oidc.tf:37,46. GitHub's sub/repository claims follow the repo's current name. So:

  • The live trusts still name the pre-split monorepo. After the split, that repo holds only CLI code and is renamed cloud-commitments-cli. This repo cannot deploy until the three ci-cd-permissions modules are re-applied with github_repo = LeanerCloud/cloud-commitments-platform.
  • A future rename, or a new repo that reuses a freed name, silently gains or loses deploy rights.
  • This repo has no environments yet. The first workflow_dispatch auto-creates them with no branch policy, and because the environment-scoped subjects are ref-agnostic, any branch could then deploy.

Fix

  1. Create every environment the workflows use (dev, staging, prod, aws-fargate-, aws-db-, gcp-db-, azure-db-, *-rollback) with deployment_branch_policy.custom_branch_policies = true limited to main, before applying trust.
  2. Make the trust rename-proof. GCP: map attribute.repository_id = assertion.repository_id and condition on it. AWS/Azure: customize the repo's OIDC sub claim template to include repository_id (and repository_owner_id), then match on the immutable ids.
  3. Re-apply the three ci-cd-permissions modules (bootstrap: a privileged human, per CLAUDE.md), and remove the trust entries for the old monorepo name.
  4. Copy the missing Actions secrets/vars (full list in the split log).

Acceptance

  • A token from any other LeanerCloud repo, or from a non-main branch, is rejected by all three clouds.
  • A test deploy from main succeeds.

Activity

  1. cristim commented on Sep 27, 2026

    @cristim
    MemberAuthor

    Evidence after the split PR merged (bd67009): the push-triggered deploys fail at the auth step exactly as expected. Deploy to Azure (run 36347968686) fails at Azure Login, GCP (36347968701) at Authenticate to Google Cloud, and AWS Lambda (36347968765) at Configure AWS credentials. CI - Build & Test, pre-commit and frontend-build-sentinel are green. Until the bootstrap in this issue is done, every push to main produces these three red runs. Consider gh workflow disable on the deploy/rollback/migration workflows in the meantime.

  2. cristim commented on Sep 28, 2026

    @cristim
    MemberAuthor

    Cross-links from the CI/OIDC p1 batch

    Working several overlapping p1 CI/OIDC issues in this repo; noting the
    overlap with this issue's bootstrap re-apply so whoever picks this up has
    the full picture:

    None of the above perform the bootstrap re-apply itself (creating
    environments live, re-applying ci-cd-permissions with the correct
    github_repo, or making the trust rename-proof) -- that's still this
    issue's scope and still needs the privileged-human bootstrap step per
    CLAUDE.md.

  3. cristim commented on Oct 4, 2026

    @cristim
    MemberAuthor

    Status (2026-10-04):

    Done:

    • Repo secrets and variables restored. The 6 IDs and backend configs come from live infra, and the 3 email/URL values from the dev Lambda environment.
    • The trust re-pointed in all three ci-cd-permissions roots and applied on 2026-09-29:
      • AWS and Azure match the immutable subject prefix repo:LeanerCloud@86753534/cloud-commitments-platform@1391259743:.... That prefix embeds owner and repo IDs, so it is already rename-proof, and no custom sub template is needed (sec(iac): key CI/CD deploy trust on immutable repository IDs #451 closed).
      • GCP matches repository == LeanerCloud/cloud-commitments-platform on refs/heads/main.
      • The old-name subjects and the stale PR and feat-branch Azure credentials are removed.
      • AWS was applied with -target on the deploy role only, using bucket cudly-terraform-state-909626172446. The KMS and boundary drift is still unapplied and needs its own review.
    • Dev deploys from main passed their auth steps on all three clouds (runs 36632649449, 36632653655, 36632658361).

    Remaining:

    • Step 1: the 15 environments with deployment branch policies (main only; v* tags on prod and aws-fargate-prod for release deploys). Today only dev exists, with no policy, and the environment subjects are ref-agnostic. So until this is done, any branch can deploy through a workflow_dispatch that binds an environment. Tracked with sec(ci): deployment environments have no protection rules, so environment-bound credentialed jobs run unapproved #141.
    • The aws/ci-cd-permissions backend in main.tf still names the wrong-account bucket cudly-terraform-state-dev.
    • Old deploy secrets and variables in cloud-commitments-cli are now inert and can be removed.
  4. cristim commented on Oct 4, 2026

    @cristim
    MemberAuthor

    Follow-up to the status comment above: #154 and #141 are now applied on this repo (2026-10-04).

    • main has ruleset 24469860 (PR + CI Success + Run pre-commit hooks, no bypass actors).
    • 15 environments exist, each limited to a deployment-branch policy of exactly main, including the six gcp-db-* and azure-db-* ones that would otherwise auto-create bare.
    • Verified per environment against the live API. Not tested by dispatching from a feature branch.

    Open consequence: release-triggered deploys to prod and aws-fargate-prod run from a tag ref and will be refused until a tag policy is added. No releases exist yet. Details, rollback and logs are in data/platform-hardening-2026-10-04/ in the split workspace.

  5. cristim commented on Oct 7, 2026

    @cristim
    MemberAuthor

    claimed by cc-platform-w3

    Working my rank 1. Starting with a verification sweep of the live state against the issue acceptance criteria before changing anything.

  6. cristim commented on Oct 7, 2026

    @cristim
    MemberAuthor

    Verification sweep (cc-platform-w3, 2026-10-07). Every part of the acceptance criteria that is a GitHub-side or live-IaC operation is already applied. What is left is in this repo's Terraform, and closing it needs one thing I cannot verify from here.

    Verified applied

    • 15 environments exist, each custom_branch_policies: true with exactly one branch policy, main (type=branch). Checked per environment against the API: aws-db-{dev,staging,prod}, aws-fargate-{dev,staging,prod}, azure-db-{dev,staging,prod}, dev, gcp-db-{dev,staging,prod}, prod, staging.
    • main ruleset 24469860 (PR + CI Success + Run pre-commit hooks, no bypass actors).
    • main-to-main deploys authenticate on all three clouds, e.g. Deploy to AWS Lambda run 37427743382 at 2026-10-06T07:07Z. The re-pointed trust is live.

    The durable half is missing from this repo

    All three ci-cd-permissions roots still key the trust on var.github_repo:

    • terraform/environments/aws/ci-cd-permissions/role.tf:92-128 lists repo:${var.github_repo}:ref:refs/heads/main and the ten repo:${var.github_repo}:environment:<name> entries.
    • terraform/environments/azure/ci-cd-permissions/sp.tf:47,77 builds the same two subject shapes.
    • terraform/environments/gcp/ci-cd-permissions/github_oidc.tf:37 conditions on assertion.repository == '${var.github_repo}', and :46 binds the principal set to attribute.repository/${var.github_repo}.

    The immutable ids from the 2026-10-04 status comment (owner 86753534, repo 1391259743) appear nowhere in this repository. The next bootstrap re-apply therefore reverts the hardening to name-keyed and brings back the rename vector this issue exists to close, silently, because the live state would look correct until it is not.

    Two halves, one of which needs the owner

    1. GCP needs no GitHub-side prerequisite. repository_id is a claim GitHub always mints, so attribute.repository_id = "assertion.repository_id" in the attribute mapping, an assertion.repository_id == '<id>' condition, and a principalSet://.../attribute.repository_id/<id> member make the GCP trust rename-proof entirely inside this repo. I can do this one.
    2. AWS and Azure can only present the id form if the repository's OIDC sub claim template is customized, which is a GitHub repository setting and not a Terraform resource. That is also what the live trust already assumes, since it matches only repo:LeanerCloud@86753534/cloud-commitments-platform@1391259743:... and main-to-main deploys authenticate, so it is very likely configured. I am not going to commit Terraform that stops matching the presented sub on the strength of a status comment: if the template is not configured, the next apply breaks every deploy on every cloud. Confirm the setting exists (repository settings, Actions, OIDC, customize sub) and I will land the AWS and Azure half in the same pass, one atomic commit per cloud.

    Also open, and still true

    prod and aws-fargate-prod are limited to a main branch policy while release-triggered deploys run from a tag ref, so they will be refused. No github provider resource exists under terraform/ or iac/, so environments and their policies are manual repository settings, not a PR in this repo. A tag policy for those two is the fix once a release actually exists.

    Parking rather than claiming: items 1 and 2 above are the only mergeable work, and item 2 is blocked on the sub claim template confirmation. I will pick this back up on confirmation.

  7. cristim commented on Oct 16, 2026

    @cristim
    MemberAuthor

    @cristim the claim above from 2026-10-07 has had no linked PR or activity for over 7 days. Is it still active? If not, I can take this over. [claude-cc-impl]

  8. cristim commented on Oct 17, 2026

    @cristim
    MemberAuthor

    Owner decision (2026-10-17): the 2026-10-07 claim on this issue by an earlier agent session is released. That session has ended and no PR was opened. The platform implementation driver will pick it up in priority order.

  9. cristim commented on Oct 17, 2026

    @cristim
    MemberAuthor

    Claiming: working on this [claude-cc-impl]. Scope: the durable in-repo half. The three ci-cd-permissions Terraform roots key trust on the immutable owner and repository IDs instead of var.github_repo. Applying it remains a bootstrap step for a privileged human, per CLAUDE.md. No apply will be done by agents.

  10. self-assigned this
    on Oct 17, 2026
  11. cristim commented on Nov 4, 2026

    @cristim
    MemberAuthor

    Taking over the earlier claim (2026-10-17, no PR since, and that session is no longer active). I'll pick this up after my current PR. Scope stays the in-repo half: key the three ci-cd-permissions Terraform roots' trust on the immutable GitHub owner and repository IDs, and restrict deploy environments by branch. Validated with terraform validate/plan locally only. Applying remains a bootstrap step for a privileged human, staging first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions