ci(secrets->kms): migrate CP_ADMIN_API_TOKEN to Infisical KMS (wave-3 molecule-ai/molecule-core) #3274
Reference in New Issue
Block a user
Delete Branch "ci/migrate-cp-admin-api-token-infisical"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Migrate
CP_ADMIN_API_TOKENfrom a duplicated Gitea Actions secret to the Infisical KMS SSOT across the 7molecule-ai/molecule-coreworkflows that consume it. Wave-3 of the Gitea-secrets-to-KMS migration; one secret per PR (established wave pattern).Recipe (mirrors bench-provision-time.yml + boot-to-registration-e2e.yml)
In the same job as each consumer (so the export spans the steps via
$GITHUB_ENV), aFetch CP_ADMIN_API_TOKEN from Infisical KMSstep:POST https://key.moleculesai.app/api/v1/auth/universal-auth/loginwith the CI machine identity (INFISICAL_CI_CLIENT_ID/INFISICAL_CI_CLIENT_SECRET, projectINFISICAL_CI_PROJECT_ID) -> accessToken.GET /api/v3/secrets/raw/CP_ADMIN_API_TOKEN?workspaceId=...&environment=prod&secretPath=%2Fshared%2Fcontrolplane-admin->.secret.secretValue.echo "::add-mask::$VAL", then assert NON-EMPTY and FAIL LOUD ([ -z "$VAL" ]->::error::+exit 1), thenecho "CP_ADMIN_API_TOKEN=$VAL" >> "$GITHUB_ENV".secrets.CP_ADMIN_API_TOKENuse site now consumes the env var.template-delivery-e2e.ymlkeeps itsMOLECULE_ADMIN_TOKENalias (exported under that name).The 3
INFISICAL_CI_*bootstrap secrets STAY in Gitea -- they are the identity that fetches everything else.Trusted-ref gate (this repo is PUBLIC)
The KMS step consumes the
INFISICAL_CI_*machine-identity secrets, so it is gated to trusted refs so an untrusted fork cannot trigger it and harvest those creds. Per-workflowif::template-delivery-e2e.ymldeliverydelivery=='true'AND (workflow_dispatchORpush+mainORpull_requestwithhead.repo.fork == false)staging-verify.ymlpromote-to-latestworkflow_dispatchORpush+stagingredeploy-tenants-on-main.ymlredeployworkflow_dispatchsweep-cf-tunnels.ymlsweepscheduleORworkflow_dispatchsweep-cf-orphans.ymlsweepscheduleORworkflow_dispatchsweep-aws-secrets.ymlsweepscheduleORworkflow_dispatchpublish-workspace-server-image.ymldeploy-productionworkflow_dispatchORpush+mainFor
template-delivery-e2ea same-repo (non-fork) delivery PR runs the fetch + e2e normally; a fork delivery PR skips the fetch and then hits the existing "Verify required secret present" hard-fail (loud, not silent) -- fail-closed, and equivalent to today since forks cannot readsecrets.*in this Gitea setup anyway.No
on:trigger changes in any file (wave-1 lesson: a migration PR must not alter triggers).Probe-200 evidence
GET /v3/secrets/raw/CP_ADMIN_API_TOKEN?...&secretPath=%2Fshared%2Fcontrolplane-adminprobed 200 before shipping:environment=prod-> 200, len 64environment=staging-> 200, len 64 (byte-identical value across envs)Path pinned to
/shared/controlplane-admin(the assessment's/shared/cp-adminis 404 -- probe-first caught it).Validation
yaml.safe_loadpasses on all 7 changed files;on:blocks and jobs intact.lint-workflow-yaml.py --workflow-dir .gitea/workflows-> 0 warnings, no Gitea-1.22.6-hostile shapes.Gitea CP_ADMIN_API_TOKEN secret RETAINED pending validate-before-delete
The Gitea
CP_ADMIN_API_TOKENsecret is NOT deleted by this PR. Per the wave gate, the PM/operator re-runs a real workflow on this PR first to prove the KMS fetch is live AND consumed; only then is the Gitea secret retired (repo scope; org scope only after ALL consumers migrate).Merge discipline
No self-merge, no self-approval. Requires the standard pool review bar.
REQUEST_CHANGES on head
857eb6caad.The fork-gate fix in template-delivery-e2e.yml is correct: the positive allowlist github.event.pull_request.head.repo.full_name == github.repository fails closed for fork/cross-repo/null head cases. However, all seven core KMS fetches still use the old extractor shape
print(json.load(sys.stdin)["secret"]["secretValue"]). If Infisical returns JSON null for secretValue, Python prints literalNone, which passes[ -z "$CP_ADMIN_API_TOKEN" ]and can export/useCP_ADMIN_API_TOKEN=None. This is the same fail-open class we just blocked on #971. Please change the extractor to emit empty/non-zero for null or non-string values before export, then the existing non-empty gate will fail closed.REQUEST_CHANGES on head
857eb6caad.Correctness/security finding: the fork gate fix in template-delivery-e2e.yml is directionally correct — the executable condition now uses the fail-closed same-repo allowlist github.event.pull_request.head.repo.full_name == github.repository, so fork/null head.repo cases do not receive INFISICAL_CI_* secrets. However the KMS read helper introduced/kept in this PR still uses the old extractor form:
python3 -c 'import sys,json;print(json.load(sys.stdin)["secret"]["secretValue"])'
If Infisical returns JSON null for secretValue, Python prints the literal string None. That is non-empty, so the later [ -z "$CP_ADMIN_API_TOKEN" ] guard does not fail closed and the workflow can export/mask/use a bogus token. This is the same null-masking class fixed on #971 by emitting an empty string unless secretValue is a string.
Other axes: /shared/controlplane-admin path, add-mask-before-export, no secret deletion, and the new same-repo PR allowlist are otherwise in the right shape; full-paginated statuses show CI / all-required green, with review-gate/template pending/noise not changing the code finding. Please update the molecule-core KMS extractor(s) in this PR to the #971-safe pattern so null/non-string broken reads fail the non-empty check.
Reviewed head
cecdbd8981.The prior blockers are resolved. The template-delivery KMS step now uses the positive same-repo allowlist (
head.repo.full_name == github.repository), so untrusted fork PRs do not get the Infisical credential path. All seven CP_ADMIN_API_TOKEN KMS-fetch extractors now use the null-safe JSON idiom that emits an empty string unlesssecretValueis a string; that makes JSON null/non-string reads fail closed at the existing[ -z ]guard instead of exporting literalNone. The oldprint(json.load(...)["secret"]["secretValue"])pattern is gone from the PR diff.5-axis review: correctness matches the wave-3 migration intent; robustness/fail-closed behavior is improved for broken Infisical reads and fork gating; security posture is clean with add-mask/export ordering retained and no secret deletion; performance impact is only the existing one-time workflow fetch; readability is acceptable and the comment documents why the fail-closed allowlist is used. Full-paginated statuses show CI/all-required and Secret scan success on this head, with remaining failures/pending contexts outside the code-review verdict path.
Verdict: APPROVED.
APPROVED on head
cecdbd8981.Scope reviewed: wave-3 CP_ADMIN_API_TOKEN -> Infisical KMS migration across molecule-core workflows, including the template-delivery fork-gate fix.
My RC 14262 is resolved. I verified all seven KMS fetch helpers now use the #971-safe extractor form that emits an empty string unless secretValue is a JSON string, so json-null/non-string broken reads hit the following [ -z ] fail-closed guard instead of becoming literal None. The old print(json.load(...)["secret"]["secretValue"]) pattern count is zero.
5-axis review: correctness/robustness/security/performance/readability look clean. The seven workflows read /shared/controlplane-admin, call ::add-mask:: before exporting, assert non-empty before writing GITHUB_ENV, and do not delete the existing CP_ADMIN_API_TOKEN secret. The template-delivery trusted-ref gate remains fail-closed for fork/null PRs via github.event.pull_request.head.repo.full_name == github.repository; the remaining head.repo.fork == false text is explanatory comment only. Full-paginated statuses show CI / all-required and Secret scan green; reserved-path/review-gate reds are the expected pre-approval checks/noise.