Skip to main content

04. OIDC Authentication & Cryptographic Artifact Signing

Securing the boundary between CI/CD pipelines and production infrastructure requires two core architectural controls: eliminating long-lived static cloud credentials and cryptographically signing all compiled software artifacts.

This chapter details OpenID Connect (OIDC) federation and container image signing using Sigstore/Cosign.


1. Eliminating Static Credentials with OIDC Federation

Storing long-lived cloud API keys (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GCP service account JSON keys) in GitHub Secrets creates a critical risk: if a pipeline runner is compromised, attackers extract keys that grant persistent access to enterprise cloud environments.

OpenID Connect (OIDC) allows CI/CD runners to request short-lived, dynamically minted OAuth 2.0 / JWT tokens directly from the cloud provider (AWS, GCP, Azure).


AWS OIDC Integration Setup

Step 1: AWS IAM Role Trust Policy (github-oidc-policy.json)

Configure an AWS IAM Role trust policy that restricts assumption to a specific GitHub organization, repository, and branch:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:techcorp/api-service:ref:refs/heads/main"
}
}
}
]
}

Step 2: GitHub Actions Workflow (.github/workflows/deploy.yml)

name: Deploy to Production AWS
on:
push:
branches: [ main ]

# CRITICAL: Grant permission to request OIDC JWT ID token
permissions:
id-token: write
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.7

- name: Authenticate to AWS via OIDC
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole
aws-region: us-east-1
# NO STATIC KEYS STORED IN SECRETS!

- name: Deploy to Cloud
run: |
aws ecr get-login-password --region us-east-1 | docker login ...

2. Cryptographic Container Image Signing (Sigstore / Cosign)

Mutable container tags (:latest, :v1.0.0) can be overwritten by unauthorized registries or man-in-the-middle attackers. Container image signing guarantees that Kubernetes clusters run ONLY container images built and signed by authorized CI/CD workflows.

Sigstore provides an open-source standard for artifact signing backed by Linux Foundation and OpenSSF.

┌─────────────────────────────────────────────────────────────────────────────┐
│ SIGSTORE ECOSYSTEM ARCHITECTURE │
├──────────────┬──────────────────────────────────────────────────────────────┤
│ Cosign │ CLI tool for signing and verifying OCI container images. │
├──────────────┼──────────────────────────────────────────────────────────────┤
│ Fulcio │ Ephemeral Certificate Authority issuing short-lived X.509 │
│ │ certificates tied to OIDC identity (e.g. GitHub Workflow). │
├──────────────┼──────────────────────────────────────────────────────────────┤
│ Rekor │ Immutable, append-only transparency log auditing all signed │
│ │ certificates and image digests globally. │
└──────────────┴──────────────────────────────────────────────────────────────┘

Cosign Keyless Signing Workflow in GitHub Actions

Keyless signing uses OIDC identity tokens to obtain temporary signing certificates from Fulcio, publishing public audit records to Rekor without managing private key files!

# .github/workflows/build_and_sign.yml
name: Build, Sign, and Push Container Image
on:
push:
branches: [ main ]

permissions:
id-token: write # Required for Sigstore keyless OIDC authentication
contents: read
packages: write

jobs:
build-and-sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.7

- name: Install Cosign CLI
uses: sigstore/cosign-installer@59acb6260d9c0d3bbf966565078588ef73c7165c # v3.5.0

- name: Log in to GitHub Container Registry (GHCR)
uses: docker/login-action@0d4c9c5ea7693da7b069a1841bce44c3a93d6d3d # v3.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and Push Docker Image
id: build-image
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
push: true
tags: ghcr.io/techcorp/api-service:${{ github.sha }}

- name: Sign Container Image (Keyless Sigstore)
env:
IMAGE_DIGEST: ghcr.io/techcorp/api-service@${{ steps.build-image.outputs.digest }}
run: |
# Cosign authenticates via GitHub OIDC ID Token automatically
cosign sign --yes "$IMAGE_DIGEST"

3. Kubernetes Admission Control Enforcement (Kyverno)

Generating signatures in CI/CD provides security only if the production Kubernetes cluster enforces signature verification prior to pod deployment.

Deploy a Kyverno ClusterPolicy to intercept pod creation requests and reject unsigned images:

# kubernetes/kyverno-verify-images.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-container-signature
annotations:
policies.kyverno.io/title: Verify Signed Container Images
policies.kyverno.io/subject: Pod
spec:
validationFailureAction: Enforce # Block non-compliant deployments
background: false
rules:
- name: verify-signature-sigstore
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/techcorp/*"
attestors:
- entries:
- keyless:
issuer: "https://token.actions.githubusercontent.com"
subject: "https://github.com/techcorp/api-service/.github/workflows/build_and_sign.yml@refs/heads/main"
rekor:
url: "https://rekor.sigstore.dev"

4. Generating & Verifying SLSA Provenance

In addition to signing container images, attach cryptographically verifiable SLSA Provenance attestations using the slsa-github-generator:

# Step inside build workflow calling SLSA Generator reusable workflow
jobs:
build:
runs-on: ubuntu-latest
outputs:
digests: ${{ steps.hash.outputs.digests }}
steps:
- id: hash
run: echo "digests=`$(sha256sum binary-artifact | base64 -w0)" >> "$`GITHUB_OUTPUT"

provenance:
needs: [build]
permissions:
id-token: write
contents: read
actions: read
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
base64-subjects: "${{ needs.build.outputs.digests }}"

Next Chapter: 05. Pipeline Security Gates & Governance →

Share this guide