Skip to content

fix(generator): derive ALB priorities, check them at plan, require name, validate compute - #181

Merged
Alexanderamiri merged 3 commits into
mainfrom
fix/alb-priority-validation
Oct 31, 2026
Merged

Alexanderamiri merged 3 commits into
mainfrom
fix/alb-priority-validation

Conversation

@Alexanderamiri

@Alexanderamiri Alexanderamiri commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Problem

  • ALB priority collisions (finding 1). app-template ships routing.priority: 100, which cupcake (frosting) already holds. jz-watch run 40079243698 failed at apply with CreateRule PriorityInUse: Priority '100' is currently in use. routing.login rules take the numbers directly below routing.priority, so nearby picks collide too. The docs said to "coordinate via the registry", but the registry has nothing for priorities, and nothing checked them before apply.
  • name default was inconsistent (part of finding 13). Only the generator fell back to the repo name. detect.yml emitted app_name="", the CLI rejects a missing name, and a repo name like 2027.javazone.no fails NAME_RE anyway.
  • Compute values were not validated (part of finding 14). An invalid Fargate cpu/memory pair, port or desired_count only failed at apply.

Change

  • scripts/generate-modules.py
    • routing.priority is optional. When it is left out, derived_priority(team, name) takes sha256 of team/name and maps it to one of 4454 slots of 11 in 1000-50000. The host rule sits at the top of its slot and up to 10 login rules sit below it, inside the same slot. The value is printed as a ::notice. domain: no longer needs a priority.
    • An explicit routing.priority must be 1-999, so it can never land in the derived range. Explicit values are passed through unchanged.
    • name is required, with a clear message. The message for a dotted name gives an example fix (2027.javazone.no becomes e.g. javazone-2027). The APP_SERVICE repo-name fallback is removed.
    • compute.cpu and compute.memory must be whole numbers and a valid Fargate task size; the error lists the memory values allowed for that cpu. port must be 1-65535 and desired_count 0 or more.
  • New scripts/check-alb-priorities.py, run in tf-plan after the plan when it has changes, using the team's read-only plan credentials (ReadOnlyAccess covers elbv2:DescribeRules). It takes every aws_lb_listener_rule the plan creates or moves and compares its priority with the live rules on that listener.
    • Priorities the plan frees in the same run (delete-then-create, or the app's own rule moving) count as free.
    • On a conflict it fails with ::error, naming the host that holds the priority and suggesting the lowest free number that leaves 10 free below it.
    • If the plan or the live rules cannot be read, it only warns, because apply still catches a conflict.
  • detect.yml fails when app.yaml has no name. expand-terraform.yml no longer passes APP_SERVICE.
  • Docs: docs/app-yaml-reference.md (name, compute table, routing.priority, login priorities, domain), docs/reusable-workflows.md and CLAUDE.md.

No rule churn

These are the live rules on javabin-platform-alb:443:

Priority Host
10 password
100 cupcake
101 cupcake-backend
150 watch
200 2027
900 test

Every app.yaml in the org (cupcake, frosting, jz-watch, 2027.javazone.no, platform-test-app, app-template) sets name and an explicit priority below 1000. Their generated priorities are unchanged; I checked by generating each app.yaml from GitHub with this branch.

Verification

  • python3 -m unittest discover -s scripts/tests: 153 tests OK, including new tests for name, priority range, derived priority (stability, range, room for 10 login rules, domain:), compute validation, and test_check_alb_priorities.py.
  • python3 -m unittest discover -s terraform/lambda-src/tests: 219 tests OK.
  • Ran the check against the live listener rules (read-only describe-rules):
    • priority 100 is reported as held by cupcake.javazone.no, with suggestion 21;
    • the derived priority for cupcake/cupcake-backend (2297) passes.
  • actionlint on the changed workflows: the only finding is the existing SC2129 style note in detect.yml.

Follow-ups (other repos)

🤖 Generated with Claude Code

…me, validate compute

routing.priority is now optional: without it the generator derives a stable
priority from team/name in 1000-50000, in a slot that leaves room for the
login rules. Explicit priorities (1-999) are kept, so live rules do not move.
tf-plan checks planned listener rules against the live ones and fails with
the holder's host and a free number instead of PriorityInUse at apply.

name is required everywhere (detect, generator, CLI); the repo-name default
in the generator is gone. compute.cpu/memory must be a Fargate task size,
port 1-65535, desired_count >= 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Alexanderamiri
Alexanderamiri requested a review from a team as a code owner October 31, 2026 12:05
@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.

…-name app fallback

tf-plan now records the conflict, posts it at the top of the plan comment and
fails the job in a final step. auth-doctor no longer falls back to the
repository name for the app name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Alexanderamiri

Copy link
Copy Markdown
Member Author

Review follow-up (new commit on this branch):

Changed

  • tf-plan comment on conflict (minor, tf-plan.yml): the ALB priority check now has continue-on-error and writes its conflicts to alb-priority-errors.txt. post-plan-comment.sh puts them at the top of the PR comment, and a final step fails the job, so the PR gets the plan comment and the conflict. Tests: test_check_alb_priorities.ErrorsFileTest, plus two new cases in test_post_plan_comment.
  • APP_SERVICE fallback (minor, tf-apply.yml / auth-doctor.py): removed the env line and the repo-name fallback. auth-doctor now uses --app or the app.yaml name, and exits 1 with "name is required" when neither is set. Test: test_auth_doctor.NameTest.
  • app-template (missed scope): opened fix(app.yaml): leave routing.priority out so the platform derives one app-template#4. It drops priority: 100 and the "(1-50000)" comment, and documents the optional 1-999 explicit range. Merge it after this PR.

Not changed here

Verified: python3 -m unittest discover -s scripts/tests (158 OK), python3 -m unittest discover -s terraform/lambda-src/tests (OK), actionlint on tf-plan.yml and tf-apply.yml (clean), sh -n post-plan-comment.sh.

🤖 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.

Both sides added a _validate_compute; keep main's (user, grace period,
exec) and have it call this branch's size/port/count checks, now
_validate_compute_sizes, so neither definition shadows the other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Alexanderamiri

