name: Release
on:
release:
types:
- published
permissions:
contents: write
packages: write
security-events: write id-token: write
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
K8S_OPENAPI_ENABLED_VERSION: "1.32"
jobs:
license-check:
name: Verify SPDX License Headers
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Check license headers
uses: firestoned/github-actions/security/license-check@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
copyright-holder: "Erick Bourgeois, firestoned"
license-id: "MIT"
verify-commits:
name: Verify Signed Commits
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with:
fetch-depth: 0
- name: Verify release tag commit is signed
uses: firestoned/github-actions/security/verify-signed-commits@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
github-token: ${{ secrets.GITHUB_TOKEN }}
verify-mode: release
extract-version:
name: Extract Version from Tag
runs-on: ubuntu-latest
needs: [license-check, verify-commits]
outputs:
version: ${{ steps.version.outputs.version }}
tag_name: ${{ steps.version.outputs.tag-name }}
short-sha: ${{ steps.version.outputs.short-sha }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Extract version
id: version
run: |
TAG_NAME="${{ github.event.release.tag_name }}"
VERSION="${TAG_NAME#v}"
SHORT_SHA="${{ github.sha }}"
SHORT_SHA="${SHORT_SHA:0:7}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "tag-name=${TAG_NAME}" >> $GITHUB_OUTPUT
echo "short-sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "Extracted version: ${VERSION} from tag: ${TAG_NAME}"
build:
name: Build - ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
needs: extract-version
strategy:
fail-fast: false
matrix:
platform:
- name: Linux x86_64
os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: bindcar-linux-amd64
binary_name: bindcar
- name: Linux ARM64
os: ubuntu-latest
target: aarch64-unknown-linux-gnu
artifact_name: bindcar-linux-arm64
binary_name: bindcar
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Update Cargo.toml version
run: |
sed -i 's/^version = ".*"/version = "${{ needs.extract-version.outputs.version }}"/' Cargo.toml
echo "Updated Cargo.toml to version ${{ needs.extract-version.outputs.version }}"
grep '^version = ' Cargo.toml
- name: Setup Rust build environment
uses: firestoned/github-actions/rust/setup-rust-build@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
target: ${{ matrix.platform.target }}
cache-key: release-${{ needs.extract-version.outputs.version }}
- name: Build binary
uses: firestoned/github-actions/rust/build-binary@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
target: ${{ matrix.platform.target }}
- name: Generate SBOM
uses: firestoned/github-actions/rust/generate-sbom@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
target: ${{ matrix.platform.target }}
- name: Upload binary and SBOM artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with:
name: ${{ matrix.platform.artifact_name }}
path: |
target/${{ matrix.platform.target }}/release/${{ matrix.platform.binary_name }}
*.cdx.*
docker-release:
name: Build and Push Docker Image - ${{ matrix.variant.name }}
runs-on: ubuntu-latest
needs: [extract-version, build]
permissions:
contents: write
packages: write
id-token: write strategy:
fail-fast: false
matrix:
variant:
- name: Chainguard
dockerfile: docker/Dockerfile.chainguard
suffix: ""
description: "BIND9 RNDC API Server - Chainguard Zero-CVE"
- name: Distroless
dockerfile: docker/Dockerfile
suffix: "-distroless"
description: "BIND9 RNDC API Server - Google Distroless"
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Prepare Docker binaries
uses: ./.github/actions/prepare-docker-binaries
- name: Setup Docker environment
uses: firestoned/github-actions/docker/setup-docker@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}${{ matrix.variant.suffix }},value=${{ needs.extract-version.outputs.version }}
type=semver,pattern={{major}}.{{minor}}${{ matrix.variant.suffix }},value=${{ needs.extract-version.outputs.version }}
type=semver,pattern={{major}}${{ matrix.variant.suffix }},value=${{ needs.extract-version.outputs.version }}
type=raw,value=${{ needs.extract-version.outputs.tag_name }}${{ matrix.variant.suffix }}
flavor: |
latest=false
- name: Build and push Docker image (${{ matrix.variant.name }})
id: docker_build
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with:
context: .
file: ${{ matrix.variant.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: |
${{ steps.meta.outputs.labels }}
org.opencontainers.image.description=${{ matrix.variant.description }}
build-args: |
VERSION=${{ needs.extract-version.outputs.version }}
cache-from: type=gha,scope=${{ matrix.variant.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.variant.name }}
platforms: linux/amd64,linux/arm64
sbom: true
provenance: true
- name: Sign container image with Cosign
uses: firestoned/github-actions/security/cosign-sign@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
image-digest: ${{ steps.docker_build.outputs.digest }}
image-tags: ${{ needs.extract-version.outputs.tag_name }}${{ matrix.variant.suffix }}
registry: ghcr.io
repository: ${{ github.repository }}
- name: Generate Docker SBOM for ${{ matrix.variant.name }}
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 with:
image: ghcr.io/${{ github.repository }}:${{ needs.extract-version.outputs.tag_name }}${{ matrix.variant.suffix }}
format: cyclonedx-json
output-file: docker-sbom-${{ matrix.variant.name }}.json
- name: Upload Docker SBOM as artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with:
name: docker-sbom-${{ matrix.variant.name }}
path: docker-sbom-${{ matrix.variant.name }}.json
sign-artifacts:
name: Sign Binary Artifacts
runs-on: ubuntu-latest
needs: [build, extract-version]
permissions:
id-token: write strategy:
fail-fast: false
matrix:
platform:
- name: Linux x86_64
artifact_name: bindcar-linux-amd64
- name: Linux ARM64
artifact_name: bindcar-linux-arm64
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Download binary artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
name: ${{ matrix.platform.artifact_name }}
path: ./artifacts/${{ matrix.platform.artifact_name }}
- name: Create tarball for signing
run: |
cd artifacts/${{ matrix.platform.artifact_name }}
# Find the binary (may be in nested directory)
BINARY=$(find . -type f -name "bindcar" | head -1)
if [ -z "$BINARY" ]; then
echo "ERROR: bindcar binary not found in artifacts/${{ matrix.platform.artifact_name }}"
find . -ls
exit 1
fi
echo "Found binary at: $BINARY"
chmod +x "$BINARY"
tar czf ${{ matrix.platform.artifact_name }}.tar.gz -C "$(dirname "$BINARY")" "$(basename "$BINARY")"
ls -lh ${{ matrix.platform.artifact_name }}.tar.gz
- name: Sign binary tarball with Cosign
uses: firestoned/github-actions/security/cosign-sign@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
artifact-path: artifacts/${{ matrix.platform.artifact_name }}/${{ matrix.platform.artifact_name }}.tar.gz
- name: Upload signed artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with:
name: ${{ matrix.platform.artifact_name }}-signed
path: |
artifacts/${{ matrix.platform.artifact_name }}/${{ matrix.platform.artifact_name }}.tar.gz
artifacts/${{ matrix.platform.artifact_name }}/${{ matrix.platform.artifact_name }}.tar.gz.bundle
generate-provenance-subjects:
name: Generate SLSA Provenance Subjects
runs-on: ubuntu-latest
needs: [sign-artifacts, extract-version]
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
steps:
- name: Download signed artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
pattern: bindcar-linux-*-signed
path: ./artifacts
merge-multiple: true
- name: Generate hashes for SLSA provenance
id: hash
run: |
cd artifacts
# Generate SHA256 hashes for all signed tarballs
sha256sum *.tar.gz > checksums.txt
cat checksums.txt
echo "hashes=$(cat checksums.txt | base64 -w0)" >> "$GITHUB_OUTPUT"
slsa-provenance:
name: Generate SLSA Provenance
needs: [generate-provenance-subjects, extract-version]
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
with:
base64-subjects: "${{ needs.generate-provenance-subjects.outputs.hashes }}"
upload-assets: true
provenance-name: "${{ needs.extract-version.outputs.version }}.intoto.jsonl"
upload-release-assets:
name: Upload Release Assets
runs-on: ubuntu-latest
needs: [build, docker-release, sign-artifacts, slsa-provenance]
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
path: ./artifacts
- name: Organize release artifacts
run: |
cd artifacts
mkdir -p release sboms signatures provenance
# Copy signed tarballs and signature bundles
for dir in bindcar-linux-*-signed; do
if [ -d "$dir" ]; then
platform="${dir%-signed}"
echo "Processing signed artifacts for $platform..."
# Copy tarball
if compgen -G "$dir/*.tar.gz" > /dev/null; then
cp "$dir"/*.tar.gz release/
fi
# Copy signature bundle
if compgen -G "$dir/*.tar.gz.bundle" > /dev/null; then
cp "$dir"/*.tar.gz.bundle signatures/
fi
fi
done
# Collect all SBOM files (using default cargo-cyclonedx naming)
for dir in bindcar-linux-amd64 bindcar-linux-arm64; do
if [ -d "$dir" ]; then
# Find any .cdx.json files (cargo-cyclonedx default naming)
if compgen -G "$dir/*.cdx.json" > /dev/null; then
cp "$dir"/*.cdx.json "sboms/${dir}-sbom.json"
fi
fi
done
# Copy Docker SBOMs for both variants
for variant in Chainguard Distroless; do
if [ -d "docker-sbom-${variant}" ] && [ -f "docker-sbom-${variant}/docker-sbom-${variant}.json" ]; then
cp "docker-sbom-${variant}/docker-sbom-${variant}.json" "sboms/docker-sbom-${variant}.json"
fi
done
# Copy SLSA provenance (generated by slsa-github-generator)
if compgen -G "*.intoto.jsonl" > /dev/null; then
# Find and copy the actual .intoto.jsonl file from within the directory
find . -name "*.intoto.jsonl" -type f -exec cp {} provenance/ \;
fi
# Generate SHA256 checksums for all release artifacts
cd release
sha256sum *.tar.gz > checksums.sha256
cd ../sboms
sha256sum *.json >> ../release/checksums.sha256
cd ../signatures
sha256sum *.bundle >> ../release/checksums.sha256
cd ../provenance
if compgen -G "*.intoto.jsonl" > /dev/null; then
sha256sum *.intoto.jsonl >> ../release/checksums.sha256
fi
echo "Release artifacts prepared:"
ls -lh ../release/
echo "Provenance files:"
ls -lh ../provenance/ || echo "No provenance files found"
- name: Upload release assets
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b with:
files: |
artifacts/release/*.tar.gz
artifacts/sboms/*.json
artifacts/signatures/*.bundle
artifacts/provenance/*.intoto.jsonl
artifacts/release/checksums.sha256
security:
name: Security Vulnerability Scan
runs-on: ubuntu-latest
needs: extract-version
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Run security scan
uses: firestoned/github-actions/rust/security-scan@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
cargo-audit-version: "0.22.0"
upload-artifact-name: cargo-audit-report-release
publish-crate:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: [extract-version, build, security]
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Update Cargo.toml version
run: |
sed -i 's/^version = ".*"/version = "${{ needs.extract-version.outputs.version }}"/' Cargo.toml
echo "Updated Cargo.toml to version ${{ needs.extract-version.outputs.version }}"
grep '^version = ' Cargo.toml
- name: Publish crate to crates.io
uses: firestoned/github-actions/rust/publish-crate@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
token: ${{ secrets.CARGO_REGISTRY_TOKEN }}