name: Build
on:
pull_request:
branches:
- main
push:
branches:
- main
release:
types:
- published
workflow_dispatch:
permissions:
contents: read
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 commits are signed
uses: firestoned/github-actions/security/verify-signed-commits@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
github-token: ${{ secrets.GITHUB_TOKEN }}
verify-mode: ${{ github.event_name == 'pull_request' && 'pr' || github.event_name == 'release' && 'release' || 'push' }}
base-ref: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || '' }}
format:
name: Check Formatting
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
needs: [license-check, verify-commits]
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 with:
components: rustfmt
- name: Check formatting
run: make fmt
clippy:
name: Clippy
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
needs: format
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 with:
components: clippy
- name: Cache cargo dependencies
uses: firestoned/github-actions/rust/cache-cargo@d0d51c638a90bffc2a1567fe7af112b37fe8854c
- name: Run clippy
run: make clippy
extract-version:
name: Extract Version Information
runs-on: ubuntu-latest
needs: [license-check, verify-commits]
outputs:
image-tag-chainguard: ${{ steps.version-chainguard.outputs.image-tag }}
image-tag-distroless: ${{ steps.version-distroless.outputs.image-tag }}
image-repository-chainguard: ${{ steps.version-chainguard.outputs.image-repository }}
image-repository-distroless: ${{ steps.version-distroless.outputs.image-repository }}
short-sha: ${{ steps.version-chainguard.outputs.short-sha || steps.version-tag.outputs.short-sha }}
version: ${{ steps.version-tag.outputs.version }}
tag-name: ${{ steps.version-tag.outputs.tag-name }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Extract version for Chainguard
id: version-chainguard
if: github.event_name != 'release'
uses: firestoned/github-actions/versioning/extract-version@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
repository: firestoned/bindcar
workflow-type: ${{ github.event_name == 'pull_request' && 'pr' || 'main' }}
pr-number: ${{ github.event.pull_request.number }}
image-suffix: ""
- name: Extract version for Distroless
id: version-distroless
if: github.event_name != 'release'
uses: firestoned/github-actions/versioning/extract-version@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
repository: firestoned/bindcar
workflow-type: ${{ github.event_name == 'pull_request' && 'pr' || 'main' }}
pr-number: ${{ github.event.pull_request.number }}
image-suffix: "-distroless"
- name: Extract version from release tag
id: version-tag
if: github.event_name == 'release'
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 (release)
if: github.event_name == 'release'
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 }}
- 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.*
retention-days: 1
if-no-files-found: error
test:
name: Test
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
- name: Download x86_64 build artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
name: bindcar-linux-amd64
path: target/x86_64-unknown-linux-gnu/release/
- name: Cache cargo dependencies
uses: firestoned/github-actions/rust/cache-cargo@d0d51c638a90bffc2a1567fe7af112b37fe8854c
- name: Run regression suite
run: make regression
docker:
name: Build and Push Docker Image - ${{ matrix.variant.name }}
runs-on: ubuntu-latest
if: github.event_name != 'release'
needs: [build, extract-version, test]
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
variant:
- name: Chainguard
dockerfile: docker/Dockerfile.chainguard
suffix: ""
description: "BIND9 RNDC API Server - Chainguard Zero-CVE"
image-tag: ${{ needs.extract-version.outputs.image-tag-chainguard }}
image-repository: ${{ needs.extract-version.outputs.image-repository-chainguard }}
- name: Distroless
dockerfile: docker/Dockerfile
suffix: "-distroless"
description: "BIND9 RNDC API Server - Google Distroless"
image-tag: ${{ needs.extract-version.outputs.image-tag-distroless }}
image-repository: ${{ needs.extract-version.outputs.image-repository-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/${{ matrix.variant.image-repository }}
tags: |
type=raw,value=${{ matrix.variant.image-tag }}
type=raw,value=sha-${{ needs.extract-version.outputs.short-sha }}
type=raw,value=main-{{date 'YYYY-MM-DD'}},enable=${{ github.event_name == 'push' }}
type=raw,value=latest,enable=${{ github.event_name == 'push' }}
flavor: |
latest=false
- name: Build and push Docker image (${{ matrix.variant.name }})
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with:
context: .
file: ${{ matrix.variant.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: |
${{ steps.meta.outputs.labels }}
org.opencontainers.image.description=${{ matrix.variant.description }}
cache-from: type=gha,scope=${{ matrix.variant.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.variant.name }}
sbom: true
provenance: true
kind-e2e:
name: Kind E2E
runs-on: ubuntu-latest
if: github.event_name != 'release'
needs: [docker, extract-version]
permissions:
contents: read
packages: read
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Install DNS tools
run: sudo apt-get update && sudo apt-get install -y dnsutils
- name: Setup Docker environment
uses: firestoned/github-actions/docker/setup-docker@d0d51c638a90bffc2a1567fe7af112b37fe8854c with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Install kind
run: |
curl -sSLo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
sudo install -m 0755 ./kind /usr/local/bin/kind
rm -f ./kind
- name: Run kind e2e against the pushed Chainguard image
run: |
make kind-e2e \
BINDCAR_IMAGE=ghcr.io/${{ needs.extract-version.outputs.image-repository-chainguard }}:${{ needs.extract-version.outputs.image-tag-chainguard }} \
SKIP_IMAGE_BUILD=true
drone-integration-test:
name: Drone Integration Test
runs-on: ubuntu-latest
if: github.event_name != 'release'
needs: build
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Install DNS tools
run: sudo apt-get update && sudo apt-get install -y dnsutils bind9-utils
- name: Download x86_64 build artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
name: bindcar-linux-amd64
path: /tmp/bindcar-bin
- name: Make binary executable
run: chmod +x /tmp/bindcar-bin/target/x86_64-unknown-linux-gnu/release/bindcar
- name: Run drone integration test
env:
BINDCAR_BIN: /tmp/bindcar-bin/target/x86_64-unknown-linux-gnu/release/bindcar
run: make drone-integration-test-ci
coverage:
name: Code Coverage
runs-on: ubuntu-latest
if: github.event_name != 'release'
needs: test
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
- name: Setup Rust cache
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 with:
tool: cargo-llvm-cov
- name: Generate coverage report
run: cargo llvm-cov --lib --lcov --output-path lcov.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f with:
files: lcov.info
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
security:
name: Security Vulnerability Scan
runs-on: ubuntu-latest
needs: [license-check, verify-commits]
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"
docker-release:
name: Release Docker Image - ${{ matrix.variant.name }}
runs-on: ubuntu-latest
if: github.event_name == 'release'
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
if: github.event_name == 'release'
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 }}
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
if: github.event_name == 'release'
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
sha256sum ./*.tar.gz > checksums.txt
cat checksums.txt
echo "hashes=$(base64 -w0 < checksums.txt)" >> "$GITHUB_OUTPUT"
slsa-provenance:
name: Generate SLSA Provenance
if: github.event_name == 'release'
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
if: github.event_name == 'release'
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
for dir in bindcar-linux-*-signed; do
if [ -d "$dir" ]; then
if compgen -G "$dir/*.tar.gz" > /dev/null; then
cp "$dir"/*.tar.gz release/
fi
if compgen -G "$dir/*.tar.gz.bundle" > /dev/null; then
cp "$dir"/*.tar.gz.bundle signatures/
fi
fi
done
for dir in bindcar-linux-amd64 bindcar-linux-arm64; do
if [ -d "$dir" ] && compgen -G "$dir/*.cdx.json" > /dev/null; then
cp "$dir"/*.cdx.json "sboms/${dir}-sbom.json"
fi
done
for variant in Chainguard Distroless; do
if [ -f "docker-sbom-${variant}/docker-sbom-${variant}.json" ]; then
cp "docker-sbom-${variant}/docker-sbom-${variant}.json" "sboms/docker-sbom-${variant}.json"
fi
done
if compgen -G "*.intoto.jsonl" > /dev/null; then
find . -name "*.intoto.jsonl" -type f -exec cp {} provenance/ \;
fi
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/
- 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
publish-crate:
name: Publish to crates.io
runs-on: ubuntu-latest
if: github.event_name == 'release'
needs: [extract-version, build, test, 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 }}