Skip to content

Commit 849f8d1

Browse files
authored
Merge pull request #531 from slashdevops/feat/openssf-signed-releases
feat(release): sign binaries (SLSA L3) and container images (cosign) — OpenSSF Phase 3
2 parents 49100a0 + 925a174 commit 849f8d1

4 files changed

Lines changed: 113 additions & 4 deletions

File tree

.github/workflows/container-image.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,30 @@ jobs:
6767
run: |
6868
echo "## Make container-publish" >> $GITHUB_STEP_SUMMARY
6969
GIT_VERSION=${{ github.ref_name }} make container-publish | tee -a $GITHUB_STEP_SUMMARY
70+
71+
- name: Install cosign
72+
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
73+
74+
- name: Cosign sign published container manifest (keyless / Sigstore)
75+
env:
76+
GIT_VERSION: ${{ github.ref_name }}
77+
COSIGN_YES: "true"
78+
run: |
79+
echo "## Cosign sign container manifest" >> $GITHUB_STEP_SUMMARY
80+
# Reuse the same GHCR credentials that podman used in the publish step.
81+
echo "${{ secrets.GITHUB_TOKEN }}" \
82+
| cosign login ghcr.io --username "${{ github.actor }}" --password-stdin
83+
IMAGE="ghcr.io/${{ github.repository }}"
84+
# podman manifest inspect → resolve the multi-arch manifest tag we just
85+
# pushed to its content digest, then sign by digest. Cosign best-practice:
86+
# signing by tag races with subsequent pushes; signing by digest does not.
87+
# --recursive covers the manifest and every per-platform image under it.
88+
for TAG in "${GIT_VERSION}" "latest"; do
89+
DIGEST=$(podman manifest inspect "${IMAGE}:${TAG}" | jq -r '.digest // .manifests[0].digest')
90+
if [ -z "${DIGEST}" ] || [ "${DIGEST}" = "null" ]; then
91+
echo "::error::Could not resolve digest for ${IMAGE}:${TAG}"
92+
exit 1
93+
fi
94+
cosign sign --recursive "${IMAGE}@${DIGEST}" | tee -a $GITHUB_STEP_SUMMARY
95+
echo "**Signed:** \`${IMAGE}@${DIGEST}\` (tag: ${TAG})" >> $GITHUB_STEP_SUMMARY
96+
done

