diff --git a/.github/ACTIONS-SECURITY.md b/.github/ACTIONS-SECURITY.md index 90df3372..011f427f 100644 --- a/.github/ACTIONS-SECURITY.md +++ b/.github/ACTIONS-SECURITY.md @@ -32,17 +32,13 @@ Workflow-level permissions default to read-only (`contents: read`). Write permis `.github/dependabot.yml` is configured for the `github-actions` ecosystem with weekly update cadence. Dependabot automatically creates PRs to update SHA pins when new action versions are released. -## Exceptions - -* `slsa-framework/slsa-github-generator@v2.1.0` uses tag-based pinning because GitHub requires tag references for reusable workflow calls (`jobs..uses`). SHA pinning is not supported for this use case. - ## Compliance Verification Verify all actions comply with this policy: ```bash -# Verify no tag-only pins (except slsa-framework and local workflow refs) -grep -rn "uses:" .github/workflows/ | grep -v "uses:.*\./" | grep -v "@[a-f0-9]\{40\}" | grep -v "slsa-framework" +# Verify no tag-only pins (except local workflow refs) +grep -rn "uses:" .github/workflows/ | grep -v "uses:.*\./" | grep -v "@[a-f0-9]\{40\}" # Verify all SHA pins have version comments grep -rn "uses:.*@[a-f0-9]\{40\}" .github/workflows/ | grep -v "#" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c03f09e8..6a3d1933 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -213,6 +213,7 @@ jobs: pages: write contents: read id-token: write + attestations: write # Only deploy if there are documentation changes if: success() uses: ./.github/workflows/pages-deploy.yml diff --git a/.github/workflows/pages-deploy.yml b/.github/workflows/pages-deploy.yml index 847477df..e163bbe3 100644 --- a/.github/workflows/pages-deploy.yml +++ b/.github/workflows/pages-deploy.yml @@ -120,6 +120,7 @@ permissions: contents: read pages: write id-token: write + attestations: write # Allow only one concurrent deployment concurrency: @@ -130,8 +131,6 @@ jobs: build: name: Build Documentation runs-on: ubuntu-latest - outputs: - hashes: ${{ steps.hash.outputs.hashes }} steps: - name: Checkout Repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -737,28 +736,10 @@ jobs: run: | echo "[TARGET] Build validation completed successfully" - - name: Generate artifact hashes for SLSA attestation - id: hash + - name: Create build artifact archive run: | - echo "🔒 Generating artifact hashes for SLSA attestation..." - - # Create a tarball of the built site for consistent hashing tar -czf site-artifact.tar.gz -C ./_site . - # Generate SHA256 hash - HASH=$(sha256sum site-artifact.tar.gz | cut -d' ' -f1) - - # Generate base64 encoded subjects in the format required by SLSA - # Format: "name":"sha256:hash" - SUBJECTS_JSON='{"site-artifact.tar.gz":"sha256:'$HASH'"}' - SUBJECTS_B64=$(echo -n "$SUBJECTS_JSON" | base64 -w0) - - echo "hashes=$SUBJECTS_B64" >> "$GITHUB_OUTPUT" - - echo "✅ Generated artifact hash: $HASH" - echo "📄 Subject: site-artifact.tar.gz" - echo "🔐 SLSA subjects: $SUBJECTS_JSON" - - name: Upload build artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: @@ -772,17 +753,43 @@ jobs: # Adjust this path to match your documentation output directory path: './_site' - # SLSA attestation for documentation artifacts - slsa-attestation: - name: SLSA Attestation - needs: build - permissions: - id-token: write - contents: read - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 - with: - base64-subjects: "${{ needs.build.outputs.hashes }}" - upload-assets: true + attest-documentation: + needs: [build] + if: inputs.deploy_environment == 'production' + runs-on: ubuntu-latest + steps: + - name: Download build artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: documentation-build-${{ github.run_number }} + path: ./attestation-staging + + - name: Attest build provenance + uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 + with: + subject-path: ./attestation-staging/** + + - name: Sparse checkout for SBOM config + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .syft.yaml + sparse-checkout-cone-mode: false + + - name: Generate SBOM + uses: anchore/sbom-action@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0 + with: + artifact-name: documentation-sbom-${{ github.run_number }} + output-file: documentation-sbom.spdx.json + format: spdx-json + config: .syft.yaml + + - name: Attest SBOM + uses: actions/attest@afd638254319277bb3d7f0a234478733e2e46a73 # v2.3.0 + with: + subject-path: documentation-sbom.spdx.json + predicate-type: https://spdx.dev/Document/v2.3 + predicate-path: documentation-sbom.spdx.json deploy: name: Deploy to GitHub Pages @@ -790,7 +797,7 @@ jobs: name: ${{ inputs.deploy_environment }} url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest - needs: [build, slsa-attestation] + needs: [build, attest-documentation] outputs: page_url: ${{ steps.deployment.outputs.page_url }} steps: diff --git a/.syft.yaml b/.syft.yaml new file mode 100644 index 00000000..b9073e20 --- /dev/null +++ b/.syft.yaml @@ -0,0 +1,17 @@ +# Syft SBOM scanner configuration for anchore/sbom-action +# Generates SPDX 2.3 JSON format SBOMs for documentation artifact attestation +output: + - "spdx-json" + +default-catalogers: + - "javascript" + - "python" + - "go" + - "rust-cargo" + +file: + metadata: + digests: + - "sha256" + content: + skip-files-above-size: 1048576 diff --git a/scripts/security/Update-ActionSHAPinning.ps1 b/scripts/security/Update-ActionSHAPinning.ps1 index 5177b440..7ff37726 100755 --- a/scripts/security/Update-ActionSHAPinning.ps1 +++ b/scripts/security/Update-ActionSHAPinning.ps1 @@ -58,12 +58,15 @@ $ActionSHAMap = @{ "actions/setup-python@v4" = "actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236" # v4.8.0 "actions/setup-dotnet@v4" = "actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee" # v4.0.1 "actions/setup-dotnet@v3" = "actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3" # v3.2.0 + "actions/attest@v2" = "actions/attest@afd638254319277bb3d7f0a234478733e2e46a73" # v2.3.0 + "actions/attest-build-provenance@v2" = "actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd" # v2.3.0 "actions/cache@v4" = "actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9" # v4.0.2 "actions/cache@v3" = "actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8" # v3.3.1 "actions/upload-artifact@v4" = "actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808" # v4.3.6 "actions/upload-artifact@v3" = "actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3" # v3.1.3 - "actions/download-artifact@v4" = "actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16" # v4.1.8 + "actions/download-artifact@v4" = "actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093" # v4.3.0 "actions/download-artifact@v3" = "actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a" # v3.0.2 + "anchore/sbom-action@v0" = "anchore/sbom-action@e11c554f704a0b820cbf8c51673f6945e0731532" # v0.20.0 "github/super-linter@v6" = "github/super-linter@4ac6c1e9bce95c4e5e456c8c2c6b468998248097" # v6.8.0 "github/super-linter@v5" = "github/super-linter@45fc0d88288beee4701c62761281edfee85655d7" # v5.7.2 "step-security/harden-runner@v2" = "step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde" # v2.9.1