Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions .github/workflows/ci-release.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# =============================================================================
# CI Release — release lane for SBOM/provenance placeholder and container
# CI Release — release lane for SBOM/provenance generation and container
# build verification. Complements release-security.yml with a lighter-weight
# build-verification pass suitable for tag/release automation.
#
# SBOM/provenance: implemented in OPS-11 (#103) via reusable-sbom-provenance.yml
# Follow-through:
# - OPS-11 (#103): SBOM/provenance generation and attestation policy
# - SEC-09 (#106) / OPS-17 (#148): dependency vulnerability policy + automation
# =============================================================================

Expand Down Expand Up @@ -76,15 +76,16 @@ jobs:
- Backend: restore + build (Release) passed
- Frontend: npm ci + vite build passed

### SBOM / Provenance (placeholder)

SBOM generation and provenance attestation are not yet wired.
Follow-through tracked in:
- `#103` OPS-11 SBOM/provenance workflow
- `#106` SEC-09 dependency vulnerability policy
- `#148` OPS-17 dependency update automation
SBOM and provenance artifacts are generated in the `sbom-provenance` job.
EOF

sbom-provenance:
name: SBOM and Release Provenance
uses: ./.github/workflows/reusable-sbom-provenance.yml
with:
artifact-name: release-sbom-provenance
retention-days: 90

container-images:
name: Container Image Artifacts
uses: ./.github/workflows/reusable-container-images.yml
3 changes: 2 additions & 1 deletion .github/workflows/ci-required.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@
#
# ci-release.yml release lane (tag/release/manual)
# ├── release build verification (backend + frontend)
# ├── SBOM/provenance placeholder (follow-through: #103, #106, #148)
# ├── reusable-sbom-provenance.yml (CycloneDX SBOM + SLSA provenance, #103)
# └── reusable-container-images.yml
#
# release-security.yml release security deep scan (tag/release/manual)
# ├── dependency inventory + vulnerability scan + enforcement
# ├── reusable-sbom-provenance.yml (CycloneDX SBOM + SLSA provenance, #103)
# └── reusable-container-images.yml
#
# pages-frontend.yml GitHub Pages deploy (main push, frontend paths)
Expand Down
15 changes: 12 additions & 3 deletions .github/workflows/release-security.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# =============================================================================
# Release Security — deep dependency inventory and vulnerability signal lane.
# Runs on tag push, release publish, and manual dispatch.
# Release Security — deep dependency inventory, vulnerability signal, and SBOM
# generation lane. Runs on tag push, release publish, and manual dispatch.
# For lighter release build verification, see ci-release.yml.
# Topology context documented in ci-required.yml header.
# SBOM/provenance: OPS-11 (#103) via reusable-sbom-provenance.yml
# =============================================================================

name: Release Security
Expand Down Expand Up @@ -126,8 +127,9 @@ jobs:
- container image artifact generation and checksum output via reusable container workflow

Planned follow-through:
- OPS-11 (`#103`): SBOM/provenance generation and attestation policy hardening
- SEC-09 (`#106`) / OPS-17 (`#148`): dependency vulnerability policy + automation tightening

SBOM and provenance artifacts are generated in the `sbom-provenance` job.
EOF

- name: Upload dependency and security artifacts
Expand All @@ -138,6 +140,13 @@ jobs:
path: /tmp/release-security
if-no-files-found: ignore

sbom-provenance:
name: SBOM and Release Provenance
uses: ./.github/workflows/reusable-sbom-provenance.yml
with:
artifact-name: release-security-sbom-provenance
retention-days: 90

container-images:
name: Container Image Artifacts
uses: ./.github/workflows/reusable-container-images.yml
254 changes: 254 additions & 0 deletions .github/workflows/reusable-sbom-provenance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
# =============================================================================
# Reusable SBOM and Release Provenance
#
# Generates CycloneDX SBOMs for backend (.NET) and frontend (npm) dependencies,
# plus a build provenance manifest capturing build environment metadata.
#
# Implements: OPS-11 (#103)
# =============================================================================

name: Reusable SBOM and Provenance

on:
workflow_call:
inputs:
dotnet-version:
required: false
type: string
default: 8.0.x
node-version:
required: false
type: string
default: 24.13.1
artifact-name:
required: false
type: string
default: sbom-provenance-artifacts
retention-days:
required: false
type: number
default: 90
fail-on-error:
description: Fail the workflow if SBOM generation encounters errors
required: false
type: boolean
default: false