.github/workflows/release.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ jobs:
4141
runs-on: ubuntu-latest
4242
permissions:
4343
contents: read
44+
outputs:
45+
hashes: ${{ steps.hash.outputs.hashes }}
4446
steps:
4547
- name: Check out code
4648
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -63,6 +65,15 @@ jobs:
6365
run: |
6466
GIT_VERSION=${{ github.ref_name }} make build-dist-zip
6567
68+
- name: Generate subject hashes for SLSA provenance
69+
id: hash
70+
run: |
71+
cd dist/assets
72+
# base64-encode the sha256sum output across all release zip artifacts;
73+
# consumed by slsa-github-generator to bind provenance to these subjects.
74+
HASHES=$(sha256sum *.zip | base64 -w0)
75+
echo "hashes=${HASHES}" >> "$GITHUB_OUTPUT"
76+
6677
- name: Upload Distribution files
6778
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
6879
with:
@@ -106,6 +117,21 @@ jobs:
106117
files: |
107118
dist/assets/**
108119
120+
provenance:
121+
name: Generate SLSA provenance for release assets
122+
needs: [build, create-github-release]
123+
permissions:
124+
actions: read # read workflow info for provenance subject
125+
id-token: write # keyless signing via Sigstore Fulcio
126+
contents: write # upload multiple.intoto.jsonl to the release
127+
# slsa-github-generator MUST be pinned by tag, not SHA. The SLSA verifier
128+
# checks the workflow ref against its own allow-list of signed releases.
129+
# See https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#referencing-the-slsa-generator
130+
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
131+
with:
132+
base64-subjects: ${{ needs.build.outputs.hashes }}
133+
upload-assets: true
134+
109135
aws_sam:
110136
name: Create and Publish AWS SAM Serverless Application
111137
needs: create-github-release

SECURITY.md

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
# Security Policy
22

3-
This project is using [Github CodeQL](https://codeql.github.com/) tool to scan the code vulnerabilities and you can see the report in the pipeline
4-
[![CodeQL Analysis](https://github.com/slashdevops/idp-scim-sync/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/slashdevops/idp-scim-sync/actions/workflows/codeql.yml)
3+
This project participates in the [OpenSSF Scorecard](https://github.com/ossf/scorecard) program and ships with several security controls enabled by default:
54

6-
Since this project is always in development and updated to the last release of `go` and its dependencies, the vulnerabilities are always fixed in the last release, so we will support the last 1 major version and its patches.
5+
* [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/slashdevops/idp-scim-sync/badge)](https://securityscorecards.dev/viewer/?uri=github.com/slashdevops/idp-scim-sync) — continuous Scorecard analysis (`.github/workflows/scorecard.yml`).
6+
* [![CodeQL](https://github.com/slashdevops/idp-scim-sync/actions/workflows/codeql.yml/badge.svg)](https://github.com/slashdevops/idp-scim-sync/actions/workflows/codeql.yml) — CodeQL SAST on every push and PR (`.github/workflows/codeql.yml`).
7+
* `govulncheck` runs in the `Build` workflow and blocks PRs that introduce known Go vulnerabilities.
8+
* All GitHub Actions are pinned by full commit SHA; container base images are pinned by `@sha256` digest.
9+
* Release binaries are signed and shipped with SLSA Level 3 provenance (see [Verifying release artifacts](#verifying-release-artifacts) below).
10+
* Container images published to `ghcr.io/slashdevops/idp-scim-sync` are signed with Cosign keyless (Sigstore).
711

812
## Supported Versions
913

14+
The project follows the latest Go release line and updates its dependencies on a continuous basis. Only the most recent minor version receives security fixes.
15+
1016
| Version | Supported |
1117
| ------- | ------------------ |
1218
| 0.44.x | :white_check_mark: |
@@ -21,4 +27,42 @@ Since this project is always in development and updated to the last release of `
2127

2228
## Reporting a Vulnerability
2329

24-
Use the [Project Issues --> Vulnerability](https://github.com/slashdevops/idp-scim-sync/issues/new/choose) to report it
30+
Use the [Project Issues → Vulnerability template](https://github.com/slashdevops/idp-scim-sync/issues/new/choose) to report a security issue. For sensitive reports, please use [GitHub's private vulnerability reporting](https://github.com/slashdevops/idp-scim-sync/security/advisories/new) instead of a public issue.
31+
32+
## Verifying release artifacts
33+
34+
Starting with versions released after [PR-3 of the OpenSSF hardening effort](https://github.com/slashdevops/idp-scim-sync/issues?q=label%3Aopenssf), every release ships with a SLSA Level 3 provenance attestation (`multiple.intoto.jsonl`) and container images are signed with Cosign keyless (Sigstore).
35+
36+
### Binary release zips (SLSA provenance)
37+
38+
Download the release zip(s) plus the `multiple.intoto.jsonl` attestation from the same release page, then verify with [`slsa-verifier`](https://github.com/slsa-framework/slsa-verifier):
39+
40+
```shell
41+
# Install slsa-verifier (one-time)
42+
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
43+
44+
# From the directory where you downloaded the assets
45+
slsa-verifier verify-artifact \
46+
--provenance-path multiple.intoto.jsonl \
47+
--source-uri github.com/slashdevops/idp-scim-sync \
48+
--source-tag v0.45.0 \
49+
idpscim-linux-amd64.zip
50+
```
51+
52+
A successful run prints `PASSED: SLSA verification passed`.
53+
54+
### Container images (Cosign keyless)
55+
56+
Verify the signature on the published multi-arch manifest:
57+
58+
```shell
59+
# Install cosign (one-time)
60+
go install github.com/sigstore/cosign/v2/cmd/cosign@latest
61+
62+
cosign verify \
63+
--certificate-identity-regexp '^https://github\.com/slashdevops/idp-scim-sync/\.github/workflows/' \
64+
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
65+
ghcr.io/slashdevops/idp-scim-sync:v0.45.0
66+
```
67+
68+
Cosign prints the verified signature, certificate, and transparency-log entry.

docs/Whats-New.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ This document tracks notable changes, new features, and bug fixes across release
44

55
## Unreleased
66

7+
### OpenSSF Scorecard Hardening (Phase 3) — Signed releases + SLSA provenance
8+
9+
Closes the **Signed-Releases** Scorecard check (0/10 → 10/10) by adopting two complementary supply-chain primitives:
10+
11+
* **SLSA Level 3 provenance for binary release zips.** A new `provenance` job in `.github/workflows/release.yml` calls the official `slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0` reusable workflow. It binds every release zip's `sha256` to a Sigstore-signed in-toto attestation (`multiple.intoto.jsonl`) and uploads it to the GitHub release. Anyone can verify the link between artifact ↔ source ↔ build with [`slsa-verifier`](https://github.com/slsa-framework/slsa-verifier).
12+
* **Cosign keyless signing for container images.** After `podman manifest push`, `.github/workflows/container-image.yml` resolves the multi-arch manifest to its content digest and signs by digest with `cosign sign --recursive` (keyless via Sigstore Fulcio + Rekor). Both the tagged image and `latest` are signed.
13+
14+
Also added a `Verifying release artifacts` section to `SECURITY.md` with copy-pasteable verification commands. Fixed a stale `codeql-analysis.yml` reference in the same file.
15+
16+
> [!NOTE]
17+
> The `slsa-github-generator` reusable workflow is intentionally pinned by tag (not by SHA). The SLSA verifier validates the workflow ref against its own allow-list of signed releases — SHA-pinning would break verification. This is the only documented exception to the project's SHA-pinning rule.
18+
719
### OpenSSF Scorecard Hardening (Phase 2) — Vulnerability remediation
820

921
Closed the **Vulnerabilities** Scorecard check (0/10 → 10/10). The previous baseline reported 19 known vulnerabilities, all tracing to two indirect dependencies:

0 commit comments

Comments
 (0)