ITADN
saschagrunert/nri-supply-chain
README.md

Supply Chain NRI Plugin

ci build and release GitHub release codecov Go Reference

An NRI plugin for supply chain attestation verification at the container runtime level. It intercepts container creation events on CRI-O or containerd and verifies SLSA provenance, VEX, VSA, Notation signatures, SBOM attestations, and CEL policy expressions before a container is allowed to run.

Runtime-level enforcement cannot be bypassed by misconfigured admission webhooks, disabled policy controllers, or direct kubelet API calls. The plugin operates below the Kubernetes API layer, so every container that runs on a node must pass verification.

Quickstart

  1. Download the latest release binary or container image from the releases page.

  2. Create a configuration file (config.toml):

    verification = "enforce"
    policy_dir = "/etc/nri-supply-chain/policies"
  3. Create a default policy (/etc/nri-supply-chain/policies/default.json):

    {
      "trust": {
        "issuers": ["https://token.actions.githubusercontent.com"],
        "sanPatterns": ["https://github.com/saschagrunert/nri-supply-chain/**"],
        "sources": ["github.com/saschagrunert/*"]
      },
      "slsa": { "missingPolicy": "deny" },
      "vex": { "missingPolicy": "deny" },
      "sbom": { "missingPolicy": "deny" }
    }
  4. Verify a single image to test the configuration:

    nri-supply-chain --config config.toml \
      verify ghcr.io/saschagrunert/nri-supply-chain:0.3.0

    The default output is a colored table:

    Image: ghcr.io/saschagrunert/nri-supply-chain:0.3.0
    Digest: sha256:abc123...
    Namespace: default
    Policy: /etc/nri-supply-chain/policies/default.json
    Mode: enforce
    Result: ALLOWED
    
    TYPE       STATUS   DETAIL
    SLSA       pass     SLSA provenance verified
    VEX        pass     VEX verification passed
    NOTATION   pass     no Notation signature found for image ghcr.io/saschagrunert/nri-supply-chain:0.3.0
    SBOM       pass     SBOM verification passed
    SCAI       pass     no SCAI attestation found for image ghcr.io/saschagrunert/nri-supply-chain:0.3.0

    Use --output json for machine-readable output:

    {
      "image": "ghcr.io/saschagrunert/nri-supply-chain:0.3.0",
      "digest": "sha256:abc123...",
      "namespace": "default",
      "allowed": true,
      "checkResults": [
        {
          "type": "slsa",
          "passed": true,
          "status": "pass",
          "detail": "SLSA provenance verified"
        },
        {
          "type": "vex",
          "passed": true,
          "status": "pass",
          "detail": "VEX verification passed"
        },
        {
          "type": "notation",
          "passed": true,
          "status": "pass",
          "detail": "no Notation signature found for image ghcr.io/saschagrunert/nri-supply-chain:0.3.0"
        },
        {
          "type": "sbom",
          "passed": true,
          "status": "pass",
          "detail": "SBOM verification passed"
        },
        {
          "type": "scai",
          "passed": true,
          "status": "pass",
          "detail": "no SCAI attestation found for image ghcr.io/saschagrunert/nri-supply-chain:0.3.0"
        }
      ]
    }

    To enable VSA-accelerated verification, add a trust.verifiers entry. A trusted VSA short-circuits SLSA and VEX checks:

    {
      "trust": {
        "issuers": ["https://token.actions.githubusercontent.com"],
        "sanPatterns": ["https://github.com/saschagrunert/nri-supply-chain/**"],
        "sources": ["github.com/saschagrunert/*"],
        "verifiers": [
          {
            "id": "https://github.com/saschagrunert/nri-supply-chain/.github/workflows/release.yml"
          }
        ]
      },
      "slsa": { "missingPolicy": "deny" },
      "vex": { "missingPolicy": "deny" },
      "sbom": { "missingPolicy": "deny" }
    }

    With this policy the default table output becomes:

    Image: ghcr.io/saschagrunert/nri-supply-chain:0.3.0
    Digest: sha256:abc123...
    Namespace: default
    Policy: /etc/nri-supply-chain/policies/default.json
    Mode: enforce
    Result: ALLOWED
    Reason: VSA verification passed, skipping direct verification
    
    TYPE   STATUS   DETAIL
    VSA    pass     VSA verification passed

    In enforce mode images that fail verification are rejected. Use verification = "warn" to observe what would be blocked without rejecting. See docs/verification.md for the full verification flow.

  5. Deploy the plugin (requires NRI enabled on the container runtime, see Runtime Requirements). Edit the placeholder values (myorg) in the ConfigMap before deploying:

    kubectl apply -f deploy/kubernetes/
  6. Check the logs and metrics to observe verification decisions.

Compatibility

ComponentSupported Versions
Kubernetes1.26+
CRI-O1.28+ (NRI enabled)
containerd1.7+ (NRI enabled)
NRI0.6+
Go (build)1.26+

NRI must be enabled in the container runtime configuration. See Runtime Requirements for details.

Architecture

