Introduction: Why Container Signing Matters

Every container image your cluster pulls is a trust decision. When your EKS node downloads myapp:v2.1.0 from ECR, how does it know that image was built by your CI pipeline and not injected by an attacker who compromised your registry credentials? Without cryptographic signatures, the answer is: it does not.

The container supply chain has become one of the most targeted attack surfaces in cloud-native environments. The container security market reached $3.05 billion in 2025 and is projected to hit $9.01 billion by 2030, driven by organizations realizing that unsigned images are an open door for supply chain attacks. Chainguard’s $356 million Series D at a $3.5 billion valuation underscores just how seriously the industry is taking this problem.

Recent incidents paint a stark picture:

  • 78% of organizations rely on third-party container images with no verification of provenance
  • Researchers demonstrated successful supply chain attacks against 92% of popular ML frameworks through poisoned container images
  • The average cost of a supply chain compromise has reached $6.8 million per incident

The solution is cryptographic signing and verification: every image gets a signature proving who built it, when, and from what source. At deployment time, admission controllers reject anything unsigned. This is zero-trust applied to your artifact pipeline.

AWS provides its own signing service (AWS Signer with managed ECR signing), but the open-source Sigstore ecosystem, led by Cosign, has become the industry standard. Kubernetes itself uses Sigstore to sign its releases. The key advantage: keyless signing with Fulcio eliminates the entire problem of key management, while Rekor transparency logs provide a public, tamper-evident audit trail.

This guide walks through implementing container image signing on AWS using both approaches, with a clear recommendation: lead with Cosign for portability and keyless signing, use AWS Signer when you need managed simplicity within a pure-AWS environment.

The Sigstore Ecosystem

Sigstore is an open-source project under the Linux Foundation that provides free, easy-to-use tools for signing, verifying, and protecting software artifacts. It consists of three core components that work together.

Cosign: The Signing Tool

Cosign is the command-line tool that signs container images and other OCI artifacts. It supports multiple signing modes:

  • Keyless signing via Fulcio (recommended) – no keys to manage
  • Key-pair signing with local or KMS-managed keys (AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault)
  • Hardware key signing with PKCS#11 tokens and Yubikeys

Cosign stores signatures as OCI artifacts alongside the image in the same registry. This means ECR, Docker Hub, GitHub Container Registry, and any OCI-compliant registry work out of the box with no additional infrastructure.

Cosign v3, the latest major release, introduces the standardized bundle format (--new-bundle-format) that packages all signed material and verification data into a single artifact. The upcoming Cosign v4 will simplify the CLI further by removing legacy flags.

Fulcio: The Certificate Authority

Fulcio is a free, public certificate authority that issues short-lived code signing certificates (valid for 10 minutes) bound to an OpenID Connect (OIDC) identity. Instead of managing long-lived signing keys, you authenticate with an identity provider (GitHub, Google, Microsoft) and Fulcio issues a certificate that proves you (or your CI system) performed the signing.

This eliminates the single biggest operational burden in code signing: key lifecycle management. No key generation, no key rotation, no key revocation, no key storage – just identity.

Rekor: The Transparency Log

Rekor is an immutable, append-only transparency log that records every signing event. When Cosign signs an image, it uploads the signature and certificate to Rekor, creating a tamper-evident record. This serves three purposes:

  1. Auditability – you can verify when any artifact was signed and by whom
  2. Compromise detection – if Fulcio is compromised and issues rogue certificates, the transparency log reveals it
  3. Offline verification – verifiers can check that a signature was logged at a time when the certificate was valid, even after the certificate expires

Together, these three components create a “keyless” signing workflow that is more secure than traditional key-based signing because it eliminates the key management attack surface entirely.

How Keyless Signing Works

Understanding the keyless signing flow is critical for trusting it. Here is what happens when you run cosign sign in keyless mode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Developer/CI ──OIDC──> Identity Provider (GitHub, Google)
     │                        │
     │                   OIDC Token
     │                        │
     ▼                        ▼
  Cosign ──certificate req──> Fulcio (issues 10-min cert)
     │                        │
     │                  Short-lived cert
     │                        │
     ▼                        ▼
  Sign image ──signature──> Rekor (transparency log)
     │
     ▼
  Push signature to OCI Registry (alongside image)

