Appearance
Services How We Work Portfolio Why Us About Us FAQ Blog Contact & Consultation Start a Project
DevOps & Security

Zero-Trust CI/CD Pipelines: Enforcing SLSA Level 3, Ephemeral Kube-Runners, and Keyless Sigstore Attestation

Software supply chain breaches have evolved from theoretical academic vectors into devastating enterprise realities. The high-profile compromises of SolarWinds, Codecov, and the critical near-miss of the xz backdoor proved that an attacker who compromises an organization’s continuous integration and continuous delivery (CI/CD) pipeline effectively controls its entire runtime estate. Yet, inside many enterprise engineering teams, CI/CD runners remain the soft underbelly of the cloud topology: long-lived virtual machines with cached credentials, ambient static AWS access tokens stored in plaintext environment secrets, and root-privileged Docker-in-Docker (DinD) engines capable of trivial container breakouts.

Achieving true zero-trust in modern CI/CD requires treating every build step as untrusted, every runner compute node as ephemeral and disposable, and every build artifact as counterfeit until proven otherwise via immutable cryptographic attestation. In this deep dive, we present an end-to-end architectural blueprint implemented for high-throughput enterprise environments: orchestrating isolated, single-use GitHub Actions runners via Kubernetes, eliminating long-lived credentials using OpenID Connect (OIDC) federation, generating Supply-chain Levels for Software Artifacts (SLSA) Level 3 build provenance with Sigstore Cosign, and enforcing cryptographically validated deployments via Kyverno policy-as-code.

The Threat Vector Matrix in High-Velocity Pipelines

Before applying controls, architects must model where modern CI/CD pipelines fail under adversarial pressure. Vulnerabilities rarely stem from the build code itself; they emerge from the ambient trust granted to the CI execution environment.

  • Runner Persistence and Workspace Poisoning: Persistent or self-hosted runners that re-use directories across job executions allow an unprivileged pull request execution (such as a compromised unit test) to alter build scripts, leave backdoor binaries in shared caches, or read residual secrets left by preceding production deployment jobs.
  • Ambient Credential Exfiltration: Storing static AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY strings within repository secrets exposes them to arbitrary code execution during workflow dispatch, fork execution vulnerabilities, or malicious third-party GitHub Actions actions.
  • Privileged Container Runtimes: Traditional Docker-in-Docker patterns require running CI daemon containers in --privileged mode. Any remote code execution inside the build container gives the attacker access to the host’s Linux kernel, cgroups, devices, and adjacent Kubernetes workloads.
  • Unattested Artifact Tampering: A standard CI pipeline builds an image, tags it with a commit SHA, and pushes it to a container registry. Without cryptographic provenance tying the digest strictly to an authenticated, tamper-proof build pipeline, an attacker with compromised registry push credentials can overwrite or inject a malicious image digest undetected.

The solution requires decoupling trust from network location, removing permanent credentials, and establishing strict cryptographic chain-of-custody from source commit to production pod admission.

Phase 1: Zero-State Ephemeral Compute with Actions Runner Controller (ARC)

To eliminate runner persistence and privilege escalation, runners must be completely ephemeral (destroyed after a single job execution) and run unprivileged without Docker daemon socket mounts. We achieve this by deploying GitHub’s modern Actions Runner Controller (ARC) based on autoscaling-runner-set on an isolated Amazon EKS or bare-metal Kubernetes cluster.

Instead of relying on Docker-in-Docker, builds are executed using rootless container tooling such as Kaniko or Google’s ko. Below is a production Kubernetes manifest configuring an unprivileged, ephemeral AutoscalingRunnerSet running on dedicated nodes tainted for CI execution:

apiVersion: actions.github.com/v1alpha1
kind: AutoscalingRunnerSet
metadata:
  name: hardened-ephemeral-runner
  namespace: arc-runners
