Skip to content

fix(iam): accept GitHub immutable OIDC subjects in CI trust policies - #168

Merged
Alexanderamiri merged 1 commit into
mainfrom
fix/oidc-immutable-sub
Oct 29, 2026
Merged

Alexanderamiri merged 1 commit into
mainfrom
fix/oidc-immutable-sub

Conversation

@Alexanderamiri

Copy link
Copy Markdown
Member

Problem

2027.javazone.no (team javazoneweb) fails tf-plan with Not authorized to perform sts:AssumeRoleWithWebIdentity. The repo was created 2026-10-26 and has use_immutable_subject: true, so its sub claim is repo:javaBin@95047/2027.javazone.no@1433986972:.... The trust policies only allow repo:javaBin/*:*. GitHub won't let the repo opt out.

Change

  • ci_app_broker, ci_override_approver and ci_apply_gate now also accept repo:javaBin@95047/*:*, pinned to the org ID (new github_org_id variable, default 95047).
  • slack_alert strips the @<id> parts when it parses sub for CI attribution, and a test covers it.
  • The broker and apply-gate Lambdas check the repository claim, not sub, so they need no change.

Verification

  • python3 -m unittest discover -s terraform/lambda-src/tests: 159 passed
  • terraform validate passes. A local targeted plan updates the three roles in place. The local plan also showed javazoneweb destroys, but only because it had no registered-teams.auto.tfvars, which CI syncs from the org.

🤖 Generated with Claude Code

Repos created after GitHub enabled immutable subjects get sub claims like
repo:javaBin@95047/<repo>@<repo_id>:..., which repo:javaBin/*:* never matches,
so AssumeRoleWithWebIdentity fails (seen on 2027.javazone.no). Allow the
org-ID-pinned form on the app-broker, override-approver and apply-gate roles.
The broker and apply-gate Lambdas check the `repository` claim, not `sub`, so
they need no change. slack_alert parses `sub` for attribution and now strips
the IDs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Alexanderamiri
Alexanderamiri requested a review from a team as a code owner October 29, 2026 20:19
@github-actions

Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 0 to add, 6 to change, 0 to destroy.

Plan output
Acquiring state lock. This may take a few moments...
aws_ssm_parameter.atlassian_api_key: Refreshing state... [id=/javabin/platform/atlassian-api-key]
aws_ssm_parameter.atlassian_org_id: Refreshing state... [id=/javabin/platform/atlassian-org-id]

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:

  # module.iam.aws_iam_role.ci_app_broker will be updated in-place
  ~ resource "aws_iam_role" "ci_app_broker" {
      ~ assume_role_policy    = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Condition = {
                          ~ StringLike   = {
                              ~ "token.actions.githubusercontent.com:sub"              = "repo:javaBin/*:*" -> [
                                  + "repo:javaBin/*:*",
                                  + "repo:javaBin@95047/*:*",
                                ]
                                # (1 unchanged attribute hidden)
                            }
                            # (1 unchanged attribute hidden)
                        }
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        id                    = "javabin-ci-app-broker"
        name                  = "javabin-ci-app-broker"
        tags                  = {}
        # (9 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # module.iam.aws_iam_role.ci_apply_gate will be updated in-place
  ~ resource "aws_iam_role" "ci_apply_gate" {
      ~ assume_role_policy    = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Condition = {
                          ~ StringLike   = {
                              ~ "token.actions.githubusercontent.com:sub"              = "repo:javaBin/*:*" -> [
                                  + "repo:javaBin/*:*",
                                  + "repo:javaBin@95047/*:*",
                                ]
                                # (1 unchanged attribute hidden)
                            }
                            # (1 unchanged attribute hidden)
                        }
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        id                    = "javabin-ci-apply-gate"
        name                  = "javabin-ci-apply-gate"
        tags                  = {
            "Name" = "javabin-ci-apply-gate"
        }
        # (9 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # module.iam.aws_iam_role.ci_override_approver will be updated in-place
  ~ resource "aws_iam_role" "ci_override_approver" {
      ~ assume_role_policy    = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Condition = {
                          ~ StringLike   = {
                              ~ "token.actions.githubusercontent.com:sub"              = "repo:javaBin/*:*" -> [
                                  + "repo:javaBin/*:*",
                                  + "repo:javaBin@95047/*:*",
                                ]
                                # (1 unchanged attribute hidden)
                            }
                            # (1 unchanged attribute hidden)
                        }
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        id                    = "javabin-ci-override-approver"
        name                  = "javabin-ci-override-approver"
        tags                  = {
            "Name" = "javabin-ci-override-approver"
        }
        # (9 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # module.identity.aws_cognito_identity_provider.internal_google[0] will be updated in-place
  ~ resource "aws_cognito_identity_provider" "internal_google" {
        id                = "eu-central-1_Icikv3dtD:Google"
      ~ provider_details  = {
          ~ "authorize_url"                 = "https://accounts.google.com/o/oauth2/v2/auth" -> "https://accounts.google.com/o/oauth2/v2/auth?hd=java.no&prompt=select_account"
            # (8 unchanged elements hidden)
        }
        # (5 unchanged attributes hidden)
    }

  # module.lambdas.aws_lambda_function.securityhub_summary will be updated in-place
  ~ resource "aws_lambda_function" "securityhub_summary" {
        id                             = "javabin-securityhub-summary"
      ~ last_modified                  = "2026-10-26T14:19:29.000+0000" -> (known after apply)
      ~ source_code_hash               = "ci2qbjUrBx0+yEslzlkKpQdZ0eQltqrBbHoakTuR6Ik=" -> "vXPaiZkQchG44V7Ee6LhlKCzD1Ny6SQ+GuzSl47sBo0="
        tags                           = {}
        # (21 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

  # module.lambdas.aws_lambda_function.slack_alert will be updated in-place
  ~ resource "aws_lambda_function" "slack_alert" {
        id                             = "javabin-slack-alert"
      ~ last_modified                  = "2026-10-26T14:19:23.000+0000" -> (known after apply)
      ~ source_code_hash               = "ci2qbjUrBx0+yEslzlkKpQdZ0eQltqrBbHoakTuR6Ik=" -> "vXPaiZkQchG44V7Ee6LhlKCzD1Ny6SQ+GuzSl47sBo0="
        tags                           = {}
        # (21 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

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

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

Saved the plan to: tfplan

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

LLM Review

Risk: 🟡 MEDIUM

Plan updates 6 existing resources (0 add, 0 destroy): widens GitHub OIDC trust on three CI IAM roles to also accept repo:javaBin@95047/*, changes the Google identity provider authorize URL to restrict to the java.no domain, and redeploys two Lambda functions with new code.

  • 🔒 [security] Three CI roles (ci_app_broker, ci_apply_gate, ci_override_approver) get a new StringLike trust entry 'repo:javaBin@95047/:' added to the GitHub OIDC sub condition, alongside the existing 'repo:javaBin/:'. The existing pattern already trusts every repo in the javaBin org, so the new entry is a second, differently-shaped subject that needs confirmation: it appears to target a repository ID/ref form rather than a repo name. Verify it is intentional and that it does not unintentionally broaden who can assume the apply gate or override approver roles. These roles are sensitive, so this is the main item to check before apply.
  • 🔒 [security] The Google identity provider authorize_url gains hd=java.no&prompt=select_account. This is a tightening: the hd parameter hints Google sign-in to java.no accounts and forces account selection. Note that hd is only a UI hint and is not enforced server-side, so it does not by itself restrict sign-in to the domain. Enforcement depends on the app's hosted domain checks; confirm that separately.
  • ✅ [routine] Both Lambda functions (securityhub_summary, slack_alert) have only source_code_hash and last_modified changing, which indicates a code-only redeploy with the same configuration. Low risk, but confirm the new artifacts are from the expected build.
  • 💥 [destruction] No resources are created or destroyed. The changes are all in-place updates, so no database, network or other critical infrastructure is removed or replaced.
  • 💰 [cost] No new billable resources (NAT gateways, load balancers, RDS, etc.) are added. No cost impact expected.

@Alexanderamiri
Alexanderamiri merged commit 15c569e into main Oct 29, 2026
4 checks passed
@Alexanderamiri
Alexanderamiri deleted the fix/oidc-immutable-sub branch October 29, 2026 20:20
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