Step by step:

  1. OIDC authentication – Cosign triggers an OIDC flow. In CI (GitHub Actions, GitLab CI), this uses the platform’s built-in OIDC token. Interactively, it opens a browser for Google/GitHub/Microsoft login.
  2. Ephemeral key generation – Cosign generates an ephemeral key pair in memory. The private key never touches disk and is never sent to any Sigstore service.
  3. Certificate request – Cosign sends the public key and OIDC token to Fulcio. Fulcio validates the token, issues a short-lived X.509 certificate binding the public key to the OIDC identity (e.g., github-actions@my-org), and publishes the certificate to a certificate transparency log.
  4. Signing – Cosign signs the image digest with the ephemeral private key.
  5. Transparency logging – The signature and certificate are uploaded to Rekor, creating an immutable record.
  6. Registry upload – The signature is pushed to the OCI registry as a referrer artifact alongside the signed image.
  7. Key destruction – The ephemeral private key is discarded from memory.

At verification time, cosign verify checks: (a) the signature is valid for the image digest, (b) the certificate was issued by Fulcio, (c) the signing event was logged in Rekor at a time when the certificate was valid, and (d) the OIDC identity matches what you expect.

AWS Signer: The Managed Alternative

AWS launched managed container image signing for ECR in late 2025. This is a fully managed service where ECR automatically signs images as they are pushed, using AWS Signer for the cryptographic operations.

Setting Up AWS Signer

1
2
3
4
5
6
7
8
9
10
11
# Create a signing profile
aws signer put-signing-profile \
    --profile-name my-container-profile \
    --platform-id Notation-OCI-SHA384-ECDSA

# Create an ECR signing rule (managed signing)
aws ecr put-registry-signing-configuration \
    --signing-configuration \
        "signingProfiles=[{profileName=my-container-profile}]" \
    --signing-rules \
        "repositorySelectionCriteria=ALL"

How It Works

Once configured, ECR automatically signs every image pushed to repositories matching the signing rule. AWS Signer handles:

  • Key material generation and secure storage (backed by AWS KMS)
  • Certificate lifecycle management including rotation
  • Signing operations logged through CloudTrail

Limitations

AWS Signer is convenient but comes with significant trade-offs:

  • AWS lock-in – signatures are in Notation format, not Cosign format. Verification requires AWS-specific tooling.
  • No keyless signing – AWS manages the keys, but keys still exist. You trade self-managed keys for AWS-managed keys.
  • No transparency log – there is no public, tamper-evident record of signing events. CloudTrail provides audit logging but within your AWS account, not a public ledger.
  • No SBOM attestation support – AWS Signer signs images but does not support arbitrary attestations (SBOMs, vulnerability scan results, SLSA provenance).
  • Limited ecosystem integration – Kubernetes admission controllers like Kyverno have first-class Cosign support; AWS Signer integration requires additional configuration.

Cosign on AWS: Signing Images in ECR

Cosign works natively with ECR. Here is the practical setup.

Prerequisites

1
2
3
4
5
6
7
8
9
10
11
12
# Install Cosign
brew install cosign
# or
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# Verify installation
cosign version

# Authenticate to ECR
aws ecr get-login-password --region us-east-1 | \
    docker login --username AWS \
    --password-stdin 111122223333.dkr.ecr.us-east-1.amazonaws.com
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Build and push your image
docker build -t myapp:v2.1.0 .
docker tag myapp:v2.1.0 \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0
docker push \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0

# Sign with keyless (Fulcio + Rekor)
# In CI, this uses the OIDC token automatically
# Interactively, it opens a browser for authentication
COSIGN_EXPERIMENTAL=1 cosign sign \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0

# Verify the signature
cosign verify \
    --certificate-oidc-issuer https://token.actions.githubusercontent.com \
    --certificate-identity-regexp "https://github.com/my-org/.*" \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0

Key-Pair Signing with AWS KMS

For organizations that require key-based signing with centralized key management:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Generate a Cosign key pair backed by AWS KMS
cosign generate-key-pair \
    --kms awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab

# Sign using the KMS key
cosign sign --key \
    awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0

# Verify using the KMS key
cosign verify --key \
    awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp:v2.1.0

ECR Terraform Configuration

