· 4 min read

AWS Credentials in GitHub Actions: A Secure OIDC Solution

AWS Credentials in GitHub Actions: A Secure OIDC Solution

Learn how to stop hardcoding AWS credentials in GitHub Actions by implementing a secure OpenID Connect (OIDC) solution using AWS CDK.

Picture this: you're reviewing a security incident report where an attacker gained access to your production AWS environment through compromised GitHub repository secrets. The AWS access keys, sitting there as plain text in your repository settings, provided the golden ticket to your entire infrastructure. Sound familiar? You're not alone.

The Hidden Dangers of Hardcoded Credentials

Every day, thousands of developers store AWS Access Keys and Secret Keys as GitHub repository secrets, unknowingly creating a ticking time bomb in their CI/CD pipelines. These long-lived credentials become persistent attack vectors that can haunt your infrastructure for months or even years.

When you hardcode credentials, you're essentially handing out permanent keys to your AWS kingdom. Unlike the temporary badges you might use at work, these credentials don't expire until you manually rotate them—and let's be honest, how often does that really happen? Meanwhile, they accumulate increasingly broad permissions as teams rush to fix broken deployments, creating a perfect storm of security vulnerabilities.

The operational burden is equally problematic. Manual key rotation becomes an error-prone process that teams frequently postpone or forget entirely. Permission creep sets in as developers add more and more privileges to make deployments work, often without understanding the full scope of access they're granting. And when something goes wrong, tracing which credentials were used when becomes a nightmare that even the most detailed audit logs struggle to illuminate.

Enter OpenID Connect: The Modern Authentication Solution

Fortunately, there's a better way. Instead of storing static credentials, GitHub Actions can securely authenticate with AWS using OpenID Connect (OIDC) tokens. This approach fundamentally changes how your CI/CD pipelines interact with cloud resources.

Here's how it works: when your GitHub Action runs, GitHub generates a unique, short-lived token specifically for that workflow execution. This token contains verifiable information about the repository, branch, and workflow context. AWS then validates this token against a configured OIDC provider and issues temporary credentials that automatically expire when the workflow completes.

The beauty of this system lies in its ephemeral nature. There are no permanent credentials to steal, no keys to rotate, and no secrets to accidentally expose in logs. Each workflow run gets its own isolated authentication context that dies with the workflow.

Building a Production-Ready OIDC Solution

Let me walk you through a real-world implementation that I've developed to solve these authentication challenges. This CDK project creates a secure, scalable OIDC integration that eliminates the need for hardcoded credentials entirely.

The heart of the solution starts with establishing trust between GitHub and AWS. In the main application file, we configure an OpenID Connect provider that serves as the bridge between these two systems:

// src/app.ts
const provider = new OpenIdConnectProvider(this, "github-provider", {
  clientIds: ["sts.amazonaws.com"],
  url: "https://token.actions.githubusercontent.com",
  thumbprints: [
    "6938fd4d98bab03faadb97b34396831e3780aea1",
    "1c58a3a8518e8759bf075b76b750d4f2df264fcd",
  ],
});

Those thumbprints might look like cryptic strings, but they're actually critical security components. GitHub uses cross-signed certificates, which means either of two possible intermediary certificates could be presented during the SSL handshake. By including both thumbprints, we ensure that AWS can validate GitHub's identity regardless of which certificate path is used—a subtle but crucial detail that prevents authentication failures.

The real magic happens in how we create repository-specific access controls. Rather than granting broad permissions to all repositories, the system creates isolated IAM roles for each repository:

// src/constructs/actions-oidc.ts
const principal = new OpenIdConnectPrincipal(provider).withConditions({
  StringLike: {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:sub": `repo:${userName}/${repoName}:*`,
  },
});

This conditional access control is where the security rubber meets the road. The sub (subject) condition ensures that only workflows running from a specific repository can assume the role. Even if someone gains access to your GitHub organization, they can't use credentials intended for one repository to access resources meant for another.

