Conversation
…vironment
The schedule-runner Lambda signs in as a portal admin to delegate
`pipeline:{name}` schedules to the backend API (workflow scripts need Node,
which the Lambda has not got). It reads the credential from Secrets Manager,
and `portal-admin` was the one platform secret whose name was not suffix-aware:
`llm-gateway-key`, `service-entry`, `mcp-hub` and the team-auth secrets all
carry `name_suffix`, this one did not, and nothing set
`PLATFORM_PORTAL_ADMIN_SECRET` either, so `config.py`'s unsuffixed default won
everywhere.
That is wrong because the credential is a user in *one* stack's Cognito pool.
Two stacks in an account cannot share it — which is exactly what `name_suffix`
and `SUFFIX` exist to support, and what terraform/README and the CLI runbook
both recommend for coexisting deployments. The symptom differed per path:
* Terraform with a suffix: no AccessDenied, because grant and default agreed —
but both stacks read one secret, and only one stack's password matches its
own pool, so the other's pipeline schedules fail on NotAuthorizedException.
* deploy-cli with a SUFFIX: the runner role was granted
`agent-platform${SUFFIX}/portal-admin*` while the code asked for the
unsuffixed name, so every pipeline schedule died on AccessDenied.
So: one `local.portal_admin_secret` in the portal module now names the secret,
the IAM grant and the Lambda's `PLATFORM_PORTAL_ADMIN_SECRET` from a single
place, and `terraform output -raw portal_admin_secret_name` tells the operator
what to create. The legacy CDK stacks have no suffix concept at all, so their
unsuffixed name stays correct and is left alone.
Auditing the CLI port for the same fix turned up a larger defect in it: the
schedule-runner Lambda was created with **no environment at all**, and
`PLATFORM_PORTAL_API_URL` was set on the backend instead. Both are inverted
relative to Terraform, and both matter:
* An empty environment leaves the runner on `config.py` defaults, `dynamo_table
= "agent-platform"` above all — so in a suffixed deployment every scheduled
run reads and writes the wrong table. Present, correct-looking, wrong data.
* `PLATFORM_PORTAL_API_URL` is what selects the delegation path in
`schedule_service._run_pipeline`. On the backend it made the backend delegate
pipelines to itself and then fail, since only the runner role carries the
portal-admin grant; on the runner, whose only path this is, it was missing.
Phase 6 now fills the runner's environment (it cannot happen in phase 5 —
the CloudFront domain does not exist yet) and the variable is off the backend.
`tests/verify.sh` gained three L1 assertions for it: the runner's table matches
the deployment, its admin-secret name is suffix-aware, and it points at the
portal API. That is the shape of check the suite exists for — every resource was
present and healthy while this was broken.
Docs: deployment.md and the CLI runbook now say what the secret is for, that
its name carries the suffix, and how to create it from the new output;
permissions.md's `PortalAdminSecret` row names the suffixed resource and notes
that the backend task role deliberately has no such grant.
Not verified against a live deployment: no AWS credentials or Terraform binary
in this environment. Shell syntax is checked (`bash -n`) and the Terraform
edits are formatted to be `terraform fmt`-stable, but `terraform validate`,
`plan` and an actual suffixed apply have not been run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The schedule-runner Lambda signs in as a portal admin to delegate
pipeline:{name}schedules to the backend API.portal-adminwas the one platform secret whose name was not suffix-aware, and nothing setPLATFORM_PORTAL_ADMIN_SECRET, soconfig.py's unsuffixed default won everywhere. The credential is a user in one stack's Cognito pool, so two stacks in an account cannot share it — which is exactly whatname_suffix/SUFFIXexist for (terraform/README and the CLI runbook both recommend it for coexisting deployments).NotAuthorizedException.AccessDeniedon every pipeline schedule.Changes
terraform/modules/portal: onelocal.portal_admin_secretdrives the IAM grant, the Lambda env, and a newportal_admin_secret_nameoutput so the operator does not type the name.PLATFORM_PORTAL_API_URLwas set on the backend instead of the runner, inverting the delegation path (schedule_service._run_pipeline). Phase 6 now fills the runner env; the variable is off the backend.deploy-cli/tests/verify.sh: three new L1 assertions (runner table matches the deployment, admin-secret name is suffix-aware, runner points at the portal API).Legacy CDK has no suffix concept, so its unsuffixed name stays correct and is untouched.
Not verified live
No AWS credentials or Terraform binary in the authoring environment.
bash -npasses and the HCL isterraform fmt-shaped, butterraform validate/planand a real suffixed apply have not been run. Pleaseterraform planbefore merging.Scanner impact
Static check against the README suppression list: no new findings expected. The one gitleaks-shaped line (
PLATFORM_PORTAL_ADMIN_SECRET = local.portal_admin_secret) has the same shape as the already-cleanPLATFORM_SERVICE_ENTRY_SECRET_NAME = aws_secretsmanager_secret.service_entry.name.🤖 Generated with Claude Code