Set up your ECR repositories with proper configuration for signed images:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
resource "aws_ecr_repository" "app" {
  name                 = "myapp"
  image_tag_mutability = "IMMUTABLE"  # Critical: prevent tag overwrites

  image_scanning_configuration {
    scan_on_push = true
  }

  encryption_configuration {
    encryption_type = "KMS"
    kms_key         = aws_kms_key.ecr_signing.arn
  }

  tags = {
    Customer    = "internal"
    Application = "myapp"
    Environment = "prod"
    Owner       = "platform-team"
    Costcenter  = "engineering"
  }
}

resource "aws_ecr_lifecycle_policy" "app" {
  repository = aws_ecr_repository.app.name

  policy = jsonencode({
    rules = [{
      rulePriority = 1
      description  = "Keep last 30 signed images"
      selection = {
        tagStatus   = "any"
        countType   = "imageCountMoreThan"
        countNumber = 30
      }
      action = {
        type = "expire"
      }
    }]
  })
}

# KMS key for ECR encryption and Cosign signing
resource "aws_kms_key" "ecr_signing" {
  description             = "KMS key for ECR image encryption and Cosign signing"
  deletion_window_in_days = 30
  enable_key_rotation     = true

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowKeyAdministration"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::111122223333:root"
        }
        Action   = "kms:*"
        Resource = "*"
      },
      {
        Sid    = "AllowCodeBuildSigning"
        Effect = "Allow"
        Principal = {
          AWS = aws_iam_role.codebuild_signing.arn
        }
        Action = [
          "kms:Sign",
          "kms:Verify",
          "kms:GetPublicKey",
          "kms:DescribeKey"
        ]
        Resource = "*"
      }
    ]
  })

  tags = {
    Customer    = "internal"
    Application = "ecr-signing"
    Environment = "prod"
    Owner       = "platform-team"
    Costcenter  = "engineering"
  }
}

resource "aws_kms_alias" "ecr_signing" {
  name          = "alias/ecr-signing"
  target_key_id = aws_kms_key.ecr_signing.key_id
}

CodeBuild Integration: Automated Signing in CI/CD

The real value of container signing is when it runs automatically in your CI/CD pipeline. Here is a complete CodeBuild buildspec that builds, signs, and attests container images.

CodeBuild Buildspec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# buildspec.yml
version: 0.2

env:
  variables:
    AWS_ACCOUNT_ID: "111122223333"
    AWS_DEFAULT_REGION: "us-east-1"
    IMAGE_REPO_NAME: "myapp"
    COSIGN_VERSION: "2.4.1"
  parameter-store:
    # Store the KMS key ARN in SSM Parameter Store
    KMS_KEY_ARN: "/myapp/prod/cosign-kms-key-arn"

phases:
  install:
    commands:
      # Install Cosign
      - curl -LO "https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64"
      - chmod +x cosign-linux-amd64
      - mv cosign-linux-amd64 /usr/local/bin/cosign
      - cosign version

  pre_build:
    commands:
      # Authenticate to ECR
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION |
          docker login --username AWS
          --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      # Set image tag from commit SHA for immutability
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)
      - IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME"
      - FULL_IMAGE="$IMAGE_URI:$IMAGE_TAG"

  build:
    commands:
      # Build the container image
      - docker build -t $FULL_IMAGE .
      # Run security scan before signing
      - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
          aquasec/trivy:latest image --exit-code 1
          --severity CRITICAL,HIGH $FULL_IMAGE
      # Push to ECR
      - docker push $FULL_IMAGE
      # Get the image digest for signing (never sign tags, sign digests)
      - IMAGE_DIGEST=$(docker inspect --format='' $FULL_IMAGE)

  post_build:
    commands:
      # Sign the image with AWS KMS key
      - cosign sign --key "awskms:///$KMS_KEY_ARN"
          --yes $IMAGE_DIGEST
      # Generate and attach SBOM attestation
      - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
          anchore/syft:latest $FULL_IMAGE -o spdx-json > sbom.spdx.json
      - cosign attest --key "awskms:///$KMS_KEY_ARN"
          --predicate sbom.spdx.json
          --type spdxjson
          --yes $IMAGE_DIGEST
      # Attach vulnerability scan results as attestation
      - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
          aquasec/trivy:latest image --format cosign-vuln
          $FULL_IMAGE > vuln-report.json
      - cosign attest --key "awskms:///$KMS_KEY_ARN"
          --predicate vuln-report.json
          --type vuln
          --yes $IMAGE_DIGEST
      # Verify the signature
      - cosign verify --key "awskms:///$KMS_KEY_ARN" $IMAGE_DIGEST
      - echo "Image signed and verified successfully"
      - echo "SIGNED_IMAGE=$IMAGE_DIGEST" >> $CODEBUILD_SRC_DIR/env.txt

