diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b36af9..c236e51 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,9 +12,13 @@ on: permissions: contents: write packages: write + # Required for keyless cosign signing via GitHub OIDC + id-token: write env: GO_VERSION: "1.25" + REGISTRY: ghcr.io + IMAGE: ghcr.io/optiqor/kerno jobs: release: @@ -50,14 +54,77 @@ jobs: - name: Set up QEMU (multi-arch) uses: docker/setup-qemu-action@v4 + - name: Install cosign + uses: sigstore/cosign-installer@v3 + with: + cosign-release: "v2.2.4" + + - name: Install Syft + uses: anchore/sbom-action/download-syft@v0 + - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v7 + id: goreleaser + uses: goreleaser/goreleaser-action@v2 with: distribution: goreleaser version: "~> v2" args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract image digest (versioned tag) + id: digest-tag + run: | + TAG="${{ github.ref_name }}" + DIGEST=$(docker buildx imagetools inspect "${{ env.IMAGE }}:${TAG}" \ + --format '{{json .Manifest}}' | jq -r '.digest') + echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + + - name: Sign container image (versioned tag) + env: + COSIGN_EXPERIMENTAL: "1" + run: | + cosign sign --yes \ + "${{ env.IMAGE }}:${{ steps.digest-tag.outputs.tag }}@${{ steps.digest-tag.outputs.digest }}" + + - name: Extract image digest (latest tag) + id: digest-latest + run: | + DIGEST=$(docker buildx imagetools inspect "${{ env.IMAGE }}:latest" \ + --format '{{json .Manifest}}' | jq -r '.digest') + echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" + + - name: Sign container image (latest tag) + env: + COSIGN_EXPERIMENTAL: "1" + run: | + cosign sign --yes \ + "${{ env.IMAGE }}:latest@${{ steps.digest-latest.outputs.digest }}" + + - name: Generate SBOM (container image) + uses: anchore/sbom-action@v0 + with: + image: "${{ env.IMAGE }}:${{ steps.digest-tag.outputs.tag }}" + format: cyclonedx-json + output-file: "${{ github.workspace }}/kerno_${{ steps.digest-tag.outputs.tag }}_sbom.cyclonedx.json" + artifact-name: "kerno_${{ steps.digest-tag.outputs.tag }}_sbom.cyclonedx.json" + + - name: Attest SBOM to image (versioned tag) + env: + COSIGN_EXPERIMENTAL: "1" + run: | + cosign attest --yes \ + --predicate "${{ github.workspace }}/kerno_${{ steps.digest-tag.outputs.tag }}_sbom.cyclonedx.json" \ + --type cyclonedx \ + "${{ env.IMAGE }}:${{ steps.digest-tag.outputs.tag }}@${{ steps.digest-tag.outputs.digest }}" + + - name: Upload SBOM to GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: "${{ github.workspace }}/kerno_${{ steps.digest-tag.outputs.tag }}_sbom.cyclonedx.json" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} snapshot: name: Snapshot build (PR) @@ -79,13 +146,23 @@ jobs: sudo apt-get install -y --no-install-recommends \ clang llvm libbpf-dev linux-headers-generic + - name: Install cosign + uses: sigstore/cosign-installer@v3 + with: + cosign-release: "v2.2.4" + + - name: Install Syft + uses: anchore/sbom-action/download-syft@v0 + - name: Run GoReleaser snapshot - uses: goreleaser/goreleaser-action@v7 + uses: goreleaser/goreleaser-action@v2 with: distribution: goreleaser version: "~> v2" - args: release --snapshot --clean + args: release --snapshot --clean --skip=sign env: + COSIGN_EXPERIMENTAL: "" + GORELEASER_SKIP: sign GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload snapshot packages diff --git a/.goreleaser.yml b/.goreleaser.yml index 23f908b..ec6aabc 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,6 +1,5 @@ # Copyright 2026 Optiqor contributors # SPDX-License-Identifier: Apache-2.0 -# https://goreleaser.com/customization/ version: 2 @@ -9,9 +8,6 @@ project_name: kerno before: hooks: - go mod tidy - # Generate bpf2go bindings (*_bpfel.go) and rewrite their build tag - # so the `ebpf` tag selects them over the no-clang stubs in gen_stub.go. - # The release workflow installs clang + libbpf-dev before invoking goreleaser. - make generate builds: @@ -27,7 +23,6 @@ builds: - arm64 flags: - -trimpath - # `ebpf` tag selects the bpf2go-generated bindings over the no-clang stubs. tags: - ebpf ldflags: @@ -50,6 +45,32 @@ archives: checksum: name_template: "checksums.txt" +signs: + # skip signing in snapshot/PR builds + - cmd: cosign + signature: "${artifact}.sig" + certificate: "${artifact}.pem" + args: + - sign-blob + - --yes + - --output-signature=${artifact}.sig + - --output-certificate=${artifact}.pem + - "${artifact}" + artifacts: checksum + output: true + +sboms: + - id: binary-sbom + artifacts: binary + documents: + - "{{ .ArtifactName }}_sbom.cyclonedx.json" + args: + - "$artifact" + - "--file" + - "$document" + - "--output" + - "cyclonedx-json" + changelog: sort: asc use: github diff --git a/README.md b/README.md index ba6ae45..a84ccf0 100644 --- a/README.md +++ b/README.md @@ -715,6 +715,64 @@ In another shell, `sudo kerno doctor` will catch the induced incident. --- +## Verifying Release Artifacts + +Every Kerno release is signed using [Sigstore](https://docs.sigstore.dev/cosign/signing/overview/) keyless signing — no private key to manage or trust. + +### Verify a container image + +```bash +cosign verify ghcr.io/optiqor/kerno:v0.1.0 \ + --certificate-identity-regexp '^https://github\.com/optiqor/kerno/\.github/workflows/release\.yml@refs/tags/v' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com +``` + +### Verify binary checksums + +```bash +VERSION=v0.1.0 +BASE=https://github.com/optiqor/kerno/releases/download/${VERSION} + +curl -fsSL ${BASE}/checksums.txt -o checksums.txt +curl -fsSL ${BASE}/checksums.txt.sig -o checksums.txt.sig +curl -fsSL ${BASE}/checksums.txt.pem -o checksums.txt.pem + +cosign verify-blob checksums.txt \ + --signature checksums.txt.sig \ + --certificate checksums.txt.pem \ + --certificate-identity-regexp '^https://github\.com/optiqor/kerno/\.github/workflows/release\.yml@refs/tags/v' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com + +curl -fsSL ${BASE}/kerno_${VERSION}_linux_amd64.tar.gz -o kerno.tar.gz +sha256sum --check --ignore-missing checksums.txt +``` + +### Inspect the SBOM + +```bash +VERSION=v0.1.0 + +# Download SBOM +curl -fsSL https://github.com/optiqor/kerno/releases/download/${VERSION}/kerno_${VERSION}_sbom.cyclonedx.json \ + | jq '.metadata.component, [.components[].name]' + +# Verify SBOM attestation +cosign verify-attestation ghcr.io/optiqor/kerno:${VERSION} \ + --type cyclonedx \ + --certificate-identity-regexp '^https://github\.com/optiqor/kerno/\.github/workflows/release\.yml@refs/tags/v' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + | jq '.payload | @base64d | fromjson | .predicate.metadata' +``` + +### Install cosign + +```bash +brew install cosign # macOS +go install github.com/sigstore/cosign/v2/cmd/cosign@latest # Go +``` + +--- + ## Contributing Contributions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for: diff --git a/SECURITY.md b/SECURITY.md index 650bdd8..6fcf233 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -41,6 +41,78 @@ If you discover a security vulnerability in Kerno, please report it responsibly: | Previous minor | ✅ (security fixes only) | | Older | ❌ | +--- + +## Release Artifact Signing & Verification + +Kerno uses **keyless Sigstore signing** for all release artifacts. No private key — signing is performed by the GitHub Actions OIDC identity and recorded in the public [Rekor](https://rekor.sigstore.dev) transparency log. + +### Signing identity + +| Field | Value | +|-------|-------| +| **OIDC issuer** | `https://token.actions.githubusercontent.com` | +| **Certificate identity (regexp)** | `^https://github\.com/optiqor/kerno/\.github/workflows/release\.yml@refs/tags/v` | +| **Transparency log** | Sigstore / Rekor (public instance) | +| **Key management** | Keyless — no private key; GitHub OIDC ephemeral cert | + +### What is signed + +| Artifact | How | Where | +|----------|-----|-------| +| Container image (`:v*` tag) | `cosign sign` | OCI registry + Rekor | +| Container image (`:latest` tag) | `cosign sign` | OCI registry + Rekor | +| `checksums.txt` (covers all binaries & archives) | `cosign sign-blob` | Release assets (`.sig` + `.pem`) | +| CycloneDX SBOM | `cosign attest --type cyclonedx` | OCI registry + Rekor | + +### Verify a container image + +```bash +cosign verify ghcr.io/optiqor/kerno:v0.1.0 \ + --certificate-identity-regexp '^https://github\.com/optiqor/kerno/\.github/workflows/release\.yml@refs/tags/v' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com +``` + +### Verify binary checksums + +```bash +VERSION=v0.1.0 +BASE=https://github.com/optiqor/kerno/releases/download/${VERSION} + +curl -fsSL ${BASE}/checksums.txt -o checksums.txt +curl -fsSL ${BASE}/checksums.txt.sig -o checksums.txt.sig +curl -fsSL ${BASE}/checksums.txt.pem -o checksums.txt.pem + +cosign verify-blob checksums.txt \ + --signature checksums.txt.sig \ + --certificate checksums.txt.pem \ + --certificate-identity-regexp '^https://github\.com/optiqor/kerno/\.github/workflows/release\.yml@refs/tags/v' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com + +curl -fsSL ${BASE}/kerno_${VERSION}_linux_amd64.tar.gz -o kerno.tar.gz +sha256sum --check --ignore-missing checksums.txt +``` + +### Verify the SBOM attestation + +```bash +cosign verify-attestation ghcr.io/optiqor/kerno:v0.1.0 \ + --type cyclonedx \ + --certificate-identity-regexp '^https://github\.com/optiqor/kerno/\.github/workflows/release\.yml@refs/tags/v' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + | jq '.payload | @base64d | fromjson | .predicate.metadata' +``` + +### Reporting a tampered artifact + +Agar `cosign verify` fail ho kisi official release tag pe — **artifact use mat karo** aur turant report karo: + +1. Email karo **team.optiqor@gmail.com** — subject: `[SECURITY] Possible tampered artifact` +2. `cosign verify` ka poora output aur exact image digest ya file hash include karo +3. We will investigate and post a GitHub Security Advisory if confirmed + +--- + ## Security Considerations for Kerno Kerno runs with elevated privileges (root or `CAP_BPF` + `CAP_PERFMON` + `CAP_SYS_PTRACE`) to load eBPF programs into the kernel. This means: @@ -53,12 +125,12 @@ Kerno runs with elevated privileges (root or `CAP_BPF` + `CAP_PERFMON` + `CAP_SY ## Disclosure Policy -- We follow [coordinated vulnerability disclosure](https://vuls.cert.org/confluence/display/Wiki/Vulnerability+Disclosure+Policy). +- We follow [coordinated vulnerability disclosure](https://certcc.github.io/CERT-Guide-to-CVD/). - We will credit reporters in security advisories (unless anonymity is requested). - We use GitHub Security Advisories for publishing fixes. ## Contact -- **Security reports:** team.optiqor@gmail.com +- **Security reports:** team.optiqor@gmail.com - **General questions:** GitHub Discussions -- **Maintainer:** Shivam Kumar (@btwshivam) +- **Maintainer:** Shivam Kumar (@btwshivam) \ No newline at end of file