Copy link
Copy Markdown
Member Author

Merged origin/main (#178, #180, #184, #185, #186, #187) in 8c06348. Conflicts and how they were resolved:

  • scripts/generate-modules.py: this PR and fix(ecs-service): make /tmp writable for non-root apps, add grace period and ECS Exec #184 each added a function called _validate_compute. The second definition would have silently replaced the first, so the cpu/memory/port/desired_count checks would never have run. This PR's version is now _validate_compute_sizes, and main's _validate_compute (user, health_check_grace_period, exec) calls it first. At the call site I kept main's form, _validate_compute(app.get("compute") or {}), so validation also runs when there is no compute section. The defaults pass, so that changes nothing for such apps.
  • docs/app-yaml-reference.md: kept this PR's cpu/memory/port ranges and the Fargate size table. Kept main's health_check_grace_period/exec example lines and sections. The desired_count comment now reads default: 1; 0 or more — applied by ecs-deploy, not tf-apply. Main's old one-line "Valid CPU/memory combinations" link is gone because the table replaces it.

The other files merged without conflicts. Checked: scripts/tests (209 OK), lambda-src/tests (249 OK), terraform fmt -check -recursive, and terraform validate in terraform/platform.

@github-actions

Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 3 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.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.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)
                    },
                ]
                # (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",
                            # (7 unchanged elements hidden)
                        ]
                        # (3 unchanged attributes hidden)
                    },
                    {
                        Action   = [
                            "iam:GetPolicy",
                            "iam:GetPolicyVersion",
                            "iam:ListPolicyTags",
                        ]
                        Effect   = "Allow"
                        Resource = "arn:aws:iam::553637109631:policy/javabin-team-*-guardrails"
                        Sid      = "ReadTeamGuardrails"
                    },
                    # (1 unchanged element hidden)
                ]
                # (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=" -> "lM6wzIndpyMp1ZmU5nft0SteLorI7ppyEyOYIL/2hjU="
        tags                           = {}
        # (21 unchanged attributes hidden)

        # (4 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=" -> "4Gcog5O29Hs6CEnMQPK5WWLQ1aShJnqsS3zVkqRjVlA="
        tags                           = {}
        # (21 unchanged attributes hidden)

      ~ environment {
          ~ variables = {
              + "AWS_ACCESS_GROUP"             = "javabin-developer@java.no"
                # (19 unchanged elements hidden)
            }
        }

        # (3 unchanged blocks hidden)
    }

  # 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: 3 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 3 resources and updates 11 in place (0 destroyed), but it widens GitHub OIDC trust on the CI deploy/plan/registry IAM roles to foreign-named repos (javaBin@95047/..., javaBin@95047/...) and expands ECS and Identity Center permissions.

  • 🔒 [security] The OIDC trust policies for ci_infra, ci_infra_plan and ci_registry gain new token.actions.githubusercontent.com:sub values such as 'repo:javaBin@95047/platform@1175354559:', 'repo:javaBin@95047/platform@1175354559:environment:production' and 'repo:javaBin@95047/registry@1175355770:ref:refs/heads/main'. These subjects carry repository IDs and a name with an '@' suffix, which look like a repository that was renamed, deleted or recreated. Anyone who can claim that repo name could assume these roles. ci_infra_plan's wildcard 'repo:javaBin@95047/platform@1175354559:' is especially broad. Verify the origin of these subjects before applying; this is the main reason for HIGH risk.
  • 🔒 [security] ci_infra_plan's trust now also includes the wildcard subject 'repo:javaBin/platform:*', which lets any ref or environment in that repo assume a role that was previously a plan-only role.
  • 🔒 [security] The ci_deploy_ecs inline policies for cupcake, javazoneweb, jzwatch and testteam add ecs:ListTasks (scoped to the javabin-platform cluster), ecs:TagResource (only when CreateAction is RegisterTaskDefinition), and ecs:ListTagsForResource and ecs:DescribeTasks. These are Allow statements. The TagResource and RegisterTaskDefinition grants use Resource '*', so the scoping is loose. Each team gets the same expansion.
  • 🔒 [security] The budget_enforcer policy adds ecs:TagResource alongside ecs:UpdateService. Because the Action change shows as a list growing from one string, this is an Allow grant, not a Deny. The team_provisioner policy adds sso:ListAccountAssignments, and it adds AWS_ACCESS_GROUP=javabin-developer@java.no to the Lambda environment.
  • ✅ [routine] New EventBridge rule javabin-team-provisioner-access-schedule (rate 3 hours) invokes the existing team-provisioner Lambda with sync_access_members, plus the matching target and lambda_permission. This is a scheduled invocation that adds recurring API calls. It is not a billing concern at this scale.
  • ✅ [routine] Lambda code updates for budget-enforcer and team-provisioner (source_code_hash changes) and the team-provisioner environment variable change. No resource is destroyed and no database, network or load balancer is touched. Cost impact is negligible.

@Alexanderamiri
Alexanderamiri merged commit 36efc61 into main Oct 31, 2026
4 checks passed
@Alexanderamiri
Alexanderamiri deleted the fix/alb-priority-validation branch October 31, 2026 14:04
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