Skip to content

Signed Releases

Bindy releases are cryptographically signed using Cosign with keyless signing (Sigstore). This ensures:

  • Authenticity: Verify that releases come from the official Bindy GitHub repository
  • Integrity: Detect any tampering with release artifacts
  • Non-repudiation: Cryptographic proof that artifacts were built by official CI/CD
  • Transparency: All signatures are recorded in the Sigstore transparency log (Rekor)

What Is Signed

Every Bindy release includes signed artifacts (ADR-0010):

  1. Container Images (release tags only; PR and main images are not signed):
  2. ghcr.io/firestoned/bindy:v* (Chainguard base, default)
  3. ghcr.io/firestoned/bindy:v*-distroless (Google Distroless base, alternative)

  4. Binary Tarballs:

  5. bindy-linux-amd64.tar.gz, bindy-linux-arm64.tar.gz
  6. bindy-macos-amd64.tar.gz, bindy-macos-arm64.tar.gz
  7. bindy-windows-amd64.tar.gz

  8. Signature Artifacts (uploaded to releases):

  9. *.tar.gz.bundle - Cosign signature bundles for binaries
  10. Container signatures are stored in the OCI registry

  11. SBOMs (CycloneDX JSON, one per shipped artifact):

  12. bindy-<os>-<arch>.cdx.json for each binary tarball
  13. bindy-image-chainguard.cdx.json, bindy-image-distroless.cdx.json for the images
  14. Each is also a signed attestation bound to its artifact's digest

  15. SLSA Build L3 provenance:

  16. <version>.intoto.jsonl covering the tarballs, install manifests and SBOMs
  17. Image provenance pushed to GHCR next to each image

Every signature and attestation names the release workflow as signer: https://github.com/firestoned/bindy/.github/workflows/build.yaml@refs/tags/<version>. Releases before v0.6.0 were signed by release.yaml instead of build.yaml. The examples below anchor on the signer, so a fork or a similarly named repository cannot pass verification:

SIGNER='^https://github\.com/firestoned/bindy/\.github/workflows/build\.yaml@refs/tags/'
ISSUER='https://token.actions.githubusercontent.com'
# Releases before v0.6.0:
# SIGNER='^https://github\.com/firestoned/bindy/\.github/workflows/release\.yaml@refs/tags/'

Installing Cosign

To verify signatures, install Cosign:

# macOS
brew install cosign

# Linux (download binary)
LATEST_VERSION=$(curl -s https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d '"' -f 4)
curl -Lo cosign https://github.com/sigstore/cosign/releases/download/${LATEST_VERSION}/cosign-linux-amd64
chmod +x cosign
sudo mv cosign /usr/local/bin/

# Verify installation
cosign version

Verifying Container Images

Cosign uses keyless signing with Sigstore, which means: - No private keys to manage or distribute - Signatures are verified against the GitHub Actions OIDC identity - All signatures are logged in the public Rekor transparency log

Quick Verification

# Verify a release (Chainguard)
cosign verify \
  --certificate-identity-regexp="$SIGNER" \
  --certificate-oidc-issuer="$ISSUER" \
  ghcr.io/firestoned/bindy:v0.8.0

# Verify the Distroless variant
cosign verify \
  --certificate-identity-regexp="$SIGNER" \
  --certificate-oidc-issuer="$ISSUER" \
  ghcr.io/firestoned/bindy:v0.8.0-distroless

# Or, from a checkout of this repository
make verify-image IMAGE_TAG=v0.8.0

Understanding the Verification Output

When verification succeeds, Cosign returns JSON output with signature details:

[
  {
    "critical": {
      "identity": {
        "docker-reference": "ghcr.io/firestoned/bindy"
      },
      "image": {
        "docker-manifest-digest": "sha256:abcd1234..."
      },
      "type": "cosign container image signature"
    },
    "optional": {
      "Bundle": {
        "SignedEntryTimestamp": "...",
        "Payload": {
          "body": "...",
          "integratedTime": 1234567890,
          "logIndex": 12345678,
          "logID": "..."
        }
      },
      "Issuer": "https://token.actions.githubusercontent.com",
      "Subject": "https://github.com/firestoned/bindy/.github/workflows/build.yaml@refs/tags/v0.1.0"
    }
  }
]

Key fields to verify: - Subject: Shows the exact GitHub workflow that created the signature - Issuer: Confirms it came from GitHub Actions - integratedTime: Unix timestamp when signature was created - logIndex: Entry in the Rekor transparency log (publicly auditable)

Verification Failures

