Image Verification

Verifying container image signatures before deployment is a critical supply chain security control. Without verification, a compromised registry, man-in-the-middle attack, or DNS hijack could serve tampered images that appear legitimate. Signature verification provides cryptographic proof that an image was signed by the ARMORRED project and has not been modified since publication.

ARMORRED images are signed using Sigstore cosign with a project-owned ECDSA P-256 key pair. The public key is published at https://armorred.org/cosign.pub.1

Note: Only images published to ghcr.io/armorred/* are signed. Docker Hub images are not signed due to registry API limitations.

Public Key

The ARMORRED cosign public key:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7vpEsQTwL/8XDx0KU6+jU64okQB1
A0sMNk5I6GNdZoNCJzJMyOj3jkuGm3TPDE6TtqWlbkScX0EcHv5U0kJqgg==
-----END PUBLIC KEY-----

Download it:


$ curl -sLO https://armorred.org/cosign.pub

Prerequisites

Install cosign:


$ curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
$ chmod +x cosign-linux-amd64
$ sudo mv cosign-linux-amd64 /usr/local/bin/cosign

$ cosign version
cosign v2.x.x

Cosign is also available through package managers:

PlatformCommand
NixOSnix shell nixpkgs#cosign
Homebrewbrew install cosign
Arch Linuxpacman -S cosign

Verifying Image Signatures

Basic Verification

Verify an ARMORRED image using the public key:


$ cosign verify --key https://armorred.org/cosign.pub \
ghcr.io/armorred/nginx:1.26-hardened

Verification for ghcr.io/armorred/nginx:1.26-hardened --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key

[{"critical":{"identity":{"docker-reference":"ghcr.io/armorred/nginx"}, ...}]

Or using a locally downloaded copy of the key:


$ curl -sLO https://armorred.org/cosign.pub
$ cosign verify --key cosign.pub ghcr.io/armorred/nginx:1.26-hardened

A successful verification confirms:

  1. The image was signed by the holder of the ARMORRED private key
  2. The image content has not been tampered with since signing

Verification Failure

If verification fails, cosign outputs an error:


$ cosign verify --key cosign.pub ghcr.io/suspicious/nginx:latest
Error: no matching signatures:
failed to verify signature

Do not deploy images that fail signature verification. Possible causes include:

  • The image was not signed by ARMORRED
  • The image was modified after publication
  • The registry returned a different image than requested
  • The image tag was overwritten with unauthorized content

Downloading and Inspecting SBOMs

ARMORRED attaches SBOM attestations to images as OCI artifacts. Download the SBOM:


$ cosign download sbom ghcr.io/armorred/nginx:1.26-hardened > sbom.json

$ jq '.metadata.component' sbom.json
{
"type": "container",
"name": "localhost/nginx",
"version": "1.26.3-hardened"
}

$ jq '.components | length' sbom.json
19

Scan the downloaded SBOM for vulnerabilities:


$ grype sbom:sbom.json
NAME INSTALLED TYPE VULNERABILITY SEVERITY
libxml2 2.13.8 nix OSV-2021-777 High

See Understanding SBOMs for comprehensive SBOM usage guidance.

Policy Enforcement

For production environments, automate signature verification through admission controllers that reject unsigned or improperly signed images.

Sigstore Policy Controller

The Sigstore Policy Controller integrates with Kubernetes admission webhooks:

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: armorred-images
spec:
  images:
    - glob: "ghcr.io/armorred/**"
  authorities:
    - key:
        data: |
          -----BEGIN PUBLIC KEY-----
          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7vpEsQTwL/8XDx0KU6+jU64okQB1
          A0sMNk5I6GNdZoNCJzJMyOj3jkuGm3TPDE6TtqWlbkScX0EcHv5U0kJqgg==
          -----END PUBLIC KEY-----

Kyverno

Kyverno provides image verification as a built-in policy feature:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-armorred
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/armorred/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7vpEsQTwL/8XDx0KU6+jU64okQB1
                      A0sMNk5I6GNdZoNCJzJMyOj3jkuGm3TPDE6TtqWlbkScX0EcHv5U0kJqgg==
                      -----END PUBLIC KEY-----

Podman Policy

For non-Kubernetes environments, configure Podman's policy.json to require signatures:

{
  "default": [{"type": "reject"}],
  "transports": {
    "docker": {
      "ghcr.io/armorred": [
        {
          "type": "sigstoreSigned",
          "keyPath": "/etc/pki/cosign/armorred.pub"
        }
      ]
    }
  }
}

Place the public key at the configured keyPath:


$ sudo mkdir -p /etc/pki/cosign
$ sudo curl -sL https://armorred.org/cosign.pub \
-o /etc/pki/cosign/armorred.pub

References


1

Sigstore. "Cosign: Container Signing." https://docs.sigstore.dev/signing/overview/