WorkingDirectory icon
TrivyCLI icon
Commands icon
Docker icon
Script icon
If icon
SlackIncomingWebhook icon
Create icon
Fail icon
Webhook icon

SLSA Supply-Chain Attestation Admission Gate

Admit container images only when proven in Kestra: Trivy SBOM, cosign sign and attest, verify gate, then promote or block the release with an issue.

Categories
Infrastructure

Diagram unavailable

We could not build the topology for this blueprint. The flow itself is valid, use the YAML on the left to run it.

Signing an image proves who built it; verifying the signature before production pulls it proves someone actually checked. Most pipelines do the first half and skip the second - the registry accepts whatever CI pushes, and the first time anyone verifies a signature is during an incident. This blueprint is the missing admission control: every pushed image gets an SBOM, an attached attestation, a cryptographic verification against your public key, and only then a promotion to the production reference. Anything that fails either half stops the release with a ticket instead of a red build nobody reads.

How it works

  1. image_push (io.kestra.plugin.core.trigger.Webhook) receives the pushed image reference from CI or a registry event relay; trigger.body.image overrides the image input.
  2. prepare_evidence (io.kestra.plugin.core.flow.WorkingDirectory) gives the evidence tasks one shared directory. Inside it, sbom (io.kestra.plugin.trivy.cli.TrivyCLI) generates a CycloneDX SBOM (sbom.json) - the predicate the attestation will cover.
  3. sign_attest (io.kestra.plugin.scripts.shell.Commands on the bitnami/cosign Docker runner, key material from env) signs the image and attaches the SBOM as a cyclonedx attestation.
  4. verify runs cosign verify and cosign verify-attestation against COSIGN_PUBLIC_KEY - deliberately writing marker files on failure instead of aborting, so the gate decides the outcome, not the shell exit code.
  5. verdict (io.kestra.plugin.scripts.python.Script) reads the JSON outputs and marker files into signature_ok, attestation_ok, and gate_pass.
  6. integrity_gate (io.kestra.plugin.core.flow.If): on pass, promote runs cosign copy so the production reference carries signatures and attestations with it, and Slack announces the admission; on failure, open_block_issue (io.kestra.plugin.github.issues.Create) files a labeled release-blocked ticket with both check results, Slack alerts, and core.execution.Fail stops any deploy pipeline waiting on this gate.
  7. The flow-level errors handler alerts Slack when the gate itself breaks (registry outage, key material missing), so a broken gate reads as blocked, never as approved.

What you get

  • A real admission decision: production only ever pulls images whose signature and SBOM attestation verify against your key.
  • SBOM generation built in - the CycloneDX document is the attestation predicate, not a report someone remembers to file.
  • Promotion via cosign copy, so provenance travels to the production reference.
  • Blocked releases as assignable GitHub issues carrying the exact failing check.
  • image, promoted_to, and gate_pass outputs for deploy pipelines to wait on.

Who it's for

  • Platform teams enforcing SLSA-style provenance on everything that reaches production registries.
  • Security engineers who need attestation evidence, not just a green checkmark in CI.
  • Regulated environments where "the image was signed and verified" must be demonstrable per release.

Why orchestrate this with Kestra

Verification stapled into CI scripts fails open: the job is skipped, the key is missing, and nothing stops the deploy. Kestra makes the gate a first-class execution with a failure handler that alerts, outputs that downstream pipelines can wait on, webhook entry from any registry event, and an audit trail where every admission or block is a recorded decision with its numbers.

Prerequisites

  • A worker with Trivy installed (or wrap it in a container runner) and Docker access for the cosign steps.
  • An image already pushed to a registry the worker can reach.
  • cosign key pair: private key for signing, public key for verification.

Secrets

  • COSIGN_PRIVATE_KEY: PEM-encoded cosign private key (used via env://COSIGN_KEY).
  • COSIGN_PASSWORD: password protecting the private key.
  • COSIGN_PUBLIC_KEY: PEM-encoded cosign public key for verification.
  • SLACK_WEBHOOK_URL: Slack incoming webhook for admission and failure messages.
  • GITHUB_TOKEN: token with issue-write access to the repository input.

Quick start

  1. Add the five secrets above and replace the webhook key with a strong random value.
  2. Set image to a pushed reference and promote_to to your production tag.
  3. Run once - watch sbom, sign_attest, verify, and the verdict numbers.
  4. Delete the public key secret (or sign with a different key) and run again - expect marker files, gate_pass=false, the GitHub issue, and the failed execution.
  5. Wire the image_push webhook to CI and gate deploys on this flow finishing green.

How to extend

  • Add CVE policy to the admission: run trivy image --exit-code 1 --severity HIGH,CRITICAL before sign_attest so vulnerability and provenance share one gate.
  • Verify signatures from multiple keys (vendor + internal) by adding a second cosign verify command and requiring both in verdict.
  • Record every admission: append image and gate_pass to a registry table with a jdbc task for audit dashboards.
  • Keyless mode: swap --key env://... for --certificate-identity / --certificate-oidc-issuer to verify Fulcio-issued signatures.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.