If verification fails, you'll see an error like:

Error: no matching signatures:

Do NOT use unverified images in production. This indicates: - The image was not signed by the official Bindy release workflow - The image may have been tampered with - The image may be a counterfeit

Verifying Binary Releases

Binary tarballs are signed with Cosign blob signing. Each release includes .bundle files containing the signature.

Download and Verify

# Download the binary tarball and signature bundle from GitHub Releases
VERSION="v0.8.0"
PLATFORM="linux-amd64"  # or linux-arm64, macos-amd64, macos-arm64, windows-amd64

# Download tarball
curl -LO "https://github.com/firestoned/bindy/releases/download/${VERSION}/bindy-${PLATFORM}.tar.gz"

# Download signature bundle
curl -LO "https://github.com/firestoned/bindy/releases/download/${VERSION}/bindy-${PLATFORM}.tar.gz.bundle"

# Verify the signature
cosign verify-blob \
  --bundle "bindy-${PLATFORM}.tar.gz.bundle" \
  --certificate-identity-regexp="$SIGNER" \
  --certificate-oidc-issuer="$ISSUER" \
  "bindy-${PLATFORM}.tar.gz"

Verification Success

If successful, you'll see:

Verified OK

You can now safely extract and use the binary:

tar xzf bindy-${PLATFORM}.tar.gz
./bindy --version

Automated Verification Script

Create a script to download and verify releases automatically:

#!/bin/bash
set -euo pipefail

VERSION="${1:-latest}"
PLATFORM="${2:-linux-amd64}"
SIGNER='^https://github\.com/firestoned/bindy/\.github/workflows/build\.yaml@refs/tags/'