artifacts:
  files:
    - env.txt

GitHub Actions with Keyless Signing

For teams using GitHub Actions, keyless signing is even simpler because GitHub provides a built-in OIDC token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# .github/workflows/build-sign.yml
name: Build, Sign, and Push Container Image

on:
  push:
    branches: [main]

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

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: myapp

jobs:
  build-sign-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::111122223333:role/github-actions-ecr
          aws-region: $

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

      - name: Install Cosign
        uses: sigstore/cosign-installer@v3

      - name: Build and push image
        id: build
        env:
          REGISTRY: $
          IMAGE_TAG: $
        run: |
          docker build -t $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          DIGEST=$(docker inspect --format='' \
              $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG)
          echo "digest=$DIGEST" >> $GITHUB_OUTPUT

      - name: Sign image with Cosign (keyless)
        env:
          DIGEST: $
        run: |
          cosign sign --yes $DIGEST

      - name: Verify signature
        env:
          DIGEST: $
        run: |
          cosign verify \
              --certificate-oidc-issuer https://token.actions.githubusercontent.com \
              --certificate-identity \
              "https://github.com/$/.github/workflows/build-sign.yml@refs/heads/main" \
              $DIGEST

      - name: Generate and attest SBOM
        env:
          DIGEST: $
        run: |
          # Generate SBOM with Syft
          curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s
          syft $DIGEST -o spdx-json > sbom.spdx.json

          # Attest SBOM to the image (keyless)
          cosign attest --yes \
              --predicate sbom.spdx.json \
              --type spdxjson \
              $DIGEST

This workflow signs the image using GitHub’s OIDC identity. The signature proves that the image was built by this specific GitHub Actions workflow in this specific repository. No keys to manage, rotate, or protect.

Verification in EKS: Kyverno Admission Policies

Signing images is only half the equation. The other half is rejecting unsigned images at deployment time. Kyverno is a Kubernetes-native policy engine that integrates directly with Cosign for image verification.

Installing Kyverno on EKS

1
2
3
4
5
6
7
8
9
10
# Add the Kyverno Helm repo
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update

# Install Kyverno with admission controller
helm install kyverno kyverno/kyverno \
    --namespace kyverno \
    --create-namespace \
    --set replicaCount=3 \
    --set resources.limits.memory=512Mi

Kyverno Policy: Require Cosign Signatures (Keyless)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# kyverno-verify-images.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-container-signatures
  annotations:
    policies.kyverno.io/title: Verify Container Image Signatures
    policies.kyverno.io/description: >-
      Require all container images to be signed by Cosign with
      a valid keyless signature from our CI/CD pipeline.
spec:
  validationFailureAction: Enforce  # Block unsigned images
  background: false
  webhookTimeoutSeconds: 30
  rules:
    - name: verify-cosign-keyless
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - production
                - staging
      verifyImages:
        - imageReferences:
            - "111122223333.dkr.ecr.us-east-1.amazonaws.com/*"
          attestors:
            - entries:
                - keyless:
                    issuer: "https://token.actions.githubusercontent.com"
                    subject: "https://github.com/my-org/*"
                    rekor:
                      url: "https://rekor.sigstore.dev"
          attestations:
            - type: spdxjson
              conditions:
                - all:
                    - key: ""
                      operator: NotEquals
                      value: ""

Kyverno Policy: Require Signatures with KMS Key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# kyverno-verify-kms.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-kms-signatures
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-cosign-kms
      match:
        any:
          - resources:
              kinds:
                - Pod
              namespaces:
                - production
      verifyImages:
        - imageReferences:
            - "111122223333.dkr.ecr.us-east-1.amazonaws.com/*"
          attestors:
            - entries:
                - keys:
                    kms: "awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd"

