test fixes and new product advisories work
This commit is contained in:
327
.gitea/workflows/ebpf-reachability-determinism.yml
Normal file
327
.gitea/workflows/ebpf-reachability-determinism.yml
Normal file
@@ -0,0 +1,327 @@
|
||||
name: eBPF Reachability Determinism
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/Signals/__Libraries/StellaOps.Signals.Ebpf/**'
|
||||
- 'src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/**'
|
||||
- 'tests/reachability/**'
|
||||
- '.gitea/workflows/ebpf-reachability-determinism.yml'
|
||||
- 'scripts/ebpf/**'
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'src/Signals/__Libraries/StellaOps.Signals.Ebpf/**'
|
||||
- 'src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/**'
|
||||
- 'tests/reachability/**'
|
||||
- '.gitea/workflows/ebpf-reachability-determinism.yml'
|
||||
- 'scripts/ebpf/**'
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
# ============================================================================
|
||||
# Multi-Kernel eBPF CO-RE Testing (3 major kernel versions)
|
||||
# ============================================================================
|
||||
multi-kernel-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Kernel 5.4 LTS (Ubuntu 20.04)
|
||||
- kernel_version: "5.4"
|
||||
distro: "focal"
|
||||
image: "ubuntu:20.04"
|
||||
dotnet_install: "true"
|
||||
runner: ${{ vars.KERNEL_5_4_RUNNER || 'ubuntu-latest' }}
|
||||
# Kernel 5.15 LTS (Ubuntu 22.04)
|
||||
- kernel_version: "5.15"
|
||||
distro: "jammy"
|
||||
image: "ubuntu:22.04"
|
||||
dotnet_install: "true"
|
||||
runner: ${{ vars.KERNEL_5_15_RUNNER || 'ubuntu-22.04' }}
|
||||
# Kernel 6.x (Ubuntu 24.04)
|
||||
- kernel_version: "6.x"
|
||||
distro: "noble"
|
||||
image: "ubuntu:24.04"
|
||||
dotnet_install: "true"
|
||||
runner: ${{ vars.KERNEL_6_X_RUNNER || 'ubuntu-24.04' }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
name: "Kernel ${{ matrix.kernel_version }} (${{ matrix.distro }})"
|
||||
env:
|
||||
DOTNET_NOLOGO: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
|
||||
TZ: UTC
|
||||
STELLAOPS_UPDATE_FIXTURES: "false"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Report kernel version
|
||||
run: |
|
||||
echo "=============================================="
|
||||
echo "Kernel ${{ matrix.kernel_version }} Test (${{ matrix.distro }})"
|
||||
echo "=============================================="
|
||||
uname -a
|
||||
cat /etc/os-release | head -5
|
||||
echo ""
|
||||
echo "BTF availability:"
|
||||
if [ -f /sys/kernel/btf/vmlinux ]; then
|
||||
echo " Built-in BTF: YES"
|
||||
ls -la /sys/kernel/btf/vmlinux
|
||||
else
|
||||
echo " Built-in BTF: NO (external BTF may be required)"
|
||||
fi
|
||||
echo ""
|
||||
echo "eBPF kernel config:"
|
||||
if [ -f /boot/config-$(uname -r) ]; then
|
||||
grep -E "CONFIG_BPF|CONFIG_DEBUG_INFO_BTF" /boot/config-$(uname -r) 2>/dev/null || echo " Config not readable"
|
||||
else
|
||||
echo " Kernel config not available"
|
||||
fi
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.nuget/packages
|
||||
.nuget/packages
|
||||
key: ebpf-${{ matrix.distro }}-nuget-${{ hashFiles('src/Signals/**/*.csproj') }}
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj --configfile nuget.config
|
||||
|
||||
- name: Build
|
||||
run: dotnet build src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj -c Release --no-restore
|
||||
|
||||
- name: Run all eBPF tests
|
||||
run: |
|
||||
echo "Running all eBPF tests on kernel ${{ matrix.kernel_version }}..."
|
||||
dotnet test src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj \
|
||||
-c Release --no-build \
|
||||
--logger "trx;LogFileName=ebpf-tests-${{ matrix.distro }}.trx" \
|
||||
--logger "console;verbosity=minimal"
|
||||
|
||||
- name: Record kernel compatibility
|
||||
run: |
|
||||
echo "Kernel ${{ matrix.kernel_version }} (${{ matrix.distro }}): PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Host kernel: $(uname -r)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ebpf-test-results-kernel-${{ matrix.kernel_version }}
|
||||
path: |
|
||||
**/ebpf-tests-${{ matrix.distro }}.trx
|
||||
retention-days: 7
|
||||
|
||||
# ============================================================================
|
||||
# Docker-based Multi-Kernel Tests (for environments without native runners)
|
||||
# ============================================================================
|
||||
docker-kernel-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Ubuntu 20.04 (Kernel 5.4 compatible)
|
||||
- kernel_version: "5.4"
|
||||
distro: "focal"
|
||||
base_image: "ubuntu:20.04"
|
||||
# Ubuntu 22.04 (Kernel 5.15 compatible)
|
||||
- kernel_version: "5.15"
|
||||
distro: "jammy"
|
||||
base_image: "ubuntu:22.04"
|
||||
# Ubuntu 24.04 (Kernel 6.x compatible)
|
||||
- kernel_version: "6.x"
|
||||
distro: "noble"
|
||||
base_image: "ubuntu:24.04"
|
||||
runs-on: ubuntu-latest
|
||||
name: "Docker: Kernel ${{ matrix.kernel_version }} (${{ matrix.distro }})"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build and test in Docker
|
||||
run: |
|
||||
chmod +x scripts/ebpf/docker-kernel-test.sh
|
||||
scripts/ebpf/docker-kernel-test.sh "${{ matrix.base_image }}" "${{ matrix.kernel_version }}" "${{ matrix.distro }}"
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docker-test-results-${{ matrix.distro }}
|
||||
path: |
|
||||
out/ebpf-tests-${{ matrix.distro }}.trx
|
||||
retention-days: 7
|
||||
|
||||
# ============================================================================
|
||||
# Cross-Distribution Tests (glibc vs musl)
|
||||
# ============================================================================
|
||||
cross-distro-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- image: "mcr.microsoft.com/dotnet/sdk:10.0"
|
||||
distro: "ubuntu-glibc"
|
||||
libc: "glibc"
|
||||
- image: "mcr.microsoft.com/dotnet/sdk:10.0-alpine"
|
||||
distro: "alpine-musl"
|
||||
libc: "musl"
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
name: "Distro: ${{ matrix.distro }} (${{ matrix.libc }})"
|
||||
env:
|
||||
DOTNET_NOLOGO: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
|
||||
TZ: UTC
|
||||
STELLAOPS_UPDATE_FIXTURES: "false"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Report environment
|
||||
run: |
|
||||
echo "=============================================="
|
||||
echo "Cross-distribution test: ${{ matrix.distro }}"
|
||||
echo "=============================================="
|
||||
uname -a
|
||||
cat /etc/os-release | head -3
|
||||
echo "libc: ${{ matrix.libc }}"
|
||||
dotnet --version
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj --configfile nuget.config
|
||||
|
||||
- name: Build
|
||||
run: dotnet build src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj -c Release --no-restore
|
||||
|
||||
- name: Run all tests
|
||||
run: |
|
||||
dotnet test src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj \
|
||||
-c Release --no-build \
|
||||
--logger "trx;LogFileName=tests-${{ matrix.distro }}.trx" \
|
||||
--logger "console;verbosity=minimal"
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: distro-test-results-${{ matrix.distro }}
|
||||
path: |
|
||||
**/tests-${{ matrix.distro }}.trx
|
||||
retention-days: 7
|
||||
|
||||
# ============================================================================
|
||||
# Determinism Tests
|
||||
# ============================================================================
|
||||
determinism-tests:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOTNET_NOLOGO: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
|
||||
TZ: UTC
|
||||
STELLAOPS_UPDATE_FIXTURES: "false"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj --configfile nuget.config
|
||||
|
||||
- name: Build
|
||||
run: dotnet build src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj -c Release --no-restore
|
||||
|
||||
- name: Run determinism tests
|
||||
run: |
|
||||
dotnet test src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj \
|
||||
-c Release --no-build \
|
||||
--filter "Category=Determinism" \
|
||||
--logger "trx;LogFileName=determinism-tests.trx" \
|
||||
--logger "console;verbosity=normal"
|
||||
|
||||
- name: Verify golden file integrity
|
||||
run: |
|
||||
if git diff --exit-code tests/reachability/fixtures/ebpf/golden/; then
|
||||
echo "Golden files unchanged - determinism verified"
|
||||
else
|
||||
echo "ERROR: Golden files were modified during test run!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Golden File Validation
|
||||
# ============================================================================
|
||||
golden-file-validation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate golden files
|
||||
run: |
|
||||
for file in tests/reachability/fixtures/ebpf/golden/*.ndjson; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "Checking $file..."
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
if [ -n "$line" ]; then
|
||||
echo "$line" | jq -e . > /dev/null 2>&1 || { echo "Invalid JSON in $file"; exit 1; }
|
||||
fi
|
||||
done < "$file"
|
||||
fi
|
||||
done
|
||||
echo "All golden files valid"
|
||||
|
||||
# ============================================================================
|
||||
# Summary
|
||||
# ============================================================================
|
||||
summary:
|
||||
needs: [multi-kernel-tests, docker-kernel-tests, cross-distro-tests, determinism-tests, golden-file-validation]
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check results
|
||||
run: |
|
||||
echo "=============================================="
|
||||
echo "eBPF Reachability Test Summary"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "Multi-kernel tests (native): ${{ needs.multi-kernel-tests.result }}"
|
||||
echo "Multi-kernel tests (Docker): ${{ needs.docker-kernel-tests.result }}"
|
||||
echo "Cross-distro tests: ${{ needs.cross-distro-tests.result }}"
|
||||
echo "Determinism tests: ${{ needs.determinism-tests.result }}"
|
||||
echo "Golden file validation: ${{ needs.golden-file-validation.result }}"
|
||||
|
||||
if [[ "${{ needs.multi-kernel-tests.result }}" != "success" ]] || \
|
||||
[[ "${{ needs.docker-kernel-tests.result }}" != "success" ]] || \
|
||||
[[ "${{ needs.cross-distro-tests.result }}" != "success" ]] || \
|
||||
[[ "${{ needs.determinism-tests.result }}" != "success" ]] || \
|
||||
[[ "${{ needs.golden-file-validation.result }}" != "success" ]]; then
|
||||
echo "ERROR: One or more test jobs failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All tests passed across kernel versions 5.4, 5.15, and 6.x!"
|
||||
167
.gitea/workflows/registry-compatibility.yml
Normal file
167
.gitea/workflows/registry-compatibility.yml
Normal file
@@ -0,0 +1,167 @@
|
||||
name: registry-compatibility
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/ExportCenter/**'
|
||||
- 'src/ReleaseOrchestrator/**/Connectors/Registry/**'
|
||||
- 'src/__Tests/**Registry**'
|
||||
- 'src/__Libraries/StellaOps.Doctor.Plugins.Integration/**'
|
||||
schedule:
|
||||
- cron: '0 4 * * 1' # Weekly on Monday at 4 AM UTC
|
||||
workflow_dispatch: {}
|
||||
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
jobs:
|
||||
registry-matrix:
|
||||
name: Registry ${{ matrix.registry }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
registry: [generic-oci, zot, distribution, harbor]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "10.0.100"
|
||||
|
||||
- name: Restore dependencies
|
||||
run: |
|
||||
dotnet restore src/__Tests/__Libraries/StellaOps.Infrastructure.Registry.Testing.Tests/StellaOps.Infrastructure.Registry.Testing.Tests.csproj
|
||||
|
||||
- name: Build test project
|
||||
run: |
|
||||
dotnet build src/__Tests/__Libraries/StellaOps.Infrastructure.Registry.Testing.Tests/StellaOps.Infrastructure.Registry.Testing.Tests.csproj --no-restore
|
||||
|
||||
- name: Run compatibility tests for ${{ matrix.registry }}
|
||||
run: |
|
||||
dotnet test src/__Tests/__Libraries/StellaOps.Infrastructure.Registry.Testing.Tests/StellaOps.Infrastructure.Registry.Testing.Tests.csproj \
|
||||
--no-build \
|
||||
--filter "Category=RegistryCompatibility" \
|
||||
--logger "trx;LogFileName=${{ matrix.registry }}-results.trx" \
|
||||
--results-directory TestResults \
|
||||
-- xunit.parallelizeTestCollections=false
|
||||
timeout-minutes: 15
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: registry-compat-${{ matrix.registry }}
|
||||
path: TestResults/
|
||||
retention-days: 30
|
||||
|
||||
compatibility-report:
|
||||
name: Generate Compatibility Report
|
||||
runs-on: ubuntu-latest
|
||||
needs: registry-matrix
|
||||
if: always()
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: TestResults
|
||||
pattern: registry-compat-*
|
||||
|
||||
- name: Generate compatibility matrix
|
||||
run: |
|
||||
echo "# Registry Compatibility Matrix" > compatibility-report.md
|
||||
echo "" >> compatibility-report.md
|
||||
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> compatibility-report.md
|
||||
echo "" >> compatibility-report.md
|
||||
echo "| Registry | OCI Compliance | Referrers API | Auth | Capabilities | Status |" >> compatibility-report.md
|
||||
echo "|----------|---------------|---------------|------|--------------|--------|" >> compatibility-report.md
|
||||
|
||||
for registry in generic-oci zot distribution harbor; do
|
||||
trx_file="TestResults/registry-compat-${registry}/${registry}-results.trx"
|
||||
if [ -f "$trx_file" ]; then
|
||||
# Count passed/failed from trx file
|
||||
passed=$(grep -c 'outcome="Passed"' "$trx_file" 2>/dev/null || echo "0")
|
||||
failed=$(grep -c 'outcome="Failed"' "$trx_file" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$failed" -eq "0" ]; then
|
||||
status="Pass"
|
||||
else
|
||||
status="Fail ($failed)"
|
||||
fi
|
||||
else
|
||||
status="No results"
|
||||
fi
|
||||
|
||||
# Referrers API support
|
||||
case $registry in
|
||||
generic-oci) referrers="Fallback" ;;
|
||||
zot|harbor|distribution) referrers="Native" ;;
|
||||
esac
|
||||
|
||||
echo "| $registry | $passed tests | $referrers | Basic | Full | $status |" >> compatibility-report.md
|
||||
done
|
||||
|
||||
echo "" >> compatibility-report.md
|
||||
echo "## Legend" >> compatibility-report.md
|
||||
echo "- **Native**: Full OCI 1.1 referrers API support" >> compatibility-report.md
|
||||
echo "- **Fallback**: Uses tag-based discovery (sha256-{digest}.*)" >> compatibility-report.md
|
||||
|
||||
cat compatibility-report.md
|
||||
|
||||
- name: Upload compatibility report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: compatibility-report
|
||||
path: compatibility-report.md
|
||||
retention-days: 90
|
||||
|
||||
- name: Comment on PR
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const report = fs.readFileSync('compatibility-report.md', 'utf8');
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: report
|
||||
});
|
||||
|
||||
doctor-checks:
|
||||
name: Doctor Registry Checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "10.0.100"
|
||||
|
||||
- name: Build Doctor plugin tests
|
||||
run: |
|
||||
dotnet build src/__Tests/__Libraries/StellaOps.Doctor.Plugins.Integration.Tests/StellaOps.Doctor.Plugins.Integration.Tests.csproj
|
||||
|
||||
- name: Run Doctor check tests
|
||||
run: |
|
||||
dotnet test src/__Tests/__Libraries/StellaOps.Doctor.Plugins.Integration.Tests/StellaOps.Doctor.Plugins.Integration.Tests.csproj \
|
||||
--no-build \
|
||||
--logger "trx;LogFileName=doctor-registry-results.trx" \
|
||||
--results-directory TestResults
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: doctor-registry-checks
|
||||
path: TestResults/
|
||||
retention-days: 30
|
||||
537
.gitea/workflows/release-evidence-pack.yml
Normal file
537
.gitea/workflows/release-evidence-pack.yml
Normal file
@@ -0,0 +1,537 @@
|
||||
# .gitea/workflows/release-evidence-pack.yml
|
||||
# Generates Release Evidence Pack for customer-facing verification
|
||||
#
|
||||
# This workflow depends on all test pipelines completing successfully before
|
||||
# generating the evidence pack to ensure only verified releases are attested.
|
||||
|
||||
name: Release Evidence Pack
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Release version (e.g., 1.2.3)"
|
||||
required: true
|
||||
type: string
|
||||
release_tag:
|
||||
description: "Git tag for the release"
|
||||
required: true
|
||||
type: string
|
||||
signing_mode:
|
||||
description: "Signing mode"
|
||||
required: false
|
||||
default: "keyless"
|
||||
type: choice
|
||||
options:
|
||||
- keyless
|
||||
- key-based
|
||||
include_rekor_proofs:
|
||||
description: "Include Rekor transparency log proofs"
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
# Trigger after release workflow completes
|
||||
workflow_run:
|
||||
workflows: ["Release Bundle"]
|
||||
types: [completed]
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
DOTNET_VERSION: "10.0.100"
|
||||
EVIDENCE_PACK_DIR: ${{ github.workspace }}/evidence-pack
|
||||
|
||||
jobs:
|
||||
# ============================================================================
|
||||
# Gate: Ensure all test pipelines have passed
|
||||
# ============================================================================
|
||||
verify-test-gates:
|
||||
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
||||
outputs:
|
||||
tests_passed: ${{ steps.check-tests.outputs.passed }}
|
||||
release_version: ${{ steps.meta.outputs.version }}
|
||||
release_tag: ${{ steps.meta.outputs.tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.release_tag || github.event.workflow_run.head_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine release metadata
|
||||
id: meta
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
TAG="${{ github.event.inputs.release_tag }}"
|
||||
else
|
||||
# Extract from workflow_run
|
||||
TAG="${{ github.event.workflow_run.head_branch }}"
|
||||
VERSION="${TAG#v}"
|
||||
fi
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify test workflows have passed
|
||||
id: check-tests
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="${{ steps.meta.outputs.sha || github.sha }}"
|
||||
|
||||
echo "Checking test status for commit: $SHA"
|
||||
|
||||
# Required workflows that must pass
|
||||
REQUIRED_WORKFLOWS=(
|
||||
"Build Test Deploy"
|
||||
"test-matrix"
|
||||
"integration-tests-gate"
|
||||
"security-testing"
|
||||
"determinism-gate"
|
||||
)
|
||||
|
||||
FAILED=()
|
||||
PENDING=()
|
||||
|
||||
for workflow in "${REQUIRED_WORKFLOWS[@]}"; do
|
||||
echo "Checking workflow: $workflow"
|
||||
|
||||
# Get workflow runs for this commit
|
||||
STATUS=$(gh api \
|
||||
"/repos/${{ github.repository }}/actions/workflows" \
|
||||
--jq ".workflows[] | select(.name == \"$workflow\") | .id" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$STATUS" ]; then
|
||||
echo " Warning: Workflow '$workflow' not found, skipping..."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get latest run for this commit
|
||||
RUN_STATUS=$(gh api \
|
||||
"/repos/${{ github.repository }}/actions/workflows/$STATUS/runs?head_sha=$SHA&per_page=1" \
|
||||
--jq '.workflow_runs[0].conclusion // .workflow_runs[0].status' 2>/dev/null || echo "not_found")
|
||||
|
||||
echo " Status: $RUN_STATUS"
|
||||
|
||||
case "$RUN_STATUS" in
|
||||
success|skipped)
|
||||
echo " ✓ Passed"
|
||||
;;
|
||||
in_progress|queued|waiting|pending)
|
||||
PENDING+=("$workflow")
|
||||
;;
|
||||
not_found)
|
||||
echo " ⚠ No run found for this commit"
|
||||
;;
|
||||
*)
|
||||
FAILED+=("$workflow ($RUN_STATUS)")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ${#FAILED[@]} -gt 0 ]; then
|
||||
echo "::error::The following required workflows have not passed: ${FAILED[*]}"
|
||||
echo "passed=false" >> "$GITHUB_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ${#PENDING[@]} -gt 0 ]; then
|
||||
echo "::warning::The following workflows are still running: ${PENDING[*]}"
|
||||
echo "::warning::Consider waiting for them to complete before generating evidence pack."
|
||||
fi
|
||||
|
||||
echo "✓ All required test workflows have passed"
|
||||
echo "passed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# ============================================================================
|
||||
# Build Evidence Pack
|
||||
# ============================================================================
|
||||
build-evidence-pack:
|
||||
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
needs: verify-test-gates
|
||||
if: needs.verify-test-gates.outputs.tests_passed == 'true'
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write # For keyless signing
|
||||
packages: read
|
||||
env:
|
||||
VERSION: ${{ needs.verify-test-gates.outputs.release_version }}
|
||||
TAG: ${{ needs.verify-test-gates.outputs.release_tag }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.TAG }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
include-prerelease: true
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.4.0
|
||||
|
||||
- name: Install Syft
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SYFT_VERSION="v1.21.0"
|
||||
curl -fsSL "https://github.com/anchore/syft/releases/download/${SYFT_VERSION}/syft_${SYFT_VERSION#v}_linux_amd64.tar.gz" -o /tmp/syft.tgz
|
||||
tar -xzf /tmp/syft.tgz -C /tmp
|
||||
sudo install -m 0755 /tmp/syft /usr/local/bin/syft
|
||||
|
||||
- name: Install rekor-cli
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REKOR_VERSION="v1.3.6"
|
||||
curl -fsSL "https://github.com/sigstore/rekor/releases/download/${REKOR_VERSION}/rekor-cli-linux-amd64" -o /tmp/rekor-cli
|
||||
sudo install -m 0755 /tmp/rekor-cli /usr/local/bin/rekor-cli
|
||||
|
||||
- name: Download release artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p artifacts/
|
||||
gh release download "$TAG" -D artifacts/ || {
|
||||
echo "::warning::Could not download release artifacts. Using build artifacts instead."
|
||||
# Fallback: download from workflow artifacts
|
||||
gh run download --name "stellaops-release-$VERSION" -D artifacts/ || true
|
||||
}
|
||||
|
||||
ls -la artifacts/
|
||||
|
||||
- name: Compute SOURCE_DATE_EPOCH
|
||||
id: epoch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EPOCH=$(git show -s --format=%ct HEAD)
|
||||
echo "epoch=$EPOCH" >> "$GITHUB_OUTPUT"
|
||||
echo "SOURCE_DATE_EPOCH=$EPOCH"
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p checksums/
|
||||
|
||||
cd artifacts/
|
||||
sha256sum * 2>/dev/null | grep -v '\.sig$' | grep -v '\.cert$' > ../checksums/SHA256SUMS || true
|
||||
sha512sum * 2>/dev/null | grep -v '\.sig$' | grep -v '\.cert$' > ../checksums/SHA512SUMS || true
|
||||
cd ..
|
||||
|
||||
echo "Generated checksums:"
|
||||
cat checksums/SHA256SUMS
|
||||
|
||||
- name: Sign checksums
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "1"
|
||||
COSIGN_KEY_REF: ${{ secrets.COSIGN_KEY_REF }}
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
SIGN_ARGS=(--yes)
|
||||
if [ "${{ github.event.inputs.signing_mode || 'keyless' }}" = "key-based" ] && [ -n "${COSIGN_KEY_REF:-}" ]; then
|
||||
SIGN_ARGS+=(--key "$COSIGN_KEY_REF")
|
||||
fi
|
||||
|
||||
cosign sign-blob "${SIGN_ARGS[@]}" \
|
||||
--output-signature checksums/SHA256SUMS.sig \
|
||||
--output-certificate checksums/SHA256SUMS.cert \
|
||||
checksums/SHA256SUMS
|
||||
|
||||
cosign sign-blob "${SIGN_ARGS[@]}" \
|
||||
--output-signature checksums/SHA512SUMS.sig \
|
||||
--output-certificate checksums/SHA512SUMS.cert \
|
||||
checksums/SHA512SUMS
|
||||
|
||||
echo "✓ Checksums signed"
|
||||
|
||||
- name: Generate SBOMs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p sbom/
|
||||
|
||||
for artifact in artifacts/stella-*.tar.gz artifacts/stella-*.zip; do
|
||||
[ -f "$artifact" ] || continue
|
||||
|
||||
base=$(basename "$artifact" | sed 's/\.\(tar\.gz\|zip\)$//')
|
||||
echo "Generating SBOM for: $base"
|
||||
|
||||
syft "$artifact" -o cyclonedx-json > "sbom/${base}.cdx.json"
|
||||
done
|
||||
|
||||
# Sign SBOMs
|
||||
for sbom in sbom/*.cdx.json; do
|
||||
[ -f "$sbom" ] || continue
|
||||
SIGN_ARGS=(--yes)
|
||||
if [ "${{ github.event.inputs.signing_mode || 'keyless' }}" = "key-based" ] && [ -n "${COSIGN_KEY_REF:-}" ]; then
|
||||
SIGN_ARGS+=(--key "$COSIGN_KEY_REF")
|
||||
fi
|
||||
cosign sign-blob "${SIGN_ARGS[@]}" \
|
||||
--output-signature "${sbom}.sig" \
|
||||
--output-certificate "${sbom}.cert" \
|
||||
"$sbom"
|
||||
done
|
||||
|
||||
echo "✓ SBOMs generated and signed"
|
||||
|
||||
- name: Generate SLSA provenance
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p provenance/
|
||||
|
||||
SOURCE_EPOCH="${{ steps.epoch.outputs.epoch }}"
|
||||
GIT_SHA="${{ github.sha }}"
|
||||
BUILD_TIME=$(date -u -d "@$SOURCE_EPOCH" +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Generate SLSA v1.0 provenance for each artifact
|
||||
for artifact in artifacts/stella-*.tar.gz artifacts/stella-*.zip; do
|
||||
[ -f "$artifact" ] || continue
|
||||
|
||||
base=$(basename "$artifact" | sed 's/\.\(tar\.gz\|zip\)$//')
|
||||
ARTIFACT_SHA256=$(sha256sum "$artifact" | awk '{print $1}')
|
||||
|
||||
cat > "provenance/${base}.slsa.intoto.jsonl" <<EOF
|
||||
{
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [
|
||||
{
|
||||
"name": "$(basename "$artifact")",
|
||||
"digest": {
|
||||
"sha256": "$ARTIFACT_SHA256"
|
||||
}
|
||||
}
|
||||
],
|
||||
"predicateType": "https://slsa.dev/provenance/v1",
|
||||
"predicate": {
|
||||
"buildDefinition": {
|
||||
"buildType": "https://stella-ops.io/ReleaseBuilder/v1",
|
||||
"externalParameters": {
|
||||
"version": "$VERSION",
|
||||
"target": "$base"
|
||||
},
|
||||
"internalParameters": {},
|
||||
"resolvedDependencies": [
|
||||
{
|
||||
"uri": "git+https://git.stella-ops.org/stella-ops.org/git.stella-ops.org@$TAG",
|
||||
"digest": {
|
||||
"gitCommit": "$GIT_SHA"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"runDetails": {
|
||||
"builder": {
|
||||
"id": "https://ci.stella-ops.org/builder/v1",
|
||||
"version": {
|
||||
"ci": "${{ github.run_id }}"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"invocationId": "${{ github.run_id }}/${{ github.run_attempt }}",
|
||||
"startedOn": "$BUILD_TIME",
|
||||
"finishedOn": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
},
|
||||
"byproducts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Sign provenance
|
||||
SIGN_ARGS=(--yes)
|
||||
if [ "${{ github.event.inputs.signing_mode || 'keyless' }}" = "key-based" ] && [ -n "${COSIGN_KEY_REF:-}" ]; then
|
||||
SIGN_ARGS+=(--key "$COSIGN_KEY_REF")
|
||||
fi
|
||||
cosign sign-blob "${SIGN_ARGS[@]}" \
|
||||
--output-signature "provenance/${base}.slsa.intoto.jsonl.sig" \
|
||||
--output-certificate "provenance/${base}.slsa.intoto.jsonl.cert" \
|
||||
"provenance/${base}.slsa.intoto.jsonl"
|
||||
done
|
||||
|
||||
echo "✓ SLSA provenance generated and signed"
|
||||
|
||||
- name: Collect Rekor proofs
|
||||
if: github.event.inputs.include_rekor_proofs != 'false'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p rekor-proofs/log-entries/
|
||||
|
||||
# Collect Rekor entries for signed artifacts
|
||||
for sig in artifacts/*.sig checksums/*.sig sbom/*.sig provenance/*.sig; do
|
||||
[ -f "$sig" ] || continue
|
||||
|
||||
artifact="${sig%.sig}"
|
||||
[ -f "$artifact" ] || continue
|
||||
|
||||
echo "Looking up Rekor entry for: $artifact"
|
||||
|
||||
# Search Rekor for this artifact
|
||||
ENTRY=$(rekor-cli search --artifact "$artifact" 2>/dev/null | head -1 || echo "")
|
||||
|
||||
if [ -n "$ENTRY" ]; then
|
||||
UUID=$(basename "$ENTRY")
|
||||
echo " Found entry: $UUID"
|
||||
|
||||
# Get the full entry
|
||||
rekor-cli get --uuid "$UUID" --format json > "rekor-proofs/log-entries/${UUID}.json" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Get current checkpoint
|
||||
rekor-cli loginfo --format json > rekor-proofs/checkpoint.json 2>/dev/null || true
|
||||
|
||||
echo "✓ Rekor proofs collected"
|
||||
|
||||
- name: Extract signing key fingerprint
|
||||
id: key-fingerprint
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Extract fingerprint from certificate or key
|
||||
if [ -f checksums/SHA256SUMS.cert ]; then
|
||||
FINGERPRINT=$(openssl x509 -in checksums/SHA256SUMS.cert -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2 | tr -d ':' | tr '[:upper:]' '[:lower:]')
|
||||
elif [ -n "${COSIGN_KEY_REF:-}" ]; then
|
||||
FINGERPRINT="key-based-signing"
|
||||
else
|
||||
FINGERPRINT="keyless-fulcio"
|
||||
fi
|
||||
|
||||
echo "fingerprint=$FINGERPRINT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build evidence pack using .NET tool
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Build the EvidencePack library
|
||||
dotnet build src/Attestor/__Libraries/StellaOps.Attestor.EvidencePack/StellaOps.Attestor.EvidencePack.csproj \
|
||||
--configuration Release
|
||||
|
||||
# Create evidence pack structure manually for now
|
||||
# (CLI tool would be: dotnet run --project src/Attestor/.../EvidencePack.Cli build-pack ...)
|
||||
|
||||
PACK_DIR="evidence-pack/stella-release-${VERSION}-evidence-pack"
|
||||
mkdir -p "$PACK_DIR"/{artifacts,checksums,sbom,provenance,attestations,rekor-proofs/log-entries}
|
||||
|
||||
# Copy files
|
||||
cp -r artifacts/* "$PACK_DIR/artifacts/" 2>/dev/null || true
|
||||
cp -r checksums/* "$PACK_DIR/checksums/" 2>/dev/null || true
|
||||
cp -r sbom/* "$PACK_DIR/sbom/" 2>/dev/null || true
|
||||
cp -r provenance/* "$PACK_DIR/provenance/" 2>/dev/null || true
|
||||
cp -r rekor-proofs/* "$PACK_DIR/rekor-proofs/" 2>/dev/null || true
|
||||
|
||||
# Copy signing public key
|
||||
if [ -f checksums/SHA256SUMS.cert ]; then
|
||||
# Extract public key from certificate
|
||||
openssl x509 -in checksums/SHA256SUMS.cert -pubkey -noout > "$PACK_DIR/cosign.pub"
|
||||
elif [ -n "${COSIGN_PUBLIC_KEY:-}" ]; then
|
||||
echo "$COSIGN_PUBLIC_KEY" > "$PACK_DIR/cosign.pub"
|
||||
fi
|
||||
|
||||
# Generate manifest.json
|
||||
cat > "$PACK_DIR/manifest.json" <<EOF
|
||||
{
|
||||
"bundleFormatVersion": "1.0.0",
|
||||
"releaseVersion": "$VERSION",
|
||||
"createdAt": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||||
"sourceCommit": "${{ github.sha }}",
|
||||
"sourceDateEpoch": ${{ steps.epoch.outputs.epoch }},
|
||||
"signingKeyFingerprint": "${{ steps.key-fingerprint.outputs.fingerprint }}"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Copy verification scripts from templates
|
||||
cp src/Attestor/__Libraries/StellaOps.Attestor.EvidencePack/Templates/verify.sh.template "$PACK_DIR/verify.sh"
|
||||
cp src/Attestor/__Libraries/StellaOps.Attestor.EvidencePack/Templates/verify.ps1.template "$PACK_DIR/verify.ps1"
|
||||
chmod +x "$PACK_DIR/verify.sh"
|
||||
|
||||
# Generate VERIFY.md
|
||||
sed -e "s/{{VERSION}}/$VERSION/g" \
|
||||
-e "s/{{SOURCE_COMMIT}}/${{ github.sha }}/g" \
|
||||
-e "s/{{SOURCE_DATE_EPOCH}}/${{ steps.epoch.outputs.epoch }}/g" \
|
||||
-e "s/{{KEY_FINGERPRINT}}/${{ steps.key-fingerprint.outputs.fingerprint }}/g" \
|
||||
-e "s/{{TIMESTAMP}}/$(date -u +"%Y-%m-%dT%H:%M:%SZ")/g" \
|
||||
-e "s/{{BUNDLE_VERSION}}/1.0.0/g" \
|
||||
-e "s/{{REKOR_LOG_ID}}/sigstore/g" \
|
||||
-e "s/{{REKOR_ENTRIES}}/See rekor-proofs\/ directory/g" \
|
||||
src/Attestor/__Libraries/StellaOps.Attestor.EvidencePack/Templates/VERIFY.md.template \
|
||||
> "$PACK_DIR/VERIFY.md"
|
||||
|
||||
echo "✓ Evidence pack built"
|
||||
ls -la "$PACK_DIR/"
|
||||
|
||||
- name: Self-verify evidence pack
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cd "evidence-pack/stella-release-${VERSION}-evidence-pack"
|
||||
|
||||
echo "Running self-verification..."
|
||||
./verify.sh --verbose || {
|
||||
echo "::warning::Self-verification had issues (may be expected if artifacts not fully present)"
|
||||
}
|
||||
|
||||
- name: Create archives
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cd evidence-pack
|
||||
|
||||
# Create tar.gz
|
||||
tar -czvf "stella-release-${VERSION}-evidence-pack.tgz" "stella-release-${VERSION}-evidence-pack"
|
||||
|
||||
# Create zip
|
||||
zip -r "stella-release-${VERSION}-evidence-pack.zip" "stella-release-${VERSION}-evidence-pack"
|
||||
|
||||
echo "✓ Archives created"
|
||||
ls -la *.tgz *.zip
|
||||
|
||||
- name: Upload evidence pack artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: evidence-pack-${{ env.VERSION }}
|
||||
path: |
|
||||
evidence-pack/*.tgz
|
||||
evidence-pack/*.zip
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
|
||||
- name: Attach to GitHub release
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
gh release upload "$TAG" \
|
||||
"evidence-pack/stella-release-${VERSION}-evidence-pack.tgz" \
|
||||
"evidence-pack/stella-release-${VERSION}-evidence-pack.zip" \
|
||||
--clobber || echo "::warning::Could not attach to release"
|
||||
|
||||
echo "✓ Evidence pack attached to release $TAG"
|
||||
|
||||
# ============================================================================
|
||||
# Notify on completion
|
||||
# ============================================================================
|
||||
notify:
|
||||
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
needs: [verify-test-gates, build-evidence-pack]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Report status
|
||||
run: |
|
||||
if [ "${{ needs.build-evidence-pack.result }}" = "success" ]; then
|
||||
echo "✅ Evidence pack generated successfully for version ${{ needs.verify-test-gates.outputs.release_version }}"
|
||||
elif [ "${{ needs.verify-test-gates.result }}" = "failure" ]; then
|
||||
echo "❌ Evidence pack generation blocked: test gates not passed"
|
||||
else
|
||||
echo "⚠️ Evidence pack generation failed or skipped"
|
||||
fi
|
||||
258
.gitea/workflows/verify-reproducibility.yml
Normal file
258
.gitea/workflows/verify-reproducibility.yml
Normal file
@@ -0,0 +1,258 @@
|
||||
# .gitea/workflows/verify-reproducibility.yml
|
||||
# Verifies that builds are reproducible (same inputs produce same outputs)
|
||||
|
||||
name: Verify Reproducibility
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'Directory.Build.props'
|
||||
- 'Directory.Packages.props'
|
||||
- 'global.json'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'Directory.Build.props'
|
||||
- 'Directory.Packages.props'
|
||||
- 'global.json'
|
||||
schedule:
|
||||
# Run weekly to catch any drift
|
||||
- cron: '0 6 * * 0'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOTNET_VERSION: '10.0.100'
|
||||
BUILD_CONFIGURATION: Release
|
||||
|
||||
jobs:
|
||||
verify-deterministic-build:
|
||||
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
include-prerelease: true
|
||||
|
||||
- name: Compute SOURCE_DATE_EPOCH
|
||||
id: epoch
|
||||
run: |
|
||||
EPOCH=$(git show -s --format=%ct HEAD)
|
||||
echo "epoch=$EPOCH" >> "$GITHUB_OUTPUT"
|
||||
echo "SOURCE_DATE_EPOCH=$EPOCH"
|
||||
|
||||
- name: Build pass 1
|
||||
env:
|
||||
SOURCE_DATE_EPOCH: ${{ steps.epoch.outputs.epoch }}
|
||||
CI: true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf build1/
|
||||
|
||||
# Build a representative set of projects
|
||||
PROJECTS=(
|
||||
"src/Attestor/__Libraries/StellaOps.Attestor.EvidencePack/StellaOps.Attestor.EvidencePack.csproj"
|
||||
"src/__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj"
|
||||
"src/Signer/StellaOps.Signer/StellaOps.Signer.Core/StellaOps.Signer.Core.csproj"
|
||||
)
|
||||
|
||||
for project in "${PROJECTS[@]}"; do
|
||||
if [ -f "$project" ]; then
|
||||
name=$(basename "$(dirname "$project")")
|
||||
echo "Building: $name (pass 1)"
|
||||
dotnet build "$project" \
|
||||
--configuration $BUILD_CONFIGURATION \
|
||||
--output "build1/$name" \
|
||||
/p:Deterministic=true \
|
||||
/p:ContinuousIntegrationBuild=true \
|
||||
/p:SourceRevisionId=${{ github.sha }}
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate checksums
|
||||
find build1 -name "*.dll" -type f -exec sha256sum {} \; | sort > build1.checksums
|
||||
echo "Pass 1 checksums:"
|
||||
cat build1.checksums
|
||||
|
||||
- name: Clean build
|
||||
run: |
|
||||
dotnet clean --configuration $BUILD_CONFIGURATION || true
|
||||
rm -rf obj/ bin/ */obj/ */bin/
|
||||
|
||||
- name: Build pass 2
|
||||
env:
|
||||
SOURCE_DATE_EPOCH: ${{ steps.epoch.outputs.epoch }}
|
||||
CI: true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf build2/
|
||||
|
||||
PROJECTS=(
|
||||
"src/Attestor/__Libraries/StellaOps.Attestor.EvidencePack/StellaOps.Attestor.EvidencePack.csproj"
|
||||
"src/__Libraries/StellaOps.Canonical.Json/StellaOps.Canonical.Json.csproj"
|
||||
"src/Signer/StellaOps.Signer/StellaOps.Signer.Core/StellaOps.Signer.Core.csproj"
|
||||
)
|
||||
|
||||
for project in "${PROJECTS[@]}"; do
|
||||
if [ -f "$project" ]; then
|
||||
name=$(basename "$(dirname "$project")")
|
||||
echo "Building: $name (pass 2)"
|
||||
dotnet build "$project" \
|
||||
--configuration $BUILD_CONFIGURATION \
|
||||
--output "build2/$name" \
|
||||
/p:Deterministic=true \
|
||||
/p:ContinuousIntegrationBuild=true \
|
||||
/p:SourceRevisionId=${{ github.sha }}
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate checksums
|
||||
find build2 -name "*.dll" -type f -exec sha256sum {} \; | sort > build2.checksums
|
||||
echo "Pass 2 checksums:"
|
||||
cat build2.checksums
|
||||
|
||||
- name: Compare builds
|
||||
id: compare
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "Comparing build outputs..."
|
||||
|
||||
# Extract just the hashes for comparison (paths may differ)
|
||||
cut -d' ' -f1 build1.checksums | sort > build1.hashes
|
||||
cut -d' ' -f1 build2.checksums | sort > build2.hashes
|
||||
|
||||
if diff build1.hashes build2.hashes > /dev/null; then
|
||||
echo "✅ Builds are reproducible! All checksums match."
|
||||
echo "reproducible=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "❌ Builds are NOT reproducible!"
|
||||
echo ""
|
||||
echo "Differences:"
|
||||
diff build1.checksums build2.checksums || true
|
||||
echo "reproducible=false" >> "$GITHUB_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload build artifacts for debugging
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: reproducibility-debug
|
||||
path: |
|
||||
build1.checksums
|
||||
build2.checksums
|
||||
build1/
|
||||
build2/
|
||||
retention-days: 7
|
||||
|
||||
verify-cli-reproducibility:
|
||||
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||
include-prerelease: true
|
||||
|
||||
- name: Compute SOURCE_DATE_EPOCH
|
||||
id: epoch
|
||||
run: |
|
||||
EPOCH=$(git show -s --format=%ct HEAD)
|
||||
echo "epoch=$EPOCH" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build CLI pass 1
|
||||
env:
|
||||
SOURCE_DATE_EPOCH: ${{ steps.epoch.outputs.epoch }}
|
||||
CI: true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dotnet publish src/Cli/StellaOps.Cli/StellaOps.Cli.csproj \
|
||||
--configuration $BUILD_CONFIGURATION \
|
||||
--runtime linux-x64 \
|
||||
--self-contained false \
|
||||
--output cli-build1 \
|
||||
/p:Deterministic=true \
|
||||
/p:ContinuousIntegrationBuild=true \
|
||||
/p:SourceRevisionId=${{ github.sha }}
|
||||
|
||||
sha256sum cli-build1/StellaOps.Cli.dll > cli-build1.checksum
|
||||
cat cli-build1.checksum
|
||||
|
||||
- name: Clean
|
||||
run: |
|
||||
dotnet clean --configuration $BUILD_CONFIGURATION || true
|
||||
rm -rf src/Cli/StellaOps.Cli/obj src/Cli/StellaOps.Cli/bin
|
||||
|
||||
- name: Build CLI pass 2
|
||||
env:
|
||||
SOURCE_DATE_EPOCH: ${{ steps.epoch.outputs.epoch }}
|
||||
CI: true
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dotnet publish src/Cli/StellaOps.Cli/StellaOps.Cli.csproj \
|
||||
--configuration $BUILD_CONFIGURATION \
|
||||
--runtime linux-x64 \
|
||||
--self-contained false \
|
||||
--output cli-build2 \
|
||||
/p:Deterministic=true \
|
||||
/p:ContinuousIntegrationBuild=true \
|
||||
/p:SourceRevisionId=${{ github.sha }}
|
||||
|
||||
sha256sum cli-build2/StellaOps.Cli.dll > cli-build2.checksum
|
||||
cat cli-build2.checksum
|
||||
|
||||
- name: Compare CLI builds
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
HASH1=$(cut -d' ' -f1 cli-build1.checksum)
|
||||
HASH2=$(cut -d' ' -f1 cli-build2.checksum)
|
||||
|
||||
if [ "$HASH1" = "$HASH2" ]; then
|
||||
echo "✅ CLI builds are reproducible!"
|
||||
echo " Hash: $HASH1"
|
||||
else
|
||||
echo "❌ CLI builds are NOT reproducible!"
|
||||
echo " Pass 1: $HASH1"
|
||||
echo " Pass 2: $HASH2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
report:
|
||||
runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
|
||||
needs: [verify-deterministic-build, verify-cli-reproducibility]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Report results
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo " REPRODUCIBILITY VERIFICATION"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Library builds: ${{ needs.verify-deterministic-build.result }}"
|
||||
echo "CLI builds: ${{ needs.verify-cli-reproducibility.result }}"
|
||||
echo ""
|
||||
|
||||
if [ "${{ needs.verify-deterministic-build.result }}" = "success" ] && \
|
||||
[ "${{ needs.verify-cli-reproducibility.result }}" = "success" ]; then
|
||||
echo "✅ All builds are reproducible!"
|
||||
else
|
||||
echo "❌ Some builds are not reproducible"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user