The Problem with Static Credentials
The naive approach to cloud authentication in CI/CD is to generate a long-lived AWS Access Key, paste it into GitHub Secrets, and reference it in your workflow. This works, but it creates a persistent credential that can be leaked, rotated incorrectly, or forgotten. If your workflow YAML is ever compromised, the attacker has a key that works indefinitely.
GitHub Secrets are the baseline. They are encrypted at rest, masked in logs, and scoped to a repository or organization. They are appropriate for API tokens, database passwords, and other credentials that cannot use OIDC — referenced in a workflow step's env: block via the secrets.* context, never hardcoded.
Secrets scoping hierarchy:
* Repository secrets: Available only to workflows in that specific repository.
* Environment secrets: Available only to jobs targeting a specific named environment (e.g., production). This is the right place for production credentials.
* Organization secrets: Available to all (or selected) repositories in an org. Good for shared infrastructure credentials.
OpenID Connect (OIDC): The Better Way for Cloud Auth
OIDC eliminates long-lived cloud credentials entirely. Instead of storing an AWS Access Key, you configure AWS to trust GitHub's identity provider. When a workflow runs, GitHub mints a short-lived JWT token that proves "this is a workflow in my-org/my-repo, running on branch main, triggered by a push." AWS verifies this token and issues temporary credentials that expire when the job ends.
The workflow itself needs an id-token: write permission to request the JWT, and a step that exchanges it for temporary AWS credentials by assuming a specific IAM role — no access key or secret key stored anywhere. Subsequent steps then use the AWS CLI normally, transparently authenticated by those temporary credentials.
The AWS IAM role's trust policy restricts which GitHub workflows can assume it, via a condition matching the token's sub claim against an exact repository-and-branch pattern. This means only workflows running on the main branch of my-org/my-repo can assume this role. A PR from a fork cannot. A workflow on a feature branch cannot. The blast radius of a compromise is minimal because credentials expire within minutes.