permissions:
contents: read

jobs:
sbom-provenance:
name: SBOM and Provenance
runs-on: ubuntu-latest
timeout-minutes: 20
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
SBOM_DIR: ${{ github.workspace }}/artifacts/sbom-provenance
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ inputs.dotnet-version }}
cache: true
cache-dependency-path: |
backend/Taskdeck.sln
backend/**/*.csproj

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
cache: npm
cache-dependency-path: frontend/taskdeck-web/package-lock.json

- name: Create output directory
run: mkdir -p "$SBOM_DIR"

- name: Restore backend dependencies
run: dotnet restore backend/Taskdeck.sln

- name: Install CycloneDX .NET tool
run: dotnet tool install --global CycloneDX

- name: Generate backend SBOM (CycloneDX)
id: backend_sbom
shell: bash
env:
SBOM_VERSION: ${{ github.ref_name }}
run: |
set +e
dotnet CycloneDX backend/Taskdeck.sln \
--output "$SBOM_DIR" \
--filename backend-sbom.json \
--json \
--include-project-references \
--set-type application \
--set-name "Taskdeck-Backend" \
--set-version "$SBOM_VERSION" 2> "$SBOM_DIR/backend-sbom.stderr"
exit_code=$?
printf '%s' "$exit_code" > "$SBOM_DIR/backend-sbom.exitcode"
if [ "$exit_code" -ne 0 ]; then
echo "::warning::Backend SBOM generation exited with code $exit_code"
else
echo "Backend SBOM generated successfully"
fi
exit "$exit_code"
continue-on-error: true

- name: Install frontend dependencies
working-directory: frontend/taskdeck-web
run: npm ci

- name: Generate frontend SBOM (CycloneDX)
id: frontend_sbom
shell: bash
run: |
set +e
npx --yes @cyclonedx/cyclonedx-npm \
--output-file "$SBOM_DIR/frontend-sbom.json" \
--spec-version 1.5 \
--omit dev \
--package-dir frontend/taskdeck-web 2> "$SBOM_DIR/frontend-sbom.stderr"
exit_code=$?
printf '%s' "$exit_code" > "$SBOM_DIR/frontend-sbom.exitcode"
if [ "$exit_code" -ne 0 ]; then
echo "::warning::Frontend SBOM generation exited with code $exit_code"
else
echo "Frontend SBOM generated successfully"
fi
exit "$exit_code"
continue-on-error: true

- name: Generate build provenance manifest
shell: bash
env:
PROVENANCE_REF_NAME: ${{ github.ref_name }}
PROVENANCE_REPO: ${{ github.repository }}
PROVENANCE_REF: ${{ github.ref }}
PROVENANCE_SHA: ${{ github.sha }}
PROVENANCE_WORKFLOW: ${{ github.workflow }}
PROVENANCE_RUN_ID: ${{ github.run_id }}
PROVENANCE_RUN_NUMBER: ${{ github.run_number }}
PROVENANCE_RUN_ATTEMPT: ${{ github.run_attempt }}
PROVENANCE_ACTOR: ${{ github.actor }}
PROVENANCE_EVENT: ${{ github.event_name }}
PROVENANCE_DOTNET: ${{ inputs.dotnet-version }}
PROVENANCE_NODE: ${{ inputs.node-version }}
run: |
node -e "
const provenance = {
_type: 'https://slsa.dev/provenance/v1',
subject: [{ name: 'Taskdeck', version: process.env.PROVENANCE_REF_NAME }],
predicateType: 'https://slsa.dev/provenance/v1',
predicate: {
buildDefinition: {
buildType: 'https://github.com/' + process.env.PROVENANCE_REPO + '/blob/main/.github/workflows/reusable-sbom-provenance.yml',
resolvedDependencies: [{
uri: 'git+https://github.com/' + process.env.PROVENANCE_REPO + '@' + process.env.PROVENANCE_REF,
digest: { sha1: process.env.PROVENANCE_SHA }
}]
},
runDetails: {
builder: { id: 'https://github.com/' + process.env.PROVENANCE_REPO + '/actions/runs/' + process.env.PROVENANCE_RUN_ID },
metadata: {
invocationId: process.env.PROVENANCE_RUN_ID + '/' + process.env.PROVENANCE_RUN_ATTEMPT,
startedOn: new Date().toISOString()
},
byproducts: [
{ name: 'backend-sbom.json', mediaType: 'application/vnd.cyclonedx+json' },
{ name: 'frontend-sbom.json', mediaType: 'application/vnd.cyclonedx+json' }
]
}
},
buildMetadata: {
repository: process.env.PROVENANCE_REPO,
ref: process.env.PROVENANCE_REF,
sha: process.env.PROVENANCE_SHA,
workflow: process.env.PROVENANCE_WORKFLOW,
runId: process.env.PROVENANCE_RUN_ID,
runNumber: process.env.PROVENANCE_RUN_NUMBER,
runAttempt: process.env.PROVENANCE_RUN_ATTEMPT,
actor: process.env.PROVENANCE_ACTOR,
eventName: process.env.PROVENANCE_EVENT,
runner: { os: 'ubuntu-latest', arch: 'x64' },
tools: {
dotnetVersion: process.env.PROVENANCE_DOTNET,
nodeVersion: process.env.PROVENANCE_NODE,
cyclonedxDotnet: 'latest',
cyclonedxNpm: 'latest'
}
}
};
require('fs').writeFileSync(
process.env.SBOM_DIR + '/build-provenance.json',
JSON.stringify(provenance, null, 2) + '\n'
);
console.log('Build provenance manifest generated');
"

