ci: use OIDC for ECR deployment - #3029
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the ECR deployment workflow to authenticate to AWS via GitHub OIDC role assumption instead of long-lived AWS access keys, while tightening workflow token permissions.
Changes:
- Add explicit workflow
permissionsfor OIDC (id-token: write) and minimal repo access (contents: read). - Switch
aws-actions/configure-aws-credentialsto assume an IAM role (role-to-assume) instead of using static access keys. - Add account allowlisting/masking and a per-run role session name for safer, more traceable credential use.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| permissions: | ||
| contents: read | ||
| id-token: write |
There was a problem hiding this comment.
actions: write is not required here. BuildKit’s type=gha backend uses the GitHub-provided ACTIONS_RUNTIME_TOKEN, and docker/build-push-action automatically supplies the cache URL and token. We also have the same contents: read + id-token: write configuration running successfully with GHA cache import/export in context7parser. Keeping the current permissions preserves least privilege.
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
| role-to-assume: ${{ secrets.AWS_ECR_ROLE_ARN }} | ||
| aws-region: ${{ secrets.AWS_REGION }} | ||
| allowed-account-ids: ${{ secrets.AWS_ACCOUNT_ID }} |
There was a problem hiding this comment.
allowed-account-ids fails open when this secret is empty, so the guardrail can silently disappear.
In configure-aws-credentials@v6 the input is parsed as getInput('allowed-account-ids').split(','), and the check bails out early when the first element is an empty string:
// src/helpers.ts:169
export function validateAccountId(expectedAccountIds: string[] | undefined, account: string | undefined): void {
if (!expectedAccountIds || expectedAccountIds.length === 0 || expectedAccountIds[0] === '') {
return;
}
...
}An unset, renamed, or typo'd AWS_ACCOUNT_ID therefore turns the account check into a no-op with no warning and no failure — the deploy just proceeds unguarded. The secret does exist today, so this works as intended right now; it's a latent trap for whenever secrets get rotated or renamed.
The earlier revision of this PR had the literal allowed-account-ids: "640168447591", which can't go missing. The account ID isn't really a secret either — it's already embedded in the role ARN and in the ECR registry hostname — so a literal or a repo variable would be a safer home for it than a secret.
Related nit on the next line: mask-aws-account-id: true is redundant when the ID comes from a secret (GitHub already masks it), and its main effect is to render the guardrail's own failure message as does not match any of the expected account IDs: *** — which is exactly the message you want readable when it trips.
Summary
contents: readandid-token: writeRequired configuration
Before running the workflow, create these GitHub repository secrets:
AWS_ECR_ROLE_ARNAWS_ACCOUNT_IDAWS_REGIONECR_REGISTRYECR_REPOSITORYThe IAM role must trust:
and grant ECR push access only to:
After a successful OIDC deployment, only the legacy
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYsecrets can be removed and thegithub-actions-context7-mcpIAM user can be retired.Validation
git diff --check.github/workflows/ecr-deploy.ymlas YAML