Testing the Policy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Apply the policy
kubectl apply -f kyverno-verify-images.yaml

# This should succeed (signed image)
kubectl run signed-app \
    --image=111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123... \
    -n production

# This should be REJECTED (unsigned image from Docker Hub)
kubectl run unsigned-app \
    --image=nginx:latest \
    -n production
# Error: admission webhook "mutate.kyverno.svc-fail" denied the request:
# image verification failed for nginx:latest

Comparison: Cosign vs AWS Signer vs Notation

Choosing the right tool depends on your environment and requirements. Here is a detailed comparison:

Feature Cosign (Sigstore) AWS Signer (Managed ECR) Notation (CNCF)
Keyless signing Yes (Fulcio + OIDC) No (AWS-managed keys) No (key/cert required)
Transparency log Yes (Rekor, public) No (CloudTrail only) No (optional, self-hosted)
Key management Optional (KMS support) Fully managed Self-managed or KMS
SBOM attestations Yes (native) No Yes (via plugins)
SLSA provenance Yes (native) No Yes (via plugins)
Kubernetes integration Kyverno, OPA, Connaisseur Requires custom webhook Ratify (CNCF)
Multi-cloud portable Yes AWS only Yes
Signature format Cosign (OCI 1.1 referrers) Notation (OCI 1.1) Notation (OCI 1.1)
CI/CD integration GitHub, GitLab, CircleCI, CodeBuild CodeBuild, CodePipeline GitHub, GitLab
Setup complexity Low (single binary) Very low (API call) Medium (plugins required)
Cost Free (public Sigstore) Free (included with ECR) Free (self-hosted infra)
Maturity GA, used by Kubernetes project GA (Nov 2025) GA (v1.3.0, Feb 2025)
Community adoption Industry standard AWS ecosystem Growing (CNCF incubating)

When to Use Each

Choose Cosign when:

  • You need keyless signing (no key management)
  • You operate across multiple clouds or registries
  • You want transparency logs for audit and compliance
  • You need SBOM/provenance attestations
  • You are deploying to Kubernetes with Kyverno or OPA

Choose AWS Signer when:

  • You are fully committed to the AWS ecosystem
  • You want zero operational overhead for signing
  • Your compliance requirements are satisfied by CloudTrail auditing
  • You do not need multi-cloud portability

Choose Notation when:

  • You need OCI-standard signatures without Sigstore dependency
  • You are using Azure (ACR has native Notation support)
  • You want plugin extensibility for custom signature formats

Attestations: Beyond Signatures

Signatures prove who built an image. Attestations prove what went into it and how it was built. Cosign supports attaching arbitrary attestations to images in the in-toto format.

SBOM Attestation

Attach a Software Bill of Materials so consumers know exactly what dependencies are in the image:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Generate SBOM with Syft
syft 111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123 \
    -o spdx-json > sbom.spdx.json

# Attest the SBOM to the image
cosign attest --yes \
    --predicate sbom.spdx.json \
    --type spdxjson \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123

# Verify the SBOM attestation
cosign verify-attestation \
    --certificate-oidc-issuer https://token.actions.githubusercontent.com \
    --certificate-identity-regexp "https://github.com/my-org/.*" \
    --type spdxjson \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123

Vulnerability Scan Attestation

Attest that the image passed a vulnerability scan at build time:

1
2
3
4
5
6
7
8
9
10
# Generate Trivy vulnerability report in Cosign format
trivy image --format cosign-vuln \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123 \
    > vuln-report.json

# Attest the vulnerability scan results
cosign attest --yes \
    --predicate vuln-report.json \
    --type vuln \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123

SLSA Provenance Attestation

SLSA (Supply-chain Levels for Software Artifacts) provenance documents the build process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Generate SLSA provenance (typically done by the build system)
cat > provenance.json <<'EOF'
{
  "buildType": "https://github.com/my-org/myapp-build@v1",
  "builder": {
    "id": "https://github.com/my-org/myapp/actions/runs/12345"
  },
  "invocation": {
    "configSource": {
      "uri": "git+https://github.com/my-org/myapp@refs/heads/main",
      "digest": { "sha1": "abc123def456" },
      "entryPoint": ".github/workflows/build-sign.yml"
    }
  },
  "materials": [
    {
      "uri": "git+https://github.com/my-org/myapp",
      "digest": { "sha256": "abc123..." }
    }
  ]
}
EOF