spec:
  githubConfigUrl: "https://github.com/studymorph/enterprise-core"
  githubConfigSecret: arc-github-app-secret
  minRunners: 2
  maxRunners: 30
  template:
    spec:
      serviceAccountName: arc-runner-sa
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        seccompProfile:
          type: RuntimeDefault
      tolerations:
        - key: "dedicated"
          operator: "Equal"
          value: "ci-runners"
          effect: "NoSchedule"
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: "workload"
                    operator: "In"
                    values: ["isolated-ci"]
      containers:
        - name: runner
          image: ghcr.io/actions/actions-runner:latest
          command: ["/home/runner/run.sh"]
          resources:
            limits:
              cpu: "4"
              memory: "8Gi"
              ephemeral-storage: "20Gi"
            requests:
              cpu: "2"
              memory: "4Gi"
              ephemeral-storage: "10Gi"
          securityContext:
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
          volumeMounts:
            - name: work
              mountPath: /home/runner/_work
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: work
          emptyDir: {}
        - name: tmp
          emptyDir: {}

With this configuration, every workflow triggers an on-demand pod running as an unprivileged UID (10001). The pod filesystem is read-only, privileged escalation is blocked at the Linux kernel boundary, and once the job finishes, the pod is immediately deleted and reclaimed, preventing cross-build persistence and forensic evasion.

Phase 2: Ambient Credential Elimination via OpenID Connect (OIDC)

Static access keys must be explicitly outlawed in enterprise policy. By establishing an OpenID Connect (OIDC) trust relationship between the GitHub Actions token service and AWS Security Token Service (STS), ephemeral runners exchange a short-lived JSON Web Token (JWT) for temporary, scope-restricted IAM session credentials.

The OIDC Trust Policy: Precise Branch and Workflow Pinning

A frequent anti-pattern is establishing OIDC trust with wildcards across an entire GitHub organization. An enterprise trust policy must bind permissions strictly to the target repository, deployment environment, and reference branch. Below is the AWS IAM Role trust policy requiring an exact match on repository identity and production deployment context:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::112233445566:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:iss": "https://token.actions.githubusercontent.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:studymorph/enterprise-core:environment:production"
        }
      }
    }
  ]
}

This policy guarantees that even if an attacker creates a branch or a pull request within the same organization, AWS STS will decline to issue credentials unless the job executes within the protected production environment gate (enforcing mandatory peer reviews and approvals).

Phase 3: Keyless Cryptographic Signing and SLSA Provenance with Sigstore

Traditional public-key cryptography in CI/CD suffers from catastrophic key management problems: developers generate private GPG or RSA keys, store them in repository secrets, never rotate them, and fail to revoke them when engineers leave the company. If the signing key leaks, an adversary can sign malicious artifacts indefinitely.

Sigstore modernizes this paradigm through Keyless Signing using three core primitives:

  1. Fulcio: A lightweight, ephemeral Public Key Infrastructure (PKI) Certificate Authority that issues short-lived X.509 certificates (valid for roughly 10 minutes) bound to an authenticated OIDC identity (e.g., the GitHub Actions workflow).
  2. Cosign: The command-line utility used to generate ephemeral key pairs, request certificates from Fulcio, sign the artifact digest, and store signatures in the container registry.
  3. Rekor: An immutable, append-only cryptographic transparency ledger that logs the signature, certificate, and timestamp. Even if the short-lived certificate expires, Rekor provides cryptographic proof that the signature was generated during the certificate’s validity window.

Production Workflow: Rootless Build, Syft SBOM, and Cosign SLSA Attestation

Below is a production-grade GitHub Actions workflow demonstrating how to compile a container image rootlessly using Kaniko, extract its Software Bill of Materials (SBOM) using Syft, sign the digest keylessly, and attach an in-toto SLSA provenance attestation:

name: Secure Build & Attestation Pipeline

on:
  push:
    tags:
      - 'v*.*.*'

permissions:
  id-token: write # Required for OIDC federation with AWS and Sigstore Fulcio
  contents: read
  packages: write

