Skip to content

fix(docs): describe per-team ECS execution roles and the Google OIDC identity module - #179

Merged
Alexanderamiri merged 4 commits into
mainfrom
fix/docs-match-code
Oct 31, 2026
Merged

Alexanderamiri merged 4 commits into
mainfrom
fix/docs-match-code

Conversation

@Alexanderamiri

Copy link
Copy Markdown
Member

Problem

Several comments and docs still describe the old setup:

  • terraform/modules/cognito-app-client/main.tf says "the shared execution role reads //apps/* at task launch". Tasks now use per-team roles javabin-ecs-exec-{team}, which can read only /javabin/apps/{team}/* (terraform/platform/iam/main.tf, terraform/modules/platform-data/main.tf).
  • The terraform/platform/iam/main.tf header still calls javabin-ecs-execution "the shared execution role for all ECS tasks" that "reads secrets". The resource comment right below it says the role is deprecated and has no secrets access.
  • docs/platform-modules.md and docs/reusable-modules.md list javabin-ecs-execution as the role that reads secrets / the role platform-data returns.
  • The docs/platform-modules.md identity section says "Not yet implemented ... blocked on Google Admin access for SAML federation". The module is deployed and the internal pool signs in through Google OIDC (feat(identity): cut the internal pool over from GoogleSAML to Google #175).

Change

The comments and docs now match the code. Only comments and docs change; there is no behaviour change.

The developer-facing docs (auth, getting started, how AWS sign-in works and what to do when a Google account isn't provisioned into Identity Center) live in javaBin/docs: javaBin/docs#5

Verification

  • Checked the text against iam/main.tf (aws_iam_role.ecs_exec_team, ecs_exec_team_secrets), platform-data (data.aws_iam_role.ecs_execution = javabin-ecs-exec-${var.team}), identity/main.tf and variables.tf (internal_identity_providers = ["Google"]). Read-only live check: the internal pool has Google and GoogleSAML, and both clients offer only Google.
  • terraform fmt -check on the touched dirs passes. terraform validate in cognito-app-client passes (the only warning, about aws_region.name, was already there).

🤖 Generated with Claude Code

…identity module

cognito-app-client and iam comments, platform-modules.md and
reusable-modules.md still described a shared execution role reading every
team's secrets, and platform-modules.md called the identity module
unimplemented and SAML-based.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@Alexanderamiri
Alexanderamiri requested a review from a team as a code owner October 31, 2026 12:04
@github-actions

Copy link
Copy Markdown

Terraform Plan

✅ No changes — infrastructure is up to date.

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

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

@Alexanderamiri

Copy link
Copy Markdown
Member Author

Addressed the review in 1cc0885. Both findings checked out against the code and live AWS, so I fixed both and rejected none.

  1. CLAUDE.md:308 and :327 (Cognito cutover): I marked the internal Cognito pool as deployed on Google OIDC. The Known Issues entry now says, in the past tense, that the cutover is done: internal_identity_providers defaults to ["Google"], and GoogleSAML is still in the pool but offered on no client until it is deleted. I checked live: pool eu-central-1_Icikv3dtD has IdPs Google and GoogleSAML, and both clients (cupcake-cupcake, platform-idp-probe) support only Google.
  2. terraform/org/identity-center.tf ABAC comments (lines 10, 22, 57): I added a note that the team attribute is never populated, so javabin-developer gets no access to team resources, and pointed to the per-team javabin-team-{team} permission sets. Only comments changed. The permission set description is unchanged, so nothing changes on the live resource.

Verification: terraform fmt -check and terraform validate (terraform/org, -backend=false) pass.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

Terraform Plan

✅ No changes — infrastructure is up to date.

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]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

@Alexanderamiri

Copy link
Copy Markdown
Member Author

Merged origin/main (#175, #178, #180, #184, #185, #186, #187) into this branch with a merge commit. Conflicts:

The other files merged cleanly. Checked that #185's new RDS password path (/javabin/apps/{team}/{app}/{name}/...) still sits under the /javabin/apps/{team}/* scope this PR describes for the per-team exec role. Checks: scripts/tests (184) and lambda-src tests (249) pass, terraform fmt -check passes, terraform validate in terraform/platform passes.

# Conflicts:
#	terraform/org/identity-center.tf
@github-actions

Copy link
Copy Markdown

Terraform Plan

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

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

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # module.iam.aws_iam_role.ci_infra will be updated in-place
  ~ resource "aws_iam_role" "ci_infra" {
      ~ assume_role_policy    = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Condition = {
                          ~ StringLike   = {
                              ~ "token.actions.githubusercontent.com:sub"              = [
                                    # (1 unchanged element hidden)
                                    "repo:javaBin/platform:environment:production",
                                  + "repo:javaBin@95047/platform@1175354559:ref:refs/heads/main",
                                  + "repo:javaBin@95047/platform@1175354559:environment:production",
                                ]
                                # (1 unchanged attribute hidden)
                            }
                            # (1 unchanged attribute hidden)
                        }
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        id                    = "javabin-ci-infra"
        name                  = "javabin-ci-infra"
        tags                  = {
            "Name" = "javabin-ci-infra"
        }
        # (9 unchanged attributes hidden)

        # (2 unchanged blocks hidden)
    }

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

        # (1 unchanged block hidden)
    }

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

        # (1 unchanged block hidden)
    }

  # module.iam.aws_iam_role_policy.ci_deploy_ecs["cupcake"] will be updated in-place
  ~ resource "aws_iam_role_policy" "ci_deploy_ecs" {
        id     = "javabin-ci-deploy-cupcake:ecs-deploy"
        name   = "ecs-deploy"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Action    = [
                            # (1 unchanged element hidden)
                            "ecs:DescribeServices",
                          + "ecs:ListTagsForResource",
                          + "ecs:DescribeTasks",
                        ]
                        # (4 unchanged attributes hidden)
                    },
                  + {
                      + Action    = [
                          + "ecs:ListTasks",
                        ]
                      + Condition = {
                          + ArnEquals = {
                              + "ecs:cluster" = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "EcsListTasks"
                    },
                  + {
                      + Action    = [
                          + "ecs:TagResource",
                        ]
                      + Condition = {
                          + StringEquals = {
                              + "ecs:CreateAction" = "RegisterTaskDefinition"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "EcsTagOnRegister"
                    },
                    {
                        Action   = [
                            "ecs:DescribeTaskDefinition",
                            "ecs:RegisterTaskDefinition",
                            "ecs:DeregisterTaskDefinition",
                        ]
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "EcsTaskDefinitionManagement"
                    },
                    # (1 unchanged element hidden)
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.iam.aws_iam_role_policy.ci_deploy_ecs["javazoneweb"] will be updated in-place
  ~ resource "aws_iam_role_policy" "ci_deploy_ecs" {
        id     = "javabin-ci-deploy-javazoneweb:ecs-deploy"
        name   = "ecs-deploy"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Action    = [
                            # (1 unchanged element hidden)
                            "ecs:DescribeServices",
                          + "ecs:ListTagsForResource",
                          + "ecs:DescribeTasks",
                        ]
                        # (4 unchanged attributes hidden)
                    },
                  + {
                      + Action    = [
                          + "ecs:ListTasks",
                        ]
                      + Condition = {
                          + ArnEquals = {
                              + "ecs:cluster" = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "EcsListTasks"
                    },
                  + {
                      + Action    = [
                          + "ecs:TagResource",
                        ]
                      + Condition = {
                          + StringEquals = {
                              + "ecs:CreateAction" = "RegisterTaskDefinition"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "EcsTagOnRegister"
                    },
                    {
                        Action   = [
                            "ecs:DescribeTaskDefinition",
                            "ecs:RegisterTaskDefinition",
                            "ecs:DeregisterTaskDefinition",
                        ]
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "EcsTaskDefinitionManagement"
                    },
                    # (1 unchanged element hidden)
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.iam.aws_iam_role_policy.ci_deploy_ecs["jzwatch"] will be updated in-place
  ~ resource "aws_iam_role_policy" "ci_deploy_ecs" {
        id     = "javabin-ci-deploy-jzwatch:ecs-deploy"
        name   = "ecs-deploy"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Action    = [
                            # (1 unchanged element hidden)
                            "ecs:DescribeServices",
                          + "ecs:ListTagsForResource",
                          + "ecs:DescribeTasks",
                        ]
                        # (4 unchanged attributes hidden)
                    },
                  + {
                      + Action    = [
                          + "ecs:ListTasks",
                        ]
                      + Condition = {
                          + ArnEquals = {
                              + "ecs:cluster" = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "EcsListTasks"
                    },
                  + {
                      + Action    = [
                          + "ecs:TagResource",
                        ]
                      + Condition = {
                          + StringEquals = {
                              + "ecs:CreateAction" = "RegisterTaskDefinition"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "EcsTagOnRegister"
                    },
                    {
                        Action   = [
                            "ecs:DescribeTaskDefinition",
                            "ecs:RegisterTaskDefinition",
                            "ecs:DeregisterTaskDefinition",
                        ]
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "EcsTaskDefinitionManagement"
                    },
                    # (1 unchanged element hidden)
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.iam.aws_iam_role_policy.ci_deploy_ecs["testteam"] will be updated in-place
  ~ resource "aws_iam_role_policy" "ci_deploy_ecs" {
        id     = "javabin-ci-deploy-testteam:ecs-deploy"
        name   = "ecs-deploy"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Action    = [
                            # (1 unchanged element hidden)
                            "ecs:DescribeServices",
                          + "ecs:ListTagsForResource",
                          + "ecs:DescribeTasks",
                        ]
                        # (4 unchanged attributes hidden)
                    },
                  + {
                      + Action    = [
                          + "ecs:ListTasks",
                        ]
                      + Condition = {
                          + ArnEquals = {
                              + "ecs:cluster" = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "EcsListTasks"
                    },
                  + {
                      + Action    = [
                          + "ecs:TagResource",
                        ]
                      + Condition = {
                          + StringEquals = {
                              + "ecs:CreateAction" = "RegisterTaskDefinition"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "EcsTagOnRegister"
                    },
                    {
                        Action   = [
                            "ecs:DescribeTaskDefinition",
                            "ecs:RegisterTaskDefinition",
                            "ecs:DeregisterTaskDefinition",
                        ]
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "EcsTaskDefinitionManagement"
                    },
                    # (1 unchanged element hidden)
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.lambdas.aws_cloudwatch_event_rule.budget_enforcer_reconcile will be created
  + resource "aws_cloudwatch_event_rule" "budget_enforcer_reconcile" {
      + arn                 = (known after apply)
      + description         = "Reconcile team budget suspensions every 6 hours"
      + event_bus_name      = "default"
      + force_destroy       = false
      + id                  = (known after apply)
      + name                = "javabin-budget-enforcer-reconcile"
      + name_prefix         = (known after apply)
      + schedule_expression = "rate(6 hours)"
      + tags_all            = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }
    }

  # module.lambdas.aws_cloudwatch_event_rule.team_provisioner_access_schedule will be created
  + resource "aws_cloudwatch_event_rule" "team_provisioner_access_schedule" {
      + arn                 = (known after apply)
      + description         = "Retry Identity Center group memberships for members Google SCIM has since provisioned"
      + event_bus_name      = "default"
      + force_destroy       = false
      + id                  = (known after apply)
      + name                = "javabin-team-provisioner-access-schedule"
      + name_prefix         = (known after apply)
      + schedule_expression = "rate(3 hours)"
      + tags_all            = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }
    }

  # module.lambdas.aws_cloudwatch_event_target.budget_enforcer_reconcile will be created
  + resource "aws_cloudwatch_event_target" "budget_enforcer_reconcile" {
      + arn            = "arn:aws:lambda:eu-central-1:553637109631:function:javabin-budget-enforcer"
      + event_bus_name = "default"
      + force_destroy  = false
      + id             = (known after apply)
      + input          = jsonencode(
            {
              + action = "reconcile"
            }
        )
      + rule           = "javabin-budget-enforcer-reconcile"
      + target_id      = "reconcile-budget-enforcer"
    }

  # module.lambdas.aws_cloudwatch_event_target.team_provisioner_access will be created
  + resource "aws_cloudwatch_event_target" "team_provisioner_access" {
      + arn            = "arn:aws:lambda:eu-central-1:553637109631:function:javabin-team-provisioner"
      + event_bus_name = "default"
      + force_destroy  = false
      + id             = (known after apply)
      + input          = jsonencode(
            {
              + action = "sync_access_members"
            }
        )
      + rule           = "javabin-team-provisioner-access-schedule"
      + target_id      = "invoke-team-provisioner-access"
    }

  # module.lambdas.aws_iam_role_policy.budget_enforcer will be updated in-place
  ~ resource "aws_iam_role_policy" "budget_enforcer" {
        id     = "javabin-budget-enforcer:javabin-budget-enforcer"
        name   = "javabin-budget-enforcer"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                    # (2 unchanged elements hidden)
                    {
                        Action   = "ecs:ListServices"
                        Effect   = "Allow"
                        Resource = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
                        Sid      = "ECSListServicesUnconstrained"
                    },
                  ~ {
                      ~ Action   = "ecs:UpdateService" -> [
                          + "ecs:UpdateService",
                          + "ecs:TagResource",
                        ]
                        # (3 unchanged attributes hidden)
                    },
                  + {
                      + Action   = [
                          + "ssm:GetParameter",
                          + "ssm:GetParametersByPath",
                          + "ssm:PutParameter",
                          + "ssm:DeleteParameter",
                        ]
                      + Effect   = "Allow"
                      + Resource = [
                          + "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/budget-suspensions",
                          + "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/budget-suspensions/*",
                        ]
                      + Sid      = "SuspensionMarkers"
                    },
                  + {
                      + Action   = "rds:DescribeDBInstances"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:rds:eu-central-1:553637109631:db:*"
                      + Sid      = "RDSDescribe"
                    },
                  + {
                      + Action    = [
                          + "rds:StopDBInstance",
                          + "rds:StartDBInstance",
                        ]
                      + Condition = {
                          + Null = {
                              + "aws:ResourceTag/team" = "false"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "arn:aws:rds:eu-central-1:553637109631:db:*"
                      + Sid       = "RDSStopStart"
                    },
                  + {
                      + Action   = "budgets:ViewBudget"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:budgets::553637109631:budget/javabin-team-*"
                      + Sid      = "BudgetsRead"
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.lambdas.aws_iam_role_policy.team_provisioner will be updated in-place
  ~ resource "aws_iam_role_policy" "team_provisioner" {
        id     = "javabin-team-provisioner:javabin-team-provisioner"
        name   = "javabin-team-provisioner"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                    # (4 unchanged elements hidden)
                    {
                        Action   = [
                            "identitystore:CreateGroup",
                            "identitystore:DescribeGroup",
                            "identitystore:DescribeUser",
                            "identitystore:ListGroups",
                            "identitystore:CreateGroupMembership",
                            "identitystore:ListGroupMemberships",
                            "identitystore:DeleteGroupMembership",
                            "identitystore:ListUsers",
                        ]
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "IdentityStoreSync"
                    },
                  ~ {
                      ~ Action   = [
                            # (1 unchanged element hidden)
                            "sso:DescribeAccountAssignmentCreationStatus",
                          + "sso:ListAccountAssignments",
                            "sso:ListPermissionSets",
                            # (2 unchanged elements hidden)
                            "sso:PutInlinePolicyToPermissionSet",
                          - "sso:AttachCustomerManagedPolicyReferenceToPermissionSet",
                            "sso:DetachCustomerManagedPolicyReferenceFromPermissionSet",
                          + "sso:AttachManagedPolicyToPermissionSet",
                          + "sso:PutPermissionsBoundaryToPermissionSet",
                            "sso:ProvisionPermissionSet",
                            # (1 unchanged element hidden)
                        ]
                        # (3 unchanged attributes hidden)
                    },
                  ~ {
                      ~ Resource = "arn:aws:iam::553637109631:policy/javabin-team-*-guardrails" -> [
                          + "arn:aws:iam::553637109631:policy/javabin-team-*-guardrails",
                          + "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess",
                        ]
                        # (3 unchanged attributes hidden)
                    },
                    {
                        Action   = [
                            "iam:GetSAMLProvider",
                            "iam:GetRole",
                            "iam:CreateRole",
                            "iam:AttachRolePolicy",
                            "iam:PutRolePolicy",
                            "iam:UpdateRole",
                            "iam:UpdateRoleDescription",
                            "iam:ListAttachedRolePolicies",
                            "iam:ListRolePolicies",
                            "iam:DeleteRole",
                            "iam:DeleteRolePolicy",
                            "iam:DetachRolePolicy",
                        ]
                        Effect   = "Allow"
                        Resource = [
                            "arn:aws:iam::553637109631:role/aws-reserved/sso.amazonaws.com/*",
                            "arn:aws:iam::553637109631:saml-provider/AWSSSO_*",
                        ]
                        Sid      = "IAMForSSOAssignment"
                    },
                  + {
                      + Action    = "iam:PutRolePermissionsBoundary"
                      + Condition = {
                          + ArnLike = {
                              + "iam:PermissionsBoundary" = "arn:aws:iam::553637109631:policy/javabin-team-*-guardrails"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "arn:aws:iam::553637109631:role/aws-reserved/sso.amazonaws.com/*"
                      + Sid       = "SetTeamGuardrailsBoundary"
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.lambdas.aws_lambda_function.budget_enforcer will be updated in-place
  ~ resource "aws_lambda_function" "budget_enforcer" {
        id                             = "javabin-budget-enforcer"
      ~ last_modified                  = "2026-03-26T20:01:54.976+0000" -> (known after apply)
      ~ source_code_hash               = "i3E4tI5mT0qCS601izSv52QOgwAndKfcZ3JkLzxTNis=" -> "di7/qFyHX8tYBT6OXRTbwov0mZQqBuJuNY4jdUtzM2E="
        tags                           = {}
        # (21 unchanged attributes hidden)

      ~ environment {
          ~ variables = {
              + "AWS_ACCOUNT_ID"     = "553637109631"
              + "SUSPENSION_PATH"    = "/javabin/budget-suspensions"
                # (3 unchanged elements hidden)
            }
        }

        # (3 unchanged blocks hidden)
    }

  # module.lambdas.aws_lambda_function.team_provisioner will be updated in-place
  ~ resource "aws_lambda_function" "team_provisioner" {
        id                             = "javabin-team-provisioner"
      ~ last_modified                  = "2026-10-31T13:03:32.000+0000" -> (known after apply)
      ~ source_code_hash               = "3I4td1T2ENiRLUUEjeBLPinRaiXc/sZrwOT+Ai83lOY=" -> "tpW4OZM/7/EBE9sKi6c6DAazYfsyHZV2rG/JJUYTTng="
        tags                           = {}
        # (21 unchanged attributes hidden)

      ~ environment {
          ~ variables = {
              + "AWS_ACCESS_GROUP"             = "[email protected]"
                # (19 unchanged elements hidden)
            }
        }

        # (3 unchanged blocks hidden)
    }

  # module.lambdas.aws_lambda_permission.budget_enforcer_reconcile will be created
  + resource "aws_lambda_permission" "budget_enforcer_reconcile" {
      + action              = "lambda:InvokeFunction"
      + function_name       = "javabin-budget-enforcer"
      + id                  = (known after apply)
      + principal           = "events.amazonaws.com"
      + source_arn          = (known after apply)
      + statement_id        = "AllowEventBridgeReconcile"
      + statement_id_prefix = (known after apply)
    }

  # module.lambdas.aws_lambda_permission.team_provisioner_access_schedule will be created
  + resource "aws_lambda_permission" "team_provisioner_access_schedule" {
      + action              = "lambda:InvokeFunction"
      + function_name       = "javabin-team-provisioner"
      + id                  = (known after apply)
      + principal           = "events.amazonaws.com"
      + source_arn          = (known after apply)
      + statement_id        = "AllowEventBridgeAccessSchedule"
      + statement_id_prefix = (known after apply)
    }

Plan: 6 to add, 11 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: 🔴 HIGH

Plan adds 6 resources and updates 11 in place (0 destroyed) and materially widens CI OIDC trust, ECS/IAM/RDS permissions, and the team provisioner's ability to set permission boundaries, which warrants review before apply.

  • 🔒 [security] CI OIDC trust policies for ci_infra, ci_infra_plan and ci_registry gain new sub values with a foreign repo identifier, 'repo:javaBin@95047/platform@1175354559:' and 'repo:javaBin@95047/registry@1175355770:' (and ref/environment variants). The javaBin@<owner-id>/<repo>@<repo-id> format is the GitHub subject claim for a renamed or recreated repo, so these entries likely trust a repository identity that is not the current javaBin/platform or javaBin/registry. ci_infra_plan now trusts the wildcard 'repo:...:*' for both. Confirm the IDs are expected before applying; if not, these roles could be assumed by an unintended repo.
  • 🔒 [security] team_provisioner IAM policy adds iam:PutRolePermissionsBoundary on sso.amazonaws.com reserved roles, conditioned only on the boundary being javabin-team-*-guardrails. It also adds sso:AttachManagedPolicyToPermissionSet, sso:PutPermissionsBoundaryToPermissionSet and sso:ListAccountAssignments, and removes sso:AttachCustomerManagedPolicyReferenceToPermissionSet. Its Resource for the guardrails policy gains arn:aws:iam::aws:policy/job-function/ViewOnlyAccess. This lets the Lambda change boundaries on SSO roles, which is a privilege-escalation path if the Lambda is compromised. Consider narrowing the resource scope and confirming the intent of each action.
  • 🔒 [security] ECS deploy roles for cupcake, javazoneweb, jzwatch and testteam gain ecs:TagResource (only when CreateAction is RegisterTaskDefinition), ecs:ListTasks scoped to the javabin-platform cluster, and ecs:ListTagsForResource and ecs:DescribeTasks. The grants are modest, but the ECS deploy policies are unchanged in the Resource/Action shape of RegisterTaskDefinition on '*'. Routine, but worth noting.
  • 🔒 [security] budget-enforcer policy adds rds:StopDBInstance and rds:StartDBInstance on all RDS instances, gated only by the aws:ResourceTag/team key existing (Null=false), plus ssm:PutParameter/DeleteParameter on /javabin/budget-suspensions and budgets:ViewBudget. Any tagged DB can be stopped by this Lambda. The condition does not restrict which teams or tag values. Consider scoping to specific ARNs or tag values.
  • 💥 [destruction] No resources are destroyed. Updates are in place. Lambda source_code_hash changes for budget_enforcer and team_provisioner will redeploy code. The team_provisioner Lambda also adds AWS_ACCESS_GROUP=[email protected] to its environment. Note last_modified shows a 2026-10-31 timestamp, which is odd for a previous deploy, so verify the source.
  • 💰 [cost] Two new EventBridge rules run on schedules: budget-enforcer reconcile every 6 hours and team-provisioner access sync every 3 hours. Each invocation is a small Lambda cost, so the overall cost impact is low.

@Alexanderamiri
Alexanderamiri merged commit 73f79c0 into main Oct 31, 2026
4 checks passed
@Alexanderamiri
Alexanderamiri deleted the fix/docs-match-code branch October 31, 2026 14:17
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