Safeguard
Container Security

Signing container images with AWS Signer for supply chain...

A practical walkthrough for signing container images with AWS Signer, verifying them in ECR and EKS, and closing supply chain gaps with cryptographic trust.

Karan Patel
Cloud Security Engineer
8 min read

Container images move fast through modern CI/CD pipelines, and that speed creates an integrity problem: how do you know the image running in production is the exact artifact your pipeline built, and not something tampered with in a registry, a mirror, or a compromised build step? AWS Signer container image signing solves this by attaching cryptographically verifiable signatures to your OCI images, backed by AWS-managed keys and audit trails in CloudTrail. Combined with Amazon ECR and the open-source Notation CLI, you get a signing chain that spans build, push, and deployment.

In this guide, you'll set up AWS Signer, sign container images AWS Signer produces trust artifacts for, push signed images to ECR, and enforce verification at admission time in EKS so unsigned or tampered images never reach a running pod. By the end, you'll have a repeatable pipeline step and a cluster-side policy that closes one of the most commonly overlooked gaps in software supply chain security.

Step 1: Set Up Your AWS Signer Container Image Signing Environment

Before you can sign anything, you need a signing profile and the right IAM permissions. AWS Signer container image signing profiles are created per platform — for containers, you'll use the Notation-OCI-SHA384-ECDSA signing profile type, which is purpose-built for OCI artifacts signed via Notation.

Start by creating the signing profile:

aws signer put-signing-profile \
  --profile-name safeguard_container_signing \
  --platform-id "Notation-OCI-SHA384-ECDSA" \
  --signing-material '{"certificateArn":"arn:aws:acm-pca:us-east-1:111122223333:certificate-authority/abc123"}'

If you don't already have a private CA in ACM Private CA, provision one first — AWS Signer requires it as the root of trust for container signing profiles. Note the profileVersionArn returned by this call; you'll reference it during signing.

Next, attach an IAM policy to your CI role granting signer:StartSigningJob, signer:GetSigningProfile, signer:DescribeSigningJob, and signer:ListSigningJobs. Least-privilege scoping matters here — a compromised CI credential with broad Signer access is itself a supply chain risk.

Step 2: Install the Notation CLI and AWS Signer Plugin

AWS Signer doesn't sign OCI manifests directly through the console; it integrates with Notation, the CNCF signing tool, via a plugin. Install both:

# Install Notation CLI
curl -Lo notation.tar.gz https://github.com/notaryproject/notation/releases/download/v1.2.0/notation_1.2.0_linux_amd64.tar.gz
tar xzf notation.tar.gz && sudo mv notation /usr/local/bin/

# Install the AWS Signer Notation plugin
notation plugin install --url https://d2hvyiie56hcat.cloudfront.net/linux/amd64/plugin.zip \
  --sha256sum <plugin-checksum>

Verify installation:

notation plugin ls

You should see com.amazonaws.signer.notation.plugin listed. This plugin is what lets Notation delegate the actual signing operation to AWS Signer's managed keys instead of holding a private key locally — a meaningful security improvement over self-managed signing keys sitting on build agents.

Step 3: Build and Push Your Image to Amazon ECR

Signing happens after the image exists in a registry, since Notation signs the image's digest, not a local tarball. Build and push as usual:

docker build -t 111122223333.dkr.ecr.us-east-1.amazonaws.com/payments-api:1.4.0 .
aws ecr get-login-password --region us-east-1 | docker login --username AWS \
  --password-stdin 111122223333.dkr.ecr.us-east-1.amazonaws.com
docker push 111122223333.dkr.ecr.us-east-1.amazonaws.com/payments-api:1.4.0

Capture the digest — you'll sign the digest reference, not the mutable tag, so that a later tag reassignment can't silently invalidate or bypass your signature:

DIGEST=$(aws ecr describe-images --repository-name payments-api \
  --image-ids imageTag=1.4.0 --query 'imageDetails[0].imageDigest' --output text)

Step 4: Sign the Image with AWS Signer

This is the core step. Using the profile ARN from Step 1, sign the pushed digest:

notation sign 111122223333.dkr.ecr.us-east-1.amazonaws.com/payments-api@$DIGEST \
  --plugin com.amazonaws.signer.notation.plugin \
  --id arn:aws:signer:us-east-1:111122223333:/signing-profiles/safeguard_container_signing

Notation calls AWS Signer's StartSigningJob API, AWS Signer signs the manifest digest with the profile's managed key, and Notation stores the resulting signature as a separate OCI artifact in ECR, linked to the original image via the OCI referrers API. Nothing about the original image layers changes — the signature lives alongside it.