Verification flow diagram
flowchart TD
    Runtime["Container Runtime\n(CRI-O / containerd)"]
    NRI["NRI Hook\n(CreateContainer)"]
    Plugin["nri-supply-chain"]
    Extract["Extract image ref + digest"]
    Policy["Policy lookup\n(namespace or default)"]
    Exclude{"Excluded?"}
    Cache{"Cache hit?"}
    Fetch["Fetch attestations\n(OCI Referrers API +\ncosign tag fallback)"]
    VSA{"Trusted VSA?"}
    Parallel["SLSA + VEX + Notation + SBOM\n(parallel)"]
    CEL["CEL policy evaluation"]
    Enforce{"Enforce / Warn"}
    Allow["Allow container"]
    Reject["Reject container"]
    Registry["OCI Registry"]

    Runtime --> NRI --> Plugin --> Extract --> Policy --> Exclude
    Exclude -- yes --> Allow
    Exclude -- no --> Cache
    Cache -- hit --> Enforce
    Cache -- miss --> Fetch
    Fetch <--> Registry
    Fetch --> VSA
    VSA -- "PASSED" --> Enforce
    VSA -- "FAILED" --> Enforce
    VSA -- "untrusted / stale / missing" --> Parallel
    Parallel --> CEL --> Enforce
    Enforce -- pass --> Allow
    Enforce -- "fail (enforce mode)" --> Reject
    Enforce -- "fail (warn mode)" --> Allow

The plugin runs as a long-lived process that connects to the container runtime via NRI. It exposes Prometheus metrics and supports live config reload via SIGHUP.

At startup, the NRI Synchronize callback delivers the list of pods and containers already running on the node. The plugin collects their image references, resolving missing digests via registry HEAD requests when needed (for example, on containerd where NRI annotations may omit the digest). It deduplicates by digest and namespace, then spawns a background goroutine that pre-verifies each image. This warms the cache so that verification results are immediately available if those containers are restarted, avoiding a cold-cache fetch penalty.

Verification

The plugin verifies SLSA provenance, VEX, VSA, Notation, and SBOM attestations with optional CEL policy expressions. It extracts image references and digests from CRI-O or containerd NRI annotations, resolves missing digests via registry HEAD requests, and applies per-namespace policies. VSA from a trusted verifier can short-circuit all other checks.

For the full verification flow, annotation handling details, and per-type checks, see docs/verification.md.

Configuration

The plugin uses two configuration layers:

  • Operational config (TOML): controls the plugin behavior (mode, timeouts, cache, metrics). See docs/config.md for the full field reference and CLI flags.
  • Policy files (JSON): define per-namespace trust roots and verification requirements. See docs/policy.md for the field reference, pattern matching semantics, and deployment patterns.

Deployment and Examples

See docs/deployment.md for all deployment options (DaemonSet, systemd, DEB/RPM, container image, pre-installed NRI plugin) and example configurations (gradual rollout, strict production, VSA-accelerated).

See deploy/examples/policies/ for ready-to-use policy files covering keyless, key-based, VEX-strict, VSA-accelerated, and other scenarios.

Operations

The plugin exposes Prometheus metrics, /healthz and /readyz endpoints, and supports live config reload via SIGHUP or filesystem watching. See docs/operations.md for the metrics reference, alerting rules, troubleshooting guide, internal limits, and security considerations.

Supply Chain Compliance

This project targets SLSA Build Level 3. SLSA provenance for both container images and binary artifacts is generated using slsa-github-generator, which runs as an isolated reusable workflow with its own signing identity. Each release also includes an OpenVEX vulnerability assessment, an SPDX SBOM, cosign keyless signatures, and a Verification Summary Attestation (VSA).

The VSA is still a bootstrapping self-attestation: it is generated by the same workflow that builds the artifact, so the signer and producer share a workflow identity. It records that the release pipeline verified its own output, but it is not independently verifiable by a third party per SLSA's verifier requirements.

See docs/threat-model.md for a STRIDE-based threat analysis covering trust boundaries, mitigations, and residual risks.

Verifying Releases

Release binaries are published with a SHA-256 checksum file that is signed using cosign. An SBOM (Software Bill of Materials) is generated with syft for each release. SLSA provenance for binary artifacts is generated using the slsa-github-generator and uploaded as a release asset. Both versioned releases and the latest tag include full attestation coverage: SLSA provenance, VEX, SBOM, self-verification, and a VSA recording the verification result from the release pipeline.

To verify a release:

  1. Verify the checksum file signature with cosign:

    cosign verify-blob --bundle checksums.txt.sigstore.json checksums.txt
  2. Verify the binary against the checksum file:

    sha256sum --check checksums.txt
  3. Verify binary SLSA provenance using slsa-verifier:

    slsa-verifier verify-artifact nri-supply-chain_<version>_linux_amd64 \
      --provenance-path multiple.intoto.jsonl \
      --source-uri github.com/saschagrunert/nri-supply-chain \
      --source-tag v<version>
  4. Verify container image SLSA provenance using slsa-verifier:

    slsa-verifier verify-image ghcr.io/saschagrunert/nri-supply-chain:<version> \
      --source-uri github.com/saschagrunert/nri-supply-chain \
      --source-tag v<version>
  5. Verify the container image signature:

    cosign verify ghcr.io/saschagrunert/nri-supply-chain:latest \
      --certificate-oidc-issuer https://token.actions.githubusercontent.com \
      --certificate-identity-regexp 'https://github.com/saschagrunert/nri-supply-chain/'
  6. Verify the VSA (Verification Summary Attestation) on the container image:

    cosign verify-attestation ghcr.io/saschagrunert/nri-supply-chain:latest \
      --type https://slsa.dev/verification_summary/v1 \
      --certificate-oidc-issuer https://token.actions.githubusercontent.com \
      --certificate-identity-regexp 'https://github.com/saschagrunert/nri-supply-chain/'
  7. Inspect the SBOM (generated with syft, integrity covered by the signed checksum file from step 1):

    cat nri-supply-chain_<version>_linux_amd64.sbom.json | jq .