From f0b73e00d403ffee65dd90beee84f7a8a793687a Mon Sep 17 00:00:00 2001 From: Erick Bourgeois Date: Sun, 21 Dec 2025 10:38:05 -0500 Subject: [PATCH] Fix how we determine what SBOMs were geenrted and add test cases for all of actions Signed-off-by: Erick Bourgeois --- .github/workflows/pr.yml | 430 +++++++++++++++++++++++++++++++++ CHANGELOG.md | 16 +- rust/generate-sbom/action.yaml | 46 +++- 3 files changed, 481 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7117cee..b6a4946 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -501,3 +501,433 @@ jobs: else echo "✓ LICENSE file exists" fi + + test-setup-rust-build: + name: Test rust/setup-rust-build + runs-on: ubuntu-latest + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - aarch64-unknown-linux-gnu + steps: + - uses: actions/checkout@v4 + + - name: Test setup-rust-build with ${{ matrix.target }} + uses: ./rust/setup-rust-build + with: + target: ${{ matrix.target }} + + - name: Verify Rust installation + run: | + rustc --version + cargo --version + if rustup target list --installed | grep -q ${{ matrix.target }}; then + echo "✓ Target ${{ matrix.target }} installed" + else + echo "✗ Target ${{ matrix.target }} NOT installed" + echo "Installed targets:" + rustup target list --installed + exit 1 + fi + + - name: Verify default components + run: | + rustfmt --version + cargo clippy --version + + - name: Verify cross for ARM64 + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + cross --version + + test-setup-rust-build-custom-components: + name: Test rust/setup-rust-build with custom components + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Test setup-rust-build with custom components + uses: ./rust/setup-rust-build + with: + target: x86_64-unknown-linux-gnu + components: llvm-tools-preview + + - name: Verify custom component + run: | + # llvm-tools-preview gets installed as llvm-tools- + if rustup component list --installed | grep -q llvm-tools; then + echo "✓ llvm-tools component installed" + rustup component list --installed | grep llvm-tools + else + echo "✗ llvm-tools component NOT installed" + echo "Installed components:" + rustup component list --installed + exit 1 + fi + + test-build-binary: + name: Test rust/build-binary + runs-on: ubuntu-latest + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + steps: + - uses: actions/checkout@v4 + + - name: Create test binary project + run: | + cargo init test-binary + cd test-binary + cat > src/main.rs <<'EOF' + fn main() { + println!("Hello from test binary!"); + } + EOF + + - name: Setup Rust + uses: ./rust/setup-rust-build + with: + target: ${{ matrix.target }} + + - name: Test build-binary action + working-directory: test-binary + run: | + cargo build --release --target ${{ matrix.target }} --verbose + + - name: Verify binary was built + run: | + cd test-binary + test -f target/${{ matrix.target }}/release/test-binary + echo "✓ Binary built successfully" + ./target/${{ matrix.target }}/release/test-binary + + test-verify-toolchain: + name: Test rust/verify-toolchain + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: ./rust/setup-rust-build + with: + target: x86_64-unknown-linux-gnu + + - name: Test verify-toolchain (cargo only) + uses: ./rust/verify-toolchain + with: + require-cargo: true + + - name: Test verify-toolchain (cargo + rustfmt + clippy) + id: verify-all + uses: ./rust/verify-toolchain + with: + require-cargo: true + require-rustfmt: true + require-clippy: true + + - name: Verify outputs + run: | + echo "Rustc version: ${{ steps.verify-all.outputs.rustc-version }}" + echo "Cargo version: ${{ steps.verify-all.outputs.cargo-version }}" + echo "Rustfmt version: ${{ steps.verify-all.outputs.rustfmt-version }}" + echo "Clippy version: ${{ steps.verify-all.outputs.clippy-version }}" + + # Check that outputs are not empty + test -n "${{ steps.verify-all.outputs.rustc-version }}" + test -n "${{ steps.verify-all.outputs.cargo-version }}" + test -n "${{ steps.verify-all.outputs.rustfmt-version }}" + test -n "${{ steps.verify-all.outputs.clippy-version }}" + + test-verify-toolchain-missing: + name: Test rust/verify-toolchain (should fail when component missing) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust without llvm-tools + uses: ./rust/setup-rust-build + with: + target: x86_64-unknown-linux-gnu + components: '' # Install without any extra components + + - name: Test verify-toolchain requiring missing component + id: verify + uses: ./rust/verify-toolchain + with: + require-cargo: true + require-llvm-tools: true # This should fail since we didn't install it + continue-on-error: true + + - name: Verify it failed as expected + run: | + if [ "${{ steps.verify.outcome }}" = "failure" ]; then + echo "✓ Test passed: verify-toolchain correctly failed when llvm-tools-preview is not installed" + else + echo "✗ Test failed: verify-toolchain should have failed when required component is missing" + echo "Outcome was: ${{ steps.verify.outcome }}" + exit 1 + fi + + test-generate-sbom: + name: Test rust/generate-sbom + runs-on: ubuntu-latest + strategy: + matrix: + include: + - describe: crate + format: json + test-case: 'Single crate, JSON format' + - describe: crate + format: xml + test-case: 'Single crate, XML format' + - describe: binaries + format: json + test-case: 'Binaries mode' + steps: + - uses: actions/checkout@v4 + + - name: Create test project + run: | + cargo init test-sbom + cd test-sbom + cat > Cargo.toml <<'EOF' + [package] + name = "test-sbom" + version = "0.1.0" + edition = "2021" + + [dependencies] + serde = { version = "1.0", features = ["derive"] } + EOF + + - name: Setup Rust + uses: ./rust/setup-rust-build + with: + target: x86_64-unknown-linux-gnu + + - name: Test generate-sbom (${{ matrix.test-case }}) + working-directory: test-sbom + run: | + # Install cargo-cyclonedx + cargo install cargo-cyclonedx --version 0.5.7 + + # Generate SBOM + cargo cyclonedx --describe ${{ matrix.describe }} --format ${{ matrix.format }} --all + + - name: Verify SBOM files + run: | + cd test-sbom + if [ "${{ matrix.format }}" = "json" ]; then + ls -lh *.cdx.json 2>/dev/null || ls -lh test-sbom_*.cdx.json || { echo "No JSON SBOM found"; exit 1; } + else + ls -lh *.cdx.xml 2>/dev/null || ls -lh test-sbom_*.cdx.xml || { echo "No XML SBOM found"; exit 1; } + fi + + test-generate-sbom-workspace: + name: Test rust/generate-sbom with workspace + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Create test workspace + run: | + mkdir test-workspace + cd test-workspace + + # Create workspace Cargo.toml + cat > Cargo.toml <<'EOF' + [workspace] + members = ["crate-a", "crate-b"] + resolver = "2" + EOF + + # Create crate-a + cargo init --lib crate-a + cat > crate-a/Cargo.toml <<'EOF' + [package] + name = "crate-a" + version = "0.1.0" + edition = "2021" + + [dependencies] + serde = "1.0" + EOF + + # Create crate-b + cargo init --lib crate-b + cat > crate-b/Cargo.toml <<'EOF' + [package] + name = "crate-b" + version = "0.1.0" + edition = "2021" + + [dependencies] + crate-a = { path = "../crate-a" } + EOF + + - name: Setup Rust + uses: ./rust/setup-rust-build + with: + target: x86_64-unknown-linux-gnu + + - name: Test generate-sbom for workspace + working-directory: test-workspace + run: | + # Install cargo-cyclonedx + cargo install cargo-cyclonedx --version 0.5.7 + + # Generate SBOM for entire workspace + # Note: cargo-cyclonedx 0.5.7 uses --all instead of --workspace + cargo cyclonedx --all --describe crate --format json + + - name: Verify workspace SBOMs + run: | + cd test-workspace + echo "Searching for SBOMs in workspace..." + + # Check root + ls -lh *.cdx.json 2>/dev/null || true + + # Check each crate + find . -name "*.cdx.json" -not -path "*/target/*" -type f | while read sbom; do + echo "Found SBOM: $sbom" + done + + # Ensure at least one SBOM was created + sbom_count=$(find . -name "*.cdx.json" -not -path "*/target/*" -type f | wc -l | tr -d ' ') + if [ "$sbom_count" -eq 0 ]; then + echo "ERROR: No SBOMs found" + exit 1 + fi + echo "✓ Found $sbom_count SBOM(s)" + + test-security-scan: + name: Test rust/security-scan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Create test project + run: | + cargo init test-security + cd test-security + cat > Cargo.toml <<'EOF' + [package] + name = "test-security" + version = "0.1.0" + edition = "2021" + + [dependencies] + serde = "1.0" + EOF + + - name: Setup Rust + uses: ./rust/setup-rust-build + with: + target: x86_64-unknown-linux-gnu + + - name: Test security-scan action + working-directory: test-security + run: | + # Install cargo-audit + cargo install cargo-audit --version 0.21.0 + + # Run audit (may or may not find vulnerabilities) + cargo audit || true + + echo "✓ Security scan completed" + + test-trivy-scan: + name: Test security/trivy-scan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build test container image + run: | + cat > Dockerfile <<'EOF' + FROM alpine:3.18 + RUN apk add --no-cache curl + CMD ["/bin/sh"] + EOF + docker build -t test-image:latest . + + - name: Test trivy-scan action + uses: ./security/trivy-scan + with: + image-ref: test-image:latest + format: table + exit-code: 0 + continue-on-error: true + + - name: Verify trivy ran + run: | + echo "✓ Trivy scan completed" + + test-trivy-scan-sarif: + name: Test security/trivy-scan with SARIF output + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - uses: actions/checkout@v4 + + - name: Build test container image + run: | + cat > Dockerfile <<'EOF' + FROM alpine:3.18 + RUN apk add --no-cache curl + CMD ["/bin/sh"] + EOF + docker build -t test-image-sarif:latest . + + - name: Test trivy-scan with SARIF + uses: ./security/trivy-scan + with: + image-ref: test-image-sarif:latest + format: sarif + output: trivy-results.sarif + sarif-category: trivy-container-test + continue-on-error: true + + - name: Verify SARIF output + run: | + if [ -f "trivy-results.sarif" ]; then + echo "✓ SARIF file created" + cat trivy-results.sarif | jq . > /dev/null || { echo "ERROR: Invalid SARIF JSON"; exit 1; } + else + echo "ERROR: SARIF file not found" + exit 1 + fi + + test-cosign-sign: + name: Test security/cosign-sign (keyless) + runs-on: ubuntu-latest + permissions: + id-token: write + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Build and tag test image + run: | + cat > Dockerfile <<'EOF' + FROM alpine:3.18 + CMD ["/bin/sh"] + EOF + docker build -t ghcr.io/test/test-image:test . + + - name: Test cosign-sign action (dry run simulation) + run: | + # Install cosign + curl -LO https://github.com/sigstore/cosign/releases/download/v2.2.0/cosign-linux-amd64 + sudo mv cosign-linux-amd64 /usr/local/bin/cosign + sudo chmod +x /usr/local/bin/cosign + + # Verify cosign is installed + cosign version + + echo "✓ Cosign installation verified" + echo "Note: Actual signing requires OIDC token and push permissions to registry" diff --git a/CHANGELOG.md b/CHANGELOG.md index f8020f0..d858f3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Workspace support (entire workspace or specific packages) - Fail-on-warnings option - Detailed step summaries and outputs -- Comprehensive test coverage for build-library action (dev/release profiles, feature flags) -- Comprehensive test coverage for lint action (well-formatted, badly-formatted, clippy warnings) +- Comprehensive test coverage for all Rust actions: + - **rust/cache-cargo** - Cache creation verification + - **rust/setup-rust-build** - Multi-target setup (x86_64, ARM64), custom components + - **rust/verify-toolchain** - Component verification, outputs validation, failure scenarios + - **rust/build-binary** - Binary compilation and execution + - **rust/build-library** - Dev/release profiles, feature flags + - **rust/lint** - Well-formatted, badly-formatted, clippy warnings + - **rust/generate-sbom** - Single crate, workspace, multiple formats (JSON/XML) + - **rust/security-scan** - cargo-audit integration +- Comprehensive test coverage for security actions: + - **security/trivy-scan** - Container scanning, SARIF output + - **security/cosign-sign** - Installation verification + - **security/license-check** - SPDX header validation + - **security/verify-signed-commits** - Commit signature verification - Example library CI workflow demonstrating feature matrix testing - Release workflow automation (triggers on release published event) - RELEASE_PROCESS.md documentation for release workflow diff --git a/rust/generate-sbom/action.yaml b/rust/generate-sbom/action.yaml index e06b0cc..14acbbf 100644 --- a/rust/generate-sbom/action.yaml +++ b/rust/generate-sbom/action.yaml @@ -64,9 +64,11 @@ runs: BASE_CMD="cargo cyclonedx" # Add package or workspace flags + # Note: cargo-cyclonedx 0.5.7 doesn't have --workspace flag + # Use --all for workspace-wide generation if [ "${{ inputs.workspace }}" = "true" ]; then - BASE_CMD="$BASE_CMD --workspace" - echo "Generating SBOM for entire workspace" + BASE_CMD="$BASE_CMD --all" + echo "Generating SBOM for entire workspace (using --all)" elif [ -n "${{ inputs.package }}" ]; then BASE_CMD="$BASE_CMD --package ${{ inputs.package }}" echo "Generating SBOM for package: ${{ inputs.package }}" @@ -100,14 +102,40 @@ runs: # List generated files for verification echo "" echo "Generated SBOM files:" - ls -lh *.cdx.* 2>/dev/null || echo "No SBOM files found in current directory" - if [ -n "${{ inputs.target }}" ] && [ -d "target/${{ inputs.target }}/release" ]; then - echo "" - echo "Target-specific SBOM files:" - ls -lh target/${{ inputs.target }}/release/*.cdx.* 2>/dev/null || true + + # Check root directory + if ls *.cdx.* 2>/dev/null; then + echo "Root directory SBOMs:" + ls -lh *.cdx.* fi + + # Check each crate directory (for workspace with describe=crate) + # cargo-cyclonedx generates SBOMs in each crate's directory if [ "${{ inputs.workspace }}" = "true" ] || [ -n "${{ inputs.package }}" ]; then echo "" - echo "Workspace/package SBOM files in target directories:" - find target -name "*.cdx.*" -type f 2>/dev/null || true + echo "Searching for SBOMs in workspace crates..." + # Find all Cargo.toml files (excluding target dirs and root if workspace) + find . -name "Cargo.toml" -not -path "*/target/*" -not -path "*/.git/*" | while read cargo_file; do + crate_dir=$(dirname "$cargo_file") + if ls "$crate_dir"/*.cdx.* 2>/dev/null; then + echo "" + echo "SBOMs in $crate_dir:" + ls -lh "$crate_dir"/*.cdx.* + fi + done fi + + # Also check target directories (for describe=binaries or all-cargo-targets) + if [ "${{ inputs.describe }}" = "binaries" ] || [ "${{ inputs.describe }}" = "all-cargo-targets" ]; then + echo "" + echo "Searching for SBOMs in target directories..." + find target -name "*.cdx.*" -type f 2>/dev/null | while read sbom_file; do + echo " $(ls -lh "$sbom_file")" + done + fi + + # Summary count + echo "" + total_sboms=$(find . -name "*.cdx.*" -not -path "*/target/*" -not -path "*/.git/*" -type f 2>/dev/null | wc -l | tr -d ' ') + target_sboms=$(find target -name "*.cdx.*" -type f 2>/dev/null | wc -l | tr -d ' ') + echo "Total SBOMs found: $total_sboms in workspace, $target_sboms in target/"