Skip to content

CD — digipin-rs

CD — digipin-rs #43

Workflow file for this run

# Publish / CD for digipin-rs (library)
# - Keyless cosign via OIDC (id-token: write)
# - Verified GitHub actions only (actions/*, github/*, sigstore/cosign-action); Syft pinned/verified
# - Produces dual SBOMs (CycloneDX + SPDX), validates SBOM, creates GitHub SLSA attestation,
# cosign attestation/sign (keyless via sigstore action), uploads release assets, then publishes to crates.io
#
# Required repository secrets / variables (set these before using the workflow):
# - CARGO_REGISTRY_TOKEN : (store as an Environment secret in 'production') crates.io API token
# - SYFT_VERSION : pinned Syft version (e.g. "0.71.0")
# - SYFT_SHA256 : sha256 of the syft tarball you will download
# - TEST_MODE : set "true" for test/draft-only
name: CD — digipin-rs
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: publish-digipin-rs-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
name: Publish digipin-rs
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
id-token: write
attestations: write
environment: production
env:
TEST_MODE: ${{ vars.TEST_MODE }}
SYFT_VERSION: ${{ vars.SYFT_VERSION }}
SYFT_SHA256: ${{ vars.SYFT_SHA256 }}
defaults:
run:
shell: bash
steps:
- name: Checkout digipin-rs
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Inspect checkout (debug)
run: |
echo "GITHUB_REF=${GITHUB_REF}"
echo "GITHUB_REF_NAME=${GITHUB_REF_NAME:-${GITHUB_REF##*/}}"
echo "GITHUB_SHA=${GITHUB_SHA}"
ls -la
- name: Set derived variables
id: set_vars
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME:-${GITHUB_REF##*/}}"
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-8)
RUN_ID="${GITHUB_RUN_NUMBER:-0}"
echo "TAG=${TAG}" >> $GITHUB_ENV
echo "SHORT_SHA=${SHORT_SHA}" >> $GITHUB_ENV
echo "RUN_ID=${RUN_ID}" >> $GITHUB_ENV
echo "GIT_OWNER=${GITHUB_REPOSITORY_OWNER}" >> $GITHUB_ENV
- name: Validate tag matches Cargo.toml version
id: check_tag_version
run: |
set -euo pipefail
TAG="${TAG:-${GITHUB_REF_NAME:-${GITHUB_REF##*/}}}"
TAG_NORMAL="${TAG#refs/tags/}"
CRATE_VERSION=$(sed -n 's/^version *= *"\(.*\)".*/\1/p' Cargo.toml | head -n1 || true)
if [ -z "$CRATE_VERSION" ]; then
echo "ERROR: Could not find version in Cargo.toml"
exit 1
fi
if [ "v${CRATE_VERSION}" != "$TAG_NORMAL" ] && [ "${CRATE_VERSION}" != "$TAG_NORMAL" ]; then
echo "ERROR: Tag ($TAG_NORMAL) does not match Cargo.toml version ($CRATE_VERSION)"
exit 2
fi
echo "Tag matches Cargo.toml version: $CRATE_VERSION"
echo "CRATE_VERSION=${CRATE_VERSION}" >> $GITHUB_ENV
- name: Install Rust toolchain
run: |
set -euo pipefail
if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
fi
rustup toolchain install stable --profile minimal || true
rustup default stable
rustc --version
cargo --version
- name: Cache cargo registry & build
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-digipin-rs-cd-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-digipin-rs-cd-
- name: Produce .crate (cargo package)
run: |
set -euo pipefail
echo "Packaging crate (Cargo.toml version ${CRATE_VERSION})..."
cargo package
mkdir -p artifacts
CRATE_PATH=$(ls target/package/*.crate | head -n1)
if [ -z "$CRATE_PATH" ]; then
echo "ERROR: cargo package did not produce a .crate file"
exit 1
fi
echo "CRATE_PATH=${CRATE_PATH}" >> $GITHUB_ENV
CRATE_FILE=$(basename "$CRATE_PATH")
echo "CRATE_FILE=${CRATE_FILE}" >> $GITHUB_ENV
cp "$CRATE_PATH" artifacts/
echo "Copied crate to artifacts/"
echo "Listing artifacts/:"
ls -la artifacts
- name: Create deterministic checksum for artifact
run: |
set -euo pipefail
mkdir -p artifacts
CRATE_FILE="${CRATE_FILE}"
sha256sum "artifacts/${CRATE_FILE}" | awk '{print $1 " " $2}' > "artifacts/${CRATE_FILE}.sha256"
echo "Checksum written to artifacts/${CRATE_FILE}.sha256"
- name: Install pinned Syft and verify checksum
id: install_syft
run: |
set -euo pipefail
if [ -z "${SYFT_VERSION:-}" ] || [ -z "${SYFT_SHA256:-}" ]; then
echo "SYFT_VERSION and SYFT_SHA256 must be set in production environment as environment variables"
exit 1
fi
ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then TARCH="linux_amd64"; else TARCH="$ARCH"; fi
SYFT_TGZ_URL="https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_${TARCH}.tar.gz"
echo "Downloading Syft from $SYFT_TGZ_URL"
curl --proto '=https' --tlsv1.2 -fsSL --retry 3 "$SYFT_TGZ_URL" -o /tmp/syft.tar.gz
DOWN_SHA=$(sha256sum /tmp/syft.tar.gz | awk '{print $1}')
if [ "$DOWN_SHA" != "$SYFT_SHA256" ]; then
echo "Syft checksum mismatch. expected=${SYFT_SHA256}, got=${DOWN_SHA}"
exit 2
fi
sudo mkdir -p /usr/local/bin
sudo tar -xzf /tmp/syft.tar.gz -C /usr/local/bin
/usr/local/bin/syft --version
- name: Generate dual SBOMs (CycloneDX + SPDX) for the .crate
run: |
set -euo pipefail
mkdir -p sbom
CRATE_FILE="${CRATE_FILE}"
/usr/local/bin/syft "artifacts/${CRATE_FILE}" -o cyclonedx-json > sbom/sbom-cyclonedx.json
/usr/local/bin/syft "artifacts/${CRATE_FILE}" -o spdx-json > sbom/sbom-spdx.json
echo "Generated SBOMs:"
ls -la sbom || true
cat sbom/sbom-cyclonedx.json
cat sbom/sbom-spdx.json
test -s sbom/sbom-cyclonedx.json
test -s sbom/sbom-spdx.json
- name: Attest provenance for crate
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0
with:
subject-path: |
artifacts/${{ env.CRATE_FILE }}
artifacts/${{ env.CRATE_FILE }}.sha256
- name: Attest provenance for SBOMs
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0
with:
subject-path: |
sbom/sbom-cyclonedx.json
sbom/sbom-spdx.json
- name: Check if GitHub release already exists
id: check_release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if gh release view "$TAG" >/dev/null 2>&1; then
echo "RELEASE_EXISTS=true" >> "$GITHUB_ENV"
IS_DRAFT=$(gh release view "$TAG" --json isDraft -q '.isDraft')
echo "RELEASE_IS_DRAFT=${IS_DRAFT}" >> "$GITHUB_ENV"
echo "Release exists (draft=${IS_DRAFT})"
else
echo "RELEASE_EXISTS=false" >> "$GITHUB_ENV"
echo "Release does not exist"
fi
- name: Abort if release already published
if: env.RELEASE_EXISTS == 'true' && env.RELEASE_IS_DRAFT != 'true'
run: |
echo "ERROR: Release $TAG already exists and is published."
echo "Refusing to modify an immutable release."
exit 1
- name: Create GitHub release
if: env.RELEASE_EXISTS != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
START_TAG: ${{ vars.START_TAG }}
run: |
set -euo pipefail
gh release create "$TAG" \
--draft \
--generate-notes \
--title "$TAG" \
${START_TAG:+--notes-start-tag "$START_TAG"} \
- name: Upload release assets
if: env.TEST_MODE != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
gh release upload "$TAG" \
artifacts/${CRATE_FILE} \
artifacts/${CRATE_FILE}.sha256 \
sbom/sbom-cyclonedx.json \
sbom/sbom-spdx.json \
--clobber
- name: Publish GitHub release
if: env.TEST_MODE != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if [ "${RELEASE_IS_DRAFT:-true}" = "true" ]; then
gh release edit "$TAG" --draft=false
echo "Release published"
else
echo "Release already published — skipping"
fi
- name: Verify SLSA provenance (self-check)
if: env.TEST_MODE != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "Verifying SLSA provenance for ${CRATE_FILE}"
gh attestation verify "artifacts/${CRATE_FILE}" \
--repo "${GITHUB_REPOSITORY}"
echo "SLSA provenance verification passed"
- name: Cleanup build artifacts before publish
if: env.TEST_MODE != 'true'
run: |
set -euo pipefail
rm -rf artifacts sbom
- name: Enforce clean git tree before publish
if: env.TEST_MODE != 'true'
run: |
set -euo pipefail
if [ -n "$(git status --porcelain)" ]; then
echo "ERROR: working tree is dirty before cargo publish"
git status --porcelain
exit 1
fi
- name: Publish crate to crates.io
if: env.TEST_MODE != 'true'
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
if [ -z "${CARGO_REGISTRY_TOKEN:-}" ]; then
echo "CARGO_REGISTRY_TOKEN not set; skipping publish"
exit 1
fi
cargo publish
echo "cargo publish completed"
- name: Finalize — print verification instructions
run: |
set -euo pipefail
echo "Release completed for tag ${TAG}, artifact artifacts/${CRATE_FILE}"
echo "TEST_MODE=${TEST_MODE}"
echo "If TEST_MODE=true the release was created as a DRAFT and crates.io publish was skipped."
echo "Verification commands (local):"
echo " sha256sum ${CRATE_FILE} && cat ${CRATE_FILE}.sha256"
echo "Verify provenance (SLSA L3):"
echo " slsa-verifier verify-artifact ${CRATE_FILE} \\"
echo " --source-uri github.com/${GITHUB_REPOSITORY} \\"
echo " --source-tag ${TAG}"