jobs:
  build-and-sign:
    runs-on: [self-hosted, hardened-ephemeral-runner]
    environment: production
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Configure AWS Credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::112233445566:role/github-actions-prod-ecr-publisher
          aws-region: us-east-1
          audience: sts.amazonaws.com

      - name: Log in to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Install Cosign and Syft
        uses: sigstore/[email protected]

      - name: Setup Syft
        uses: anchore/sbom-action/[email protected]

      - name: Execute Rootless Image Build (Kaniko)
        run: |
          docker run --rm 
            -v $(pwd):/workspace 
            gcr.io/kaniko-project/executor:latest 
            --dockerfile=Dockerfile 
            --context=dir:///workspace 
            --destination=${{ steps.login-ecr.outputs.registry }}/enterprise-core:${{ github.ref_name }} 
            --digest-file=/workspace/image-digest.txt

      - name: Capture Image Digest
        id: artifact
        run: |
          DIGEST=$(cat image-digest.txt)
          IMAGE_URI="${{ steps.login-ecr.outputs.registry }}/enterprise-core@${DIGEST}"
          echo "image_uri=${IMAGE_URI}" >> $GITHUB_OUTPUT
          echo "digest=${DIGEST}" >> $GITHUB_OUTPUT

      - name: Generate Software Bill of Materials (SBOM)
        run: |
          syft ${{ steps.artifact.outputs.image_uri }} -o spdx-json > sbom.spdx.json

      - name: Keyless Signing and SBOM Attestation with Cosign
        env:
          COSIGN_EXPERIMENTAL: "1"
        run: |
          # Sign the container image digest keylessly using GitHub OIDC identity
          cosign sign --yes ${{ steps.artifact.outputs.image_uri }}

          # Attach the generated SBOM as an in-toto predicate
          cosign attest --yes 
            --type spdxjson 
            --predicate sbom.spdx.json 
            ${{ steps.artifact.outputs.image_uri }}

          # Attest SLSA build provenance
          cosign attest --yes 
            --type slsaprovenance 
            --predicate <(echo '{"builder":{"id":"https://github.com/studymorph/actions/runner"},"buildType":"https://actions.github.com/buildtypes/v1"}') 
            ${{ steps.artifact.outputs.image_uri }}

Security Principle: Notice that signing is executed strictly against the immutable content addressable digest (sha256:...) rather than a mutable container tag. Tags can be changed or hijacked in registry storage; SHA-256 digests cannot.

Phase 4: Admission-Time Enforcement with Kyverno Policy-as-Code

Cryptographic attestations are completely useless if production clusters happily run unverified images. Security must fail closed at deployment time. We implement an admission-control boundary in Kubernetes using Kyverno.

The Kyverno ClusterPolicy intercepts every pod admission request, verifies that the image digest has been signed keylessly by Sigstore, inspects the Rekor transparency log, and validates that the OIDC certificate subject matches our exact repository and workflow identity.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: enforce-trusted-supply-chain
  annotations:
    policies.kyverno.io/title: Enforce Signed Images & Verified Attestations
    policies.kyverno.io/category: Software Supply Chain Security
    policies.kyverno.io/severity: high
    policies.kyverno.io/subject: Pod
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-sigstore-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - production
                - payment-gateway
      verifyImages:
        - imageReferences:
            - "112233445566.dkr.ecr.us-east-1.amazonaws.com/enterprise-core:*"
          attestors:
            - entries:
                - keyless:
                    issuer: "https://token.actions.githubusercontent.com"
                    subject: "https://github.com/studymorph/enterprise-core/.github/workflows/secure-build.yml@refs/tags/v*"
                    rekor:
                      url: "https://rekor.sigstore.dev"
          attestations:
            - predicateType: "https://spdx.dev/Document"
              conditions:
                - all:
                    - key: "{{ element.name }}"
                      operator: Equals
                      value: "enterprise-core"

If a developer tries to apply a standard deployment, or if an attacker compromises cluster credentials and attempts to deploy an image built outside of this specific workflow, the Kubernetes API server rejects the request with an HTTP 403 Forbidden:

Error from server: admission webhook "validate.kyverno.svc" denied the request:
policy Pod/production/core-service-7f6c49877-k8qwd fails verification:
image 112233445566.dkr.ecr.us-east-1.amazonaws.com/enterprise-core@sha256:d8c4...
failed signature verification: no valid signatures found matching the specified issuer and subject.

Operational Performance and Architecture Trade-offs

Transitioning to rootless builds and strict attestation introduces structural trade-offs that systems architects must anticipate and optimize:

1. Build Performance without the Docker Daemon