if [ "$VERSION" = "latest" ]; then
  VERSION=$(curl -s https://api.github.com/repos/firestoned/bindy/releases/latest | grep tag_name | cut -d '"' -f 4)
fi

echo "Downloading Bindy $VERSION for $PLATFORM..."

# Download artifacts
curl -LO "https://github.com/firestoned/bindy/releases/download/${VERSION}/bindy-${PLATFORM}.tar.gz"
curl -LO "https://github.com/firestoned/bindy/releases/download/${VERSION}/bindy-${PLATFORM}.tar.gz.bundle"

# Verify signature
echo "Verifying signature..."
cosign verify-blob \
  --bundle "bindy-${PLATFORM}.tar.gz.bundle" \
  --certificate-identity-regexp="$SIGNER" \
  --certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
  "bindy-${PLATFORM}.tar.gz"

# Extract
echo "Extracting..."
tar xzf "bindy-${PLATFORM}.tar.gz"

echo "✓ Bindy $VERSION successfully verified and installed"
./bindy --version

Additional Security Verification

Check SHA256 Checksums

Every release includes a checksums.sha256 file with SHA256 hashes of all artifacts:

# Download checksums
curl -LO "https://github.com/firestoned/bindy/releases/download/${VERSION}/checksums.sha256"

# Verify the tarball checksum
sha256sum -c checksums.sha256 --ignore-missing

Inspect Rekor Transparency Log

All signatures are recorded in the public Rekor transparency log:

# Search for Bindy signatures
rekor-cli search --email noreply@github.com --rekor_server https://rekor.sigstore.dev

# Or use the web interface:
# https://search.sigstore.dev/?email=noreply@github.com

Verify SLSA Build L3 Provenance

Every release file and image has SLSA v1.0 Build L3 provenance from the isolated slsa-github-generator workflows. Verify it with slsa-verifier (make slsa-verifier-install):

# A tarball, install manifest or SBOM, against the release's provenance file
curl -LO "https://github.com/firestoned/bindy/releases/download/${VERSION}/${VERSION#v}.intoto.jsonl"
slsa-verifier verify-artifact "bindy-${PLATFORM}.tar.gz" \
  --provenance-path "${VERSION#v}.intoto.jsonl" \
  --source-uri github.com/firestoned/bindy \
  --source-tag "${VERSION}"

# An image: always by digest, never by tag
DIGEST=$(crane digest ghcr.io/firestoned/bindy:${VERSION})
slsa-verifier verify-image "ghcr.io/firestoned/bindy@${DIGEST}" \
  --source-uri github.com/firestoned/bindy \
  --source-tag "${VERSION}"

# The same checks, from a checkout of this repository
make verify-provenance ARTIFACT=bindy-${PLATFORM}.tar.gz VERSION=${VERSION}
make verify-image-provenance IMAGE=ghcr.io/firestoned/bindy@${DIGEST} VERSION=${VERSION}

Verify the SBOM

Each SBOM is a signed attestation bound to the artifact it describes, so you can prove the SBOM you read is the one generated for the binary or image you run:

# Binary: the SBOM attestation for this exact tarball
gh attestation verify "bindy-${PLATFORM}.tar.gz" --repo firestoned/bindy \
  --predicate-type https://cyclonedx.org/bom \
  --signer-workflow firestoned/bindy/.github/workflows/build.yaml

# Image: the attestation is also stored in GHCR next to the image
gh attestation verify "oci://ghcr.io/firestoned/bindy@${DIGEST}" --repo firestoned/bindy \
  --predicate-type https://cyclonedx.org/bom

# Or
make verify-sbom-attestation ARTIFACT=bindy-${PLATFORM}.tar.gz

The binary SBOMs list the operator's full Rust dependency tree. The image SBOMs list the base image's OS packages; Syft cannot see inside a Rust binary, so for the operator's own dependencies read the binary SBOM of the same release.

Kubernetes Deployment Verification

Three complementary layers, in the order most deployments adopt them:

  1. FluxCD (GitOps) — verify the deployment source (signed OCI artifact) before anything is applied.
  2. ValidatingAdmissionPolicy — enforce image provenance (trusted registries, immutable references) with zero extra controllers. Ships with bindy.
  3. Kyverno / sigstore policy-controller — cryptographic signature verification at pod admission (CEL cannot verify signatures, so this layer needs a verifying controller).

FluxCD (GitOps) Verification

Flux verifies Cosign signatures on the OCI sources it pulls (OCIRepository / HelmChart) — tampered or unsigned artifacts are never applied. Note Flux does not verify container-image signatures at pod admission; combine it with the layers below.

Wrap bindy's released manifests in an OCI artifact you sign, and let Flux refuse anything unsigned:

# One-time (or per bindy upgrade): package + sign the manifests you deploy
VERSION=v0.6.0
curl -sSLO https://github.com/firestoned/bindy/releases/download/${VERSION}/install.yaml
flux push artifact oci://ghcr.io/<your-org>/bindy-manifests:${VERSION} \
  --path=. --source="github.com/firestoned/bindy" --revision="${VERSION}"
cosign sign ghcr.io/<your-org>/bindy-manifests:${VERSION}
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
  name: bindy-manifests
  namespace: flux-system
spec:
  interval: 10m
  url: oci://ghcr.io/<your-org>/bindy-manifests
  ref:
    tag: v0.6.0
  verify:
    provider: cosign
    # Keyless: pin the signing identity. For artifacts signed in YOUR CI,
    # match your workflow; for interactive `cosign sign`, match your OIDC
    # identity via a secretRef / matchOIDCIdentity as appropriate.
    matchOIDCIdentity:
      - issuer: "https://token.actions.githubusercontent.com"
        subject: "https://github.com/<your-org>/<your-repo>/.github/workflows/.*"
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: bindy
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: OCIRepository
    name: bindy-manifests
  path: ./
  prune: true

With spec.verify set, Flux marks the source SourceVerifiedFailed and refuses to reconcile if the signature is missing or does not match the pinned identity.

ValidatingAdmissionPolicy (no extra controller)

Bindy ships an image-provenance policy — deploy/admission-policies/15-bindy-image-provenance-policy.yaml — enforced by the API server itself (Kubernetes 1.30+, CEL; no webhook or controller to operate):

  • bindcar sidecar overrides must come from ghcr.io/firestoned/
  • BIND9 operand overrides must be internetsystemsconsortium/bind9 or a ghcr.io/firestoned/ mirror
  • every override must be immutable: digest-pinned (@sha256:) or a named non-:latest tag
kubectl apply -f deploy/admission-policies/
# or the combined release artifact:
kubectl apply -f https://github.com/firestoned/bindy/releases/latest/download/admission-policies.yaml

Scope: CEL cannot perform cryptographic verification — this policy pins where images come from and that references are immutable, not who signed them. For signature verification at admission, add Kyverno or the sigstore policy-controller below.

Kyverno Policy Example

When deploying to Kubernetes, use policy-controller or Kyverno to enforce signature verification:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-bindy-images
spec:
  validationFailureAction: enforce
  background: false
  rules:
    - name: verify-bindy-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/firestoned/bindy*"
          attestors:
            - entries:
                - keyless:
                    # Releases are signed by the consolidated build.yaml
                    # workflow. Releases published before the consolidation
                    # were signed by the former release.yaml identity.
                    subject: "https://github.com/firestoned/bindy/.github/workflows/build.yaml@refs/tags/*"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev

This policy ensures: - Only signed Bindy images can run in the cluster - Signatures must come from the official release workflow - Signatures are verified against the Rekor transparency log

Troubleshooting

"Error: no matching signatures"

Cause: Image/artifact is not signed or signature doesn't match the identity.

Solution: - Verify you're using an official release from ghcr.io/firestoned/bindy* - Check the tag/version exists on the GitHub releases page - Ensure you're not using a locally-built image

"Error: unable to verify bundle"

Cause: Signature bundle is corrupted or doesn't match the artifact.

Solution: - Re-download the artifact and bundle - Verify the SHA256 checksum matches checksums.sha256 - Report the issue if checksums match but verification fails

"Error: fetching bundle: context deadline exceeded"

Cause: Network issue connecting to Sigstore services.

Solution: - Check your internet connection - Verify you can reach https://rekor.sigstore.dev and https://fulcio.sigstore.dev - Try again with increased timeout: COSIGN_TIMEOUT=60s cosign verify ...

Security Contact

If you discover a security issue with signed releases:

  • DO NOT open a public GitHub issue
  • Report to: security@firestoned.io
  • Include: artifact name, version, verification output, and steps to reproduce

See SECURITY.md for our security policy and vulnerability disclosure process.

SPDX License Headers

All Bindy source files include SPDX license identifiers for automated license compliance tracking.

What is SPDX?

SPDX (Software Package Data Exchange) is an ISO standard (ISO/IEC 5962:2021) for communicating software license information. SPDX identifiers enable:

  • Automated SBOM generation: Tools like cargo-cyclonedx detect licenses automatically
  • License compliance auditing: Verify no GPL contamination in this Apache-2.0-licensed project
  • Supply chain transparency: Clear license identification at file granularity
  • Tooling integration: GitHub, Snyk, Trivy, and other tools recognize SPDX headers

Required Header Format

All source files MUST include SPDX headers in the first 10 lines:

Rust files (.rs):

// Copyright (c) 2025 Erick Bourgeois, firestoned
// SPDX-License-Identifier: Apache-2.0

Shell scripts (.sh, .bash):

#!/usr/bin/env bash
# Copyright (c) 2025 Erick Bourgeois, firestoned
# SPDX-License-Identifier: Apache-2.0

Makefiles (Makefile, *.mk):

# Copyright (c) 2025 Erick Bourgeois, firestoned
# SPDX-License-Identifier: Apache-2.0

GitHub Actions workflows (.yaml, .yml):

# Copyright (c) 2025 Erick Bourgeois, firestoned
# SPDX-License-Identifier: Apache-2.0
name: My Workflow

Automated Verification

Bindy enforces SPDX headers via CI/CD:

Workflow: .github/workflows/license-check.yaml

Checks: - All Rust files (.rs) - All Shell scripts (.sh, .bash) - All Makefiles (Makefile, *.mk) - All GitHub Actions workflows (.yaml, .yml)

Enforcement: - Runs on every pull request - Runs on every push to main - Pull requests fail if any source files lack SPDX headers - Provides clear error messages with examples for missing headers

Output Example:

✅ All 347 source files have SPDX license headers

File types checked:
  - Rust files (.rs)
  - Shell scripts (.sh, .bash)
  - Makefiles (Makefile, *.mk)
  - GitHub Actions workflows (.yaml, .yml)

License: Apache-2.0

Bindy is licensed under the Apache License 2.0, a permissive license with an express patent grant.

Permissions: - ✅ Commercial use - ✅ Modification - ✅ Distribution - ✅ Private use

Conditions: - 📋 Include license text and NOTICE file - 📋 State significant changes

Limitations: - ❌ No liability - ❌ No warranty

Full license text: LICENSE

Compliance Evidence

SOX 404 (Sarbanes-Oxley): - Control: License compliance and intellectual property tracking - Evidence: All source files tagged with SPDX identifiers, automated verification - Audit Trail: Git history shows when SPDX headers were added

PCI-DSS 6.4.6 (Payment Card Industry): - Requirement: Code review and approval processes - Evidence: SPDX verification blocks unapproved code (missing headers) from merging - Automation: CI/CD enforces license compliance before code review

SLSA / SBOM (Supply Chain Security): - Requirement: Dependency inventory and license data for every shipped artifact - Evidence: SPDX headers give the SBOMs machine-readable license data for bindy's own crates; provenance itself is covered under Verify SLSA Build L3 Provenance - Transparency: Every dependency's license is machine-readable


References