# Attest provenance
cosign attest --yes \
    --predicate provenance.json \
    --type slsaprovenance \
    111122223333.dkr.ecr.us-east-1.amazonaws.com/myapp@sha256:abc123

Complete Pipeline: Build, Sign, Attest, Deploy

Here is the complete end-to-end flow that ties everything together. This represents a zero-trust artifact pipeline where every image must be signed and attested before it can run in production.

Container Image Signing and Verification Pipeline End-to-end container signing pipeline: Build in CodeBuild, sign with Cosign via Fulcio/Rekor, push to ECR, verify with Kyverno, deploy to EKS

Pipeline Stages

Stage 1: Build and Scan

  • CodeBuild (or GitHub Actions) builds the container image from source
  • Trivy scans for vulnerabilities; CRITICAL/HIGH findings fail the build
  • Syft generates an SBOM in SPDX format

Stage 2: Sign and Attest

  • Cosign signs the image digest (keyless via OIDC or KMS-backed key)
  • Signature is recorded in Rekor transparency log
  • SBOM, vulnerability scan, and SLSA provenance are attached as attestations

Stage 3: Push to Registry

  • Signed image and all attestation artifacts are pushed to ECR
  • ECR immutable tags prevent overwriting signed images with unsigned ones

Stage 4: Verify at Admission

  • Deployment targets EKS with Kyverno admission controller
  • Kyverno verifies Cosign signature against expected OIDC issuer/subject
  • Kyverno optionally verifies SBOM and vulnerability attestations
  • Unsigned or unverified images are rejected with a clear error message

Stage 5: Runtime Monitoring

  • Falco or GuardDuty monitors for container runtime anomalies
  • Drift detection alerts if running container content differs from signed image

Implementation Roadmap

Phase 1: Foundation (Weeks 1-2)

  • Install Cosign in CI/CD environment (CodeBuild or GitHub Actions)
  • Configure ECR repositories with immutable tags
  • Implement key-pair signing with AWS KMS as a starting point
  • Sign images in non-production pipelines first

Phase 2: Keyless and Attestations (Weeks 3-4)

  • Migrate from KMS signing to keyless signing with Fulcio
  • Add SBOM generation and attestation to the build pipeline
  • Add vulnerability scan attestation
  • Deploy Kyverno to EKS in Audit mode (log but do not block)

Phase 3: Enforcement (Weeks 5-6)

  • Switch Kyverno to Enforce mode in staging
  • Validate all production images are signed before enabling enforcement
  • Switch Kyverno to Enforce mode in production
  • Set up alerting for signature verification failures

Phase 4: Advanced (Ongoing)

  • Add SLSA provenance attestations
  • Implement Rekor monitoring for unexpected signing events
  • Extend signing to Helm charts and other OCI artifacts
  • Evaluate Cosign policy-controller for additional policy capabilities

Additional Resources

Official Documentation

Tools and Frameworks

  • Syft – SBOM generation for container images
  • Trivy – vulnerability scanner with Cosign integration
  • SLSA Framework – Supply-chain Levels for Software Artifacts
  • Chainguard Images – pre-signed, minimal container base images

Industry Resources

Conclusion

Container image signing is no longer optional. With supply chain attacks increasing in frequency and sophistication, cryptographic verification of every artifact in your pipeline is a baseline security requirement.

The Sigstore ecosystem, with Cosign at its center, provides the most flexible and secure approach to container signing available today. Keyless signing with Fulcio eliminates the operational burden of key management. Rekor transparency logs provide tamper-evident audit trails that no proprietary solution can match. And with Kyverno enforcement in EKS, you can guarantee that only verified images run in your clusters.

AWS Signer with managed ECR signing is a solid choice for teams that want zero operational overhead within a pure-AWS environment. But for teams that value portability, transparency, and the ability to attach rich attestations (SBOMs, vulnerability scans, SLSA provenance), Cosign is the clear winner.

Start with signing. Add attestations. Enforce verification. That is how you zero-trust your artifact pipeline.

For personalized guidance on implementing container signing and supply chain security in your AWS environment, connect with Jon Price on LinkedIn.

Updated: