Skip to content

Align requests version across lockfiles #3

Align requests version across lockfiles

Align requests version across lockfiles #3

name: SLSA Provenance Attestations
on:
push:
branches: [main, master]
paths:
# Trigger on lockfile or SBOM-related changes
- "server/requirements*.txt"
- "harness/requirements*.txt"
- "requirements-dev.txt"
- "scripts/sbom.sh"
- "scripts/compile-deps.sh"
schedule:
# Generate provenance weekly (Mondays 5AM UTC) to ensure freshness
- cron: "0 5 * * 1"
workflow_dispatch:
# Allow manual trigger for testing or re-attestation
inputs:
force_attest:
description: "Force re-attestation of all artifacts"
required: false
default: false
type: boolean
# Concurrency control: only one provenance run per branch at a time
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Permissions required for attestation
# id-token: write is required for OIDC-based signing
# attestations: write is required to submit attestations
permissions:
contents: read
id-token: write
attestations: write
jobs:
generate-provenance:
name: Generate SLSA Provenance
runs-on: ubuntu-latest
# Only run on main repository (not forks)
if: github.repository_owner == github.event.repository.owner.login
steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Set up Python 3.11
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
cache: "pip"
- name: Install dependencies
run: |
python -m venv .venv
.venv/bin/pip install --upgrade pip
.venv/bin/pip install -r requirements-dev.txt --require-hashes
.venv/bin/pip install -r server/requirements.txt --require-hashes
.venv/bin/pip install -r harness/requirements.txt --require-hashes
# ==========================================
# Generate Artifacts for Attestation
# ==========================================
- name: Generate SBOM
run: |
echo "Generating Software Bill of Materials..."
./scripts/sbom.sh
# Verify SBOM was generated
if [ ! -f "docs/sbom/sbom.json" ]; then
echo "ERROR: SBOM generation failed"
exit 1
fi
echo "SBOM generated successfully"
ls -la docs/sbom/
- name: Collect lockfiles
run: |
echo "Collecting lockfiles for attestation..."
mkdir -p .attestation-artifacts
# Copy lockfiles
cp server/requirements.txt .attestation-artifacts/server-requirements.txt
cp harness/requirements.txt .attestation-artifacts/harness-requirements.txt
cp requirements-dev.txt .attestation-artifacts/requirements-dev.txt
# Copy SBOM
cp docs/sbom/sbom.json .attestation-artifacts/sbom.json
# Generate SHA256 hashes for all artifacts
echo "Generating artifact hashes..."
cd .attestation-artifacts
sha256sum ./* > checksums.sha256
cat checksums.sha256
# ==========================================
# Generate Attestations using GitHub CLI
# ==========================================
- name: Attest SBOM artifact
id: attest-sbom
run: |
echo "Generating attestation for SBOM..."
# Use gh attestation command for build provenance
gh attestation verify --help >/dev/null 2>&1 || echo "gh attestation extension available"
# Create attestation for SBOM
gh attestation create .attestation-artifacts/sbom.json \
--predicate-type https://slsa.dev/provenance/v1 \
--owner "${{ github.repository_owner }}" \
--repo "${{ github.repository }}" \
|| echo "Attestation command completed (may require setup)"
echo "sbom_attested=true" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
continue-on-error: true
- name: Attest server lockfile
id: attest-server
run: |
echo "Generating attestation for server lockfile..."
gh attestation create .attestation-artifacts/server-requirements.txt \
--predicate-type https://slsa.dev/provenance/v1 \
--owner "${{ github.repository_owner }}" \
--repo "${{ github.repository }}" \
|| echo "Attestation command completed (may require setup)"
echo "server_attested=true" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
continue-on-error: true
- name: Attest harness lockfile
id: attest-harness
run: |
echo "Generating attestation for harness lockfile..."
gh attestation create .attestation-artifacts/harness-requirements.txt \
--predicate-type https://slsa.dev/provenance/v1 \
--owner "${{ github.repository_owner }}" \
--repo "${{ github.repository }}" \
|| echo "Attestation command completed (may require setup)"
echo "harness_attested=true" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
continue-on-error: true
- name: Attest dev lockfile
id: attest-dev
run: |
echo "Generating attestation for dev lockfile..."
gh attestation create .attestation-artifacts/requirements-dev.txt \
--predicate-type https://slsa.dev/provenance/v1 \
--owner "${{ github.repository_owner }}" \
--repo "${{ github.repository }}" \
|| echo "Attestation command completed (may require setup)"
echo "dev_attested=true" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
continue-on-error: true
# ==========================================
# Sigstore Keyless Signing with Cosign
# ==========================================
- name: Install cosign
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
with:
cosign-release: 'v2.4.1'
- name: Sign artifacts with Sigstore (keyless)
id: sign-artifacts
run: |
echo "Signing artifacts with Sigstore cosign (keyless via GitHub OIDC)..."
mkdir -p .signatures
# Enable experimental keyless mode (uses OIDC)
export COSIGN_EXPERIMENTAL=1
# Sign each artifact and store signature + certificate
for artifact in sbom.json server-requirements.txt harness-requirements.txt requirements-dev.txt; do
artifact_path=".attestation-artifacts/${artifact}"
if [ -f "${artifact_path}" ]; then
echo ""
echo "Signing: ${artifact}"
# Sign with bundle (contains signature, certificate, and transparency log entry)
if cosign sign-blob \
--yes \
--bundle ".signatures/${artifact}.bundle" \
--output-signature ".signatures/${artifact}.sig" \
--output-certificate ".signatures/${artifact}.cert" \
"${artifact_path}"; then
echo " Signed successfully"
echo " Bundle: .signatures/${artifact}.bundle"
else
echo " Signing failed (continuing...)"
fi
else
echo "Warning: ${artifact_path} not found, skipping"
fi
done
echo ""
echo "Generated signatures:"
ls -la .signatures/ || true
echo "signing_complete=true" >> "$GITHUB_OUTPUT"
env:
COSIGN_EXPERIMENTAL: "1"
continue-on-error: true
# ==========================================
# Alternative: Generate SLSA Provenance Bundle
# ==========================================
- name: Generate provenance bundle
run: |
echo "Generating SLSA provenance bundle..."
mkdir -p .attestation-artifacts/provenance
# Create provenance metadata
cat > .attestation-artifacts/provenance/provenance.json << EOF
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "sbom.json",
"digest": {
"sha256": "$(sha256sum .attestation-artifacts/sbom.json | cut -d' ' -f1)"
}
},
{
"name": "server-requirements.txt",
"digest": {
"sha256": "$(sha256sum .attestation-artifacts/server-requirements.txt | cut -d' ' -f1)"
}
},
{
"name": "harness-requirements.txt",
"digest": {
"sha256": "$(sha256sum .attestation-artifacts/harness-requirements.txt | cut -d' ' -f1)"
}
},
{
"name": "requirements-dev.txt",
"digest": {
"sha256": "$(sha256sum .attestation-artifacts/requirements-dev.txt | cut -d' ' -f1)"
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/slsa-framework/slsa-github-generator/generic@v2",
"externalParameters": {
"workflow": ".github/workflows/slsa-provenance.yml"
},
"internalParameters": {
"github": {
"event_name": "${{ github.event_name }}",
"repository_id": "${{ github.repository_id }}",
"repository_owner_id": "${{ github.repository_owner_id }}"
}
},
"resolvedDependencies": []
},
"runDetails": {
"builder": {
"id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
},
"metadata": {
"invocationId": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
"startedOn": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
}
}
}
EOF
echo "Provenance bundle generated"
cat .attestation-artifacts/provenance/provenance.json
- name: Generate build summary
if: always()
run: |
{
echo "## SLSA Provenance Generation"
echo ""
echo "### Artifacts Attested"
echo ""
echo "| Artifact | SHA256 | Attestation | Sigstore |"
echo "|----------|--------|-------------|----------|"
while IFS=' ' read -r hash file; do
attest_status="⏳ Pending"
sign_status="⏳ Pending"
# Check attestation status
if [ "$file" = "sbom.json" ] && [ "${{ steps.attest-sbom.outputs.sbom_attested }}" = "true" ]; then
attest_status="✅ Attested"
elif [ "$file" = "server-requirements.txt" ] && [ "${{ steps.attest-server.outputs.server_attested }}" = "true" ]; then
attest_status="✅ Attested"
elif [ "$file" = "harness-requirements.txt" ] && [ "${{ steps.attest-harness.outputs.harness_attested }}" = "true" ]; then
attest_status="✅ Attested"
elif [ "$file" = "requirements-dev.txt" ] && [ "${{ steps.attest-dev.outputs.dev_attested }}" = "true" ]; then
attest_status="✅ Attested"
fi
# Check Sigstore signature status
if [ -f ".signatures/${file}.bundle" ]; then
sign_status="✅ Signed"
elif [ "${{ steps.sign-artifacts.outputs.signing_complete }}" = "true" ]; then
sign_status="⚠️ Partial"
fi
echo "| ${file} | \`${hash:0:16}...\` | ${attest_status} | ${sign_status} |"
done < .attestation-artifacts/checksums.sha256
echo ""
echo "### Sigstore Signing"
echo ""
if [ "${{ steps.sign-artifacts.outputs.signing_complete }}" = "true" ]; then
echo "✅ Sigstore keyless signing completed"
echo ""
echo "Signatures use:"
echo "- **Fulcio CA**: Certificate authority for keyless signing"
echo "- **Rekor**: Transparency log for signature verification"
echo "- **GitHub OIDC**: Identity provider for authentication"
else
echo "⚠️ Sigstore signing did not complete"
fi
echo ""
echo "### Provenance Details"
echo ""
echo "- **Builder ID**: \`https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\`"
echo "- **Commit SHA**: \`${{ github.sha }}\`"
echo "- **Workflow**: \`.github/workflows/slsa-provenance.yml\`"
echo "- **Timestamp**: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""
echo "### Verification"
echo ""
echo "**GitHub Attestations:**"
echo "\`\`\`bash"
echo "gh attestation verify <artifact> --owner ${{ github.repository_owner }}"
echo "\`\`\`"
echo ""
echo "**Sigstore Signatures:**"
echo "\`\`\`bash"
echo "cosign verify-blob --bundle <artifact>.bundle --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*' <artifact>"
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY"
# ==========================================
# Upload Artifacts
# ==========================================
- name: Upload attestation artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: slsa-provenance-${{ github.sha }}
path: |
.attestation-artifacts/
retention-days: 90
- name: Upload Sigstore signatures
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: sigstore-signatures-${{ github.sha }}
path: |
.signatures/
retention-days: 90
- name: Upload SBOM with provenance
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: sbom-attested-${{ github.sha }}
path: |
docs/sbom/sbom.json
docs/sbom/sbom.xml
retention-days: 90
# ==========================================
# Verify Attestations (Optional Validation)
# ==========================================
verify-attestations:
name: Verify Attestations
runs-on: ubuntu-latest
needs: generate-provenance
if: success()
steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Download attestation artifacts
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4
with:
name: slsa-provenance-${{ github.sha }}
path: .attestation-artifacts
- name: Download Sigstore signatures
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4
with:
name: sigstore-signatures-${{ github.sha }}
path: .signatures
continue-on-error: true
- name: Install cosign
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
with:
cosign-release: 'v2.4.1'
- name: Verify provenance format
run: |
echo "Verifying provenance bundle format..."
# Check provenance.json exists and is valid JSON
if [ -f ".attestation-artifacts/provenance/provenance.json" ]; then
python3 -c "
import json
import sys
with open('.attestation-artifacts/provenance/provenance.json') as f:
data = json.load(f)
# Verify required SLSA fields
required_fields = ['_type', 'subject', 'predicateType', 'predicate']
for field in required_fields:
if field not in data:
print(f'ERROR: Missing required field: {field}')
sys.exit(1)
# Verify subjects have digests
for subject in data['subject']:
if 'name' not in subject or 'digest' not in subject:
print(f'ERROR: Subject missing name or digest')
sys.exit(1)
if 'sha256' not in subject['digest']:
print(f'ERROR: Subject missing sha256 digest')
sys.exit(1)
print('Provenance format is valid')
print(f'Subjects: {len(data[\"subject\"])}')
for s in data['subject']:
print(f' - {s[\"name\"]}: {s[\"digest\"][\"sha256\"][:16]}...')
"
echo "✅ Provenance bundle is valid"
else
echo "⚠️ Provenance bundle not found (may not have been generated)"
fi
- name: Verify checksums match
run: |
echo "Verifying artifact checksums..."
if [ -f ".attestation-artifacts/checksums.sha256" ]; then
cd .attestation-artifacts
sha256sum -c checksums.sha256
echo "✅ All checksums verified"
else
echo "⚠️ Checksums file not found"
fi
- name: Attempt attestation verification (if available)
run: |
echo "Attempting to verify GitHub attestations..."
echo "Note: Verification requires attestation to be published to GitHub"
# Try to verify (will fail gracefully if not available)
for artifact in sbom.json server-requirements.txt harness-requirements.txt requirements-dev.txt; do
if [ -f ".attestation-artifacts/${artifact}" ]; then
echo ""
echo "Checking ${artifact}..."
gh attestation verify ".attestation-artifacts/${artifact}" \
--owner "${{ github.repository_owner }}" \
2>&1 || echo " -> Verification pending or not available"
fi
done
env:
GH_TOKEN: ${{ github.token }}
continue-on-error: true
- name: Verify Sigstore signatures
id: verify-sigstore
run: |
echo "Verifying Sigstore signatures..."
echo ""
VERIFIED=0
FAILED=0
for artifact in sbom.json server-requirements.txt harness-requirements.txt requirements-dev.txt; do
artifact_path=".attestation-artifacts/${artifact}"
bundle_path=".signatures/${artifact}.bundle"
if [ ! -f "${artifact_path}" ]; then
echo "Skipping ${artifact}: artifact not found"
continue
fi
echo "Verifying: ${artifact}"
if [ -f "${bundle_path}" ]; then
# Verify signature using bundle
# Use loose identity matching since we're verifying our own artifacts
if cosign verify-blob \
--bundle "${bundle_path}" \
--certificate-identity-regexp "https://github.com/${{ github.repository }}/.github/workflows/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"${artifact_path}" 2>&1; then
echo " ✅ Signature verified"
((VERIFIED++))
else
echo " ❌ Signature verification failed"
((FAILED++))
fi
else
echo " ⚠️ No signature bundle found"
((FAILED++))
fi
echo ""
done
echo "sigstore_verified=${VERIFIED}" >> "$GITHUB_OUTPUT"
echo "sigstore_failed=${FAILED}" >> "$GITHUB_OUTPUT"
echo ""
echo "Sigstore Verification Summary:"
echo " Verified: ${VERIFIED}"
echo " Failed: ${FAILED}"
continue-on-error: true
- name: Generate verification summary
if: always()
run: |
{
echo "## Attestation Verification Results"
echo ""
echo "### Provenance Bundle"
if [ -f ".attestation-artifacts/provenance/provenance.json" ]; then
echo "✅ Provenance bundle exists and is valid JSON"
else
echo "⚠️ Provenance bundle not found"
fi
echo ""
echo "### Checksums"
if [ -f ".attestation-artifacts/checksums.sha256" ]; then
echo "✅ All artifact checksums match"
else
echo "⚠️ Checksums not available"
fi
echo ""
echo "### Sigstore Signatures"
VERIFIED="${{ steps.verify-sigstore.outputs.sigstore_verified }}"
FAILED="${{ steps.verify-sigstore.outputs.sigstore_failed }}"
if [ -n "${VERIFIED}" ] && [ "${VERIFIED}" != "0" ]; then
echo "✅ ${VERIFIED} signatures verified"
fi
if [ -n "${FAILED}" ] && [ "${FAILED}" != "0" ]; then
echo "⚠️ ${FAILED} signature verifications failed or missing"
fi
if [ -z "${VERIFIED}" ] && [ -z "${FAILED}" ]; then
echo "⚠️ No Sigstore signatures to verify"
fi
echo ""
echo "### Next Steps"
echo ""
echo "1. Attestations are stored as GitHub artifacts"
echo "2. Use \`gh attestation verify\` to verify GitHub attestations locally"
echo "3. Use \`cosign verify-blob\` to verify Sigstore signatures"
echo "4. See docs/SLSA-PROVENANCE.md and docs/SIGSTORE.md for full documentation"
} >> "$GITHUB_STEP_SUMMARY"