Confirm the signature landed in the registry:

notation ls 111122223333.dkr.ecr.us-east-1.amazonaws.com/payments-api@$DIGEST

This is the point where teams typically wire signing into a pipeline step that runs immediately after push and before any deployment manifest references the image — treat an unsigned image the same way you'd treat a failed test.

Step 5: Wire Signing into CI/CD

Manual signing doesn't scale. Add the sign step to your pipeline right after push, and fail the build if signing fails:

- name: Sign image with AWS Signer
  run: |
    notation sign ${{ env.ECR_REPO }}@${{ steps.push.outputs.digest }} \
      --plugin com.amazonaws.signer.notation.plugin \
      --id ${{ secrets.SIGNER_PROFILE_ARN }}

Because the signing operation calls out to AWS Signer (not a local key), you don't need to manage or rotate a signing key in your CI secrets store at all — only an IAM role capable of invoking Signer. That's a real reduction in the attack surface compared to storing a cosign or GPG private key as a pipeline secret.

Step 6: Verify Signed Images in EKS at Admission

Signing is only half the story — you need something in the cluster that checks the signature before a pod is scheduled. Kyverno is a common choice for this in EKS, since it supports Notation verification natively as of Kyverno 1.11+.

Install Kyverno if it's not already running:

helm install kyverno kyverno/kyverno -n kyverno --create-namespace

Then apply a policy that requires a valid AWS Signer signature for any image from your ECR registry:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-ecr-signature
      match:
        any:
          - resources:
              kinds: ["Pod"]
      verifyImages:
        - imageReferences:
            - "111122223333.dkr.ecr.us-east-1.amazonaws.com/*"
          attestors:
            - entries:
                - certificates:
                    cert: |
                      -----BEGIN CERTIFICATE-----
                      <your ACM Private CA root certificate>
                      -----END CERTIFICATE-----

With this policy in Enforce mode, any pod referencing an unsigned or tamper-evident image is rejected at admission — this is how you verify signed images EKS workloads depend on before they ever hit a node, rather than discovering the problem in production.

Step 7: Extend Coverage to Multi-Account and GitOps Pipelines

If you run multiple AWS accounts (common in org structures with separate build and runtime accounts), replicate the signing profile's trust anchor — the ACM Private CA certificate — into each cluster's verification policy rather than duplicating signing profiles. Signing should happen once, centrally, in your build account; verification should happen everywhere images are deployed. For GitOps flows with Argo CD or Flux, add an admission-time check as shown in Step 6 rather than a pre-merge check, since manifests can reference tags that get re-pointed after review.

Troubleshooting and Verification

Signature not found during verification: Confirm you signed the digest, not the tag. A docker push after signing (even with the same tag) creates a new digest with no attached signature — always re-sign after any rebuild or retag.

AccessDeniedException on StartSigningJob: Check that the CI role's trust policy and permissions boundary both allow signer:StartSigningJob for the specific profile ARN — Signer supports resource-level IAM conditions, and a permissions boundary silently narrowing access is a frequent culprit.

Kyverno reports failed to verify signature: This almost always means a certificate mismatch. Re-export the current ACM Private CA root and confirm it matches what's embedded in your ClusterPolicy — CA rotation without a corresponding policy update will break verification for every image signed after the rotation.

Confirming end-to-end integrity: Periodically run notation verify against a known image outside the cluster to confirm the signature and trust chain independently of Kyverno:

notation verify 111122223333.dkr.ecr.us-east-1.amazonaws.com/payments-api@$DIGEST \
  --trust-policy trustpolicy.json

This gives you a registry-side check that's independent of cluster admission logic, useful for catching drift between your signing pipeline and your enforcement policy.

How Safeguard Helps

Setting up AWS Signer container image signing is a strong foundation, but most organizations run dozens of repositories, multiple AWS accounts, and a mix of EKS clusters at different policy maturity levels — and signing coverage tends to erode quietly over time as new services and pipelines get added without the signing step. Safeguard continuously inventories every container image across your registries and clusters, flags any image running without a valid signature or with a broken trust chain, and maps signing coverage back to specific repositories and teams so gaps get fixed instead of discovered during an audit.

Safeguard also validates that your Kyverno or equivalent admission policies are actually in Enforce mode across every cluster — not just staged in Audit — and alerts when a policy drifts, a CA rotates without a corresponding update, or a new namespace is deployed without signature verification wired in. For teams building toward SOC 2 or supply chain attestation requirements, Safeguard turns your AWS Signer and Notation setup into continuous, auditable evidence rather than a one-time configuration exercise.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.