Eliminating Docker-in-Docker stops container breakout vulnerabilities, but tools like Kaniko lack a shared local layer cache across ephemeral pods. Without mitigation, build times can increase by 300% to 500%.

Resolution: Implement registry-based caching using Kaniko’s --cache=true and --cache-repo flags, pointing to a dedicated Amazon ECR cache repository. Kaniko checks the remote registry for cached intermediate layers before recompiling, bringing build times down to parity with persistent Docker daemon environments.

2. Network Egress Hardening on CI Runner Pods

A compromised dependency (such as a poisoned npm or Composer package) executes malicious lifecycle scripts during npm install or composer install. If the runner pod has unrestricted outbound internet access, the script can exfiltrate source code, pull down secondary payloads, or establish a reverse shell.

Resolution: Apply strict Kubernetes NetworkPolicy resources or Cilium eBPF egress policies to CI runner namespaces. Allow egress exclusively to:

  • The GitHub runner orchestration endpoints.
  • The package registry mirrors (via internal artifactory or proxy).
  • The cloud provider STS and ECR endpoints.
  • The Sigstore Fulcio and Rekor public transparency infrastructures.

Drop all other egress traffic by default. If an open-source dependency attempts to phone home to an unknown command-and-control IP, the connection is instantly reset at the Linux networking layer.

3. High-Availability Considerations for Sigstore

Relying on public Sigstore instances (rekor.sigstore.dev and fulcio.sigstore.dev) introduces an external dependency into your production deployment path. If Rekor experiences a degraded state, cluster admission controllers might block legitimate pod restarts during a horizontal scale event.

Resolution: Set Kyverno’s failurePolicy: Fail for deployment operations, but decouple pod restarts by verifying signatures during deployment creation rather than node-level pod rescheduling. For regulated banking or defense environments, host a private Sigstore stack (Enterprise Rekor, Fulcio, and Trillian) entirely within your private VPC boundary.

The Implementation Roadmap for Enterprise Engineering

Securing the continuous delivery machinery is not accomplished overnight. Engineering leaders should execute this transformation through a staged maturity model:

  1. Week 1-2 (Credential Eradication): Audit all repository secrets. Replace every static cloud credential with AWS/GCP OIDC federation. Revoke the old IAM keys and enforce branch protections on deployment roles.
  2. Week 3-4 (Compute Hardening): Migrate off shared virtual machines. Deploy the Actions Runner Controller on a locked-down, multi-tenant Kubernetes cluster. Enforce non-root execution contexts and disable privileged containers.
  3. Week 5-6 (Keyless Signing Pipeline): Integrate Cosign and Syft into baseline continuous integration templates. Generate SBOMs and push signatures to the registry alongside images. Run in audit mode to ensure all production artifacts are correctly signed without blocking releases.
  4. Week 7-8 (Admission Enforcement): Deploy Kyverno or Open Policy Agent (OPA) Gatekeeper in production clusters. Transition image verification policies from Audit to Enforce. Verify alerting pipelines and establish playbooks for emergency break-glass procedures.

Conclusion: The Zero-Trust Imperative

Security architectures that treat CI/CD systems as protected perimeter zones are obsolete. The modern threat landscape demands that we verify every stage of the software delivery process cryptographically and operationally. By combining ephemeral, unprivileged Kubernetes compute, OIDC credential federation, Sigstore keyless provenance, and Kubernetes admission-time enforcement, enterprise systems architects build an immutable software supply chain. Attackers who breach upstream dependencies or capture developer credentials find themselves contained in sandboxed environments, unable to push rogue binaries into clusters that refuse to run anything less than fully attested code.

Tags: #Cloud Security #DevOps #Kubernetes #Sigstore #Supply Chain Security

Tubagus Putra Kencana

Founder & Principal Engineer at Study Morph. Specializing in modern web architecture, cloud systems, and scalable digital platforms.

Interested in Building a Modern Digital System?

Study Morph helps organizations engineer high-performance web applications, custom business platforms, and automated cloud workflows.

Discuss Your Architecture Needs →

Ready to Build Something Great?

Tell us what you want to build. We help turn your ideas into resilient, secure, and production-ready digital software.

Chat WhatsApp