The permission structure itself follows the principle of least privilege, granting only the specific AWS actions needed for CDK deployments:

// src/common.ts
export const cdkPolicyStatements = [
  new PolicyStatement({
    effect: Effect.ALLOW,
    actions: ["sts:AssumeRole", "iam:PassRole"],
    resources: ["arn:aws:iam::*:role/cdk*"],
  }),
  new PolicyStatement({
    effect: Effect.ALLOW,
    actions: ["s3:*"],
    resources: ["arn:aws:s3:::cdk*"],
  }),
  // Additional CDK-specific permissions...
];

Notice how the resources are scoped to CDK-specific patterns. This isn't an accident. It's a deliberate design choice that prevents workflows from accessing resources outside their intended scope. If a workflow tries to access an S3 bucket that doesn't start with "cdk", it will be denied access.

The architecture scales elegantly as your organization grows. Instead of managing individual credential pairs for each repository, you can define repository classes that inherit from a common base:

// src/app.ts
const MANAGED_REPOSITORIES = [ThisOIDCStack, AnotherProjectStack];

MANAGED_REPOSITORIES.map((Repository) => new Repository(this, provider));

Each repository gets its own managed role with tailored permissions, but they all share the same underlying OIDC provider. This reduces operational overhead while maintaining security isolation between projects.

Security Benefits

No Stored Secrets

  • Zero long-lived credentials in GitHub
  • Tokens are ephemeral and automatically expire
  • No credential rotation required

Principle of Least Privilege

  • Repository-specific access controls
  • Time-limited token validity
  • Conditional access based on workflow context

Audit Trail

  • Complete CloudTrail logging of all actions
  • Token-based attribution to specific workflows
  • No shared credential usage

Compliance Ready

  • Meets SOC 2 and ISO 27001 requirements
  • Supports automated compliance checks
  • Eliminates credential storage risks

Implementation Steps

  1. Deploy the CDK Stack

    npm install
    npx cdk deploy
    
  2. Configure GitHub Workflow

    name: deploy
    run-name: Deploy 🚀
    on:
      push:
        branches:
          - main
    permissions:
      id-token: write
      contents: read
    jobs:
      deploy:
        runs-on: ubuntu-latest
        env:
          CDK_DEFAULT_ACCOUNT: ${{vars.CDK_DEFAULT_ACCOUNT}}
          CDK_DEFAULT_REGION: ${{vars.CDK_DEFAULT_REGION}}
        steps:
          - uses: actions/checkout@v4
          - uses: pnpm/action-setup@v4
            with:
              run_install: false
          - uses: actions/setup-node@v4
            with:
              node-version: 20
              cache: 'pnpm'
          - name: Install
            run: pnpm install
          - name: Assume role using OIDC
            uses: aws-actions/configure-aws-credentials@master
            with:
              aws-region: ${{vars.CDK_DEFAULT_REGION}}
              role-to-assume: arn:aws:iam::${{vars.CDK_DEFAULT_ACCOUNT}}:role/${{vars.APP_NAME}}-github-ci-role
          - name: Deploy
            run: npx aws-cdk deploy --verbose --require-approval never
    

Conclusion

OIDC integration with GitHub Actions represents a significant security improvement over traditional credential storage. By eliminating long-lived credentials, implementing granular access controls, and providing complete audit trails, this solution addresses the fundamental security challenges of CI/CD authentication.

The CDK implementation presented here provides a production-ready foundation that can be customized for your specific requirements while maintaining security best practices.

Key Takeaways:

  • OIDC tokens are more secure than stored credentials
  • Repository-specific access controls prevent lateral movement
  • Automated credential management reduces operational overhead
  • Comprehensive testing ensures reliable security controls

Ready to modernize your CI/CD security? Start by deploying this CDK solution and gradually migrating your workflows to use OIDC authentication.


This implementation is open source and available at github-oidc-cdk. Contributions and feedback are welcome!