test fixes and new product advisories work

This commit is contained in:
master
2026-01-28 02:30:48 +02:00
parent 82caceba56
commit 644887997c
288 changed files with 69101 additions and 375 deletions

View 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!"

View 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

View 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

View 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