- name: Generate artifact checksums
shell: bash
run: |
cd "$SBOM_DIR"
sha256sum ./*.json > checksums.sha256 2>/dev/null || true
echo "Checksums:"
cat checksums.sha256

- name: Write SBOM summary
shell: bash
env:
SUMMARY_REF: ${{ github.ref }}
SUMMARY_SHA: ${{ github.sha }}
SUMMARY_RUN_ID: ${{ github.run_id }}
SUMMARY_ARTIFACT_NAME: ${{ inputs.artifact-name }}
SUMMARY_RETENTION: ${{ inputs.retention-days }}
run: |
backend_exit=$(cat "$SBOM_DIR/backend-sbom.exitcode" 2>/dev/null || echo "N/A")
frontend_exit=$(cat "$SBOM_DIR/frontend-sbom.exitcode" 2>/dev/null || echo "N/A")

backend_status="passed"
frontend_status="passed"
if [ "$backend_exit" != "0" ]; then backend_status="failed (exit $backend_exit)"; fi
if [ "$frontend_exit" != "0" ]; then frontend_status="failed (exit $frontend_exit)"; fi

cat <<EOF >> "$GITHUB_STEP_SUMMARY"
## SBOM and Release Provenance

| Artifact | Format | Status |
|----------|--------|--------|
| Backend SBOM | CycloneDX JSON | $backend_status |
| Frontend SBOM | CycloneDX JSON | $frontend_status |
| Build Provenance | SLSA v1 JSON | generated |
| Checksums | SHA-256 | generated |

**Ref:** \`$SUMMARY_REF\` | **SHA:** \`$SUMMARY_SHA\` | **Run:** \`$SUMMARY_RUN_ID\`

Artifacts are uploaded as \`$SUMMARY_ARTIFACT_NAME\` with $SUMMARY_RETENTION-day retention.

Policy: see \`docs/ops/SBOM_RELEASE_PROVENANCE.md\`
EOF

- name: Enforce SBOM generation success
if: inputs.fail-on-error
shell: bash
run: |
backend_exit=$(cat "$SBOM_DIR/backend-sbom.exitcode" 2>/dev/null || echo "1")
frontend_exit=$(cat "$SBOM_DIR/frontend-sbom.exitcode" 2>/dev/null || echo "1")
if [ "$backend_exit" != "0" ] || [ "$frontend_exit" != "0" ]; then
echo "::error::SBOM generation failed. Backend exit: $backend_exit, Frontend exit: $frontend_exit"
echo "Review stderr artifacts for details."
exit 1
fi

- name: Upload SBOM and provenance artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.artifact-name }}
path: ${{ env.SBOM_DIR }}
if-no-files-found: warn
retention-days: ${{ inputs.retention-days }}
Loading
Loading