Skip to content

Commit 3f8d13c

Browse files
authored
ci: switch from SLSA provenance to actions/attest with subject-path (#118)
1 parent 2ba2e9f commit 3f8d13c

4 files changed

Lines changed: 64 additions & 106 deletions

File tree

.github/actions/build/action.yml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,10 @@ inputs:
44
workspace_path:
55
description: 'Path to the package to build.'
66
required: true
7-
outputs:
8-
package-hashes:
9-
description: "base64-encoded sha256 hashes of distribution files"
10-
value: ${{ steps.package-hashes.outputs.package-hashes }}
117

128
runs:
139
using: composite
1410
steps:
1511
- name: Build distribution files
1612
shell: bash
1713
run: make -C ${{ inputs.workspace_path }} build
18-
19-
- name: Hash build files for provenance
20-
id: package-hashes
21-
shell: bash
22-
working-directory: ${{ inputs.workspace_path }}/dist
23-
run: |
24-
echo "package-hashes=$(sha256sum * | base64 -w0)" >> "$GITHUB_OUTPUT"

.github/workflows/release-please.yml

Lines changed: 26 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,10 @@ jobs:
5858
needs: ['release-please']
5959
permissions:
6060
id-token: write # Needed for OIDC to get release secrets from AWS.
61+
attestations: write # Needed for actions/attest.
6162
if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' }}
62-
outputs:
63-
package-hashes: ${{ steps.build.outputs.package-hashes }}
6463
steps:
6564
- uses: actions/checkout@v4
66-
with:
67-
fetch-depth: 0
6865

6966
- uses: ./.github/actions/ci
7067
with:
@@ -75,6 +72,11 @@ jobs:
7572
with:
7673
workspace_path: packages/sdk/server-ai
7774

75+
- name: Attest build provenance
76+
uses: actions/attest@v4
77+
with:
78+
subject-path: 'packages/sdk/server-ai/dist/*'
79+
7880
- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
7981
name: 'Get PyPI token'
8082
with:
@@ -92,13 +94,10 @@ jobs:
9294
needs: ['release-please']
9395
permissions:
9496
id-token: write # Needed for OIDC to get release secrets from AWS.
97+
attestations: write # Needed for actions/attest.
9598
if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' }}
96-
outputs:
97-
package-hashes: ${{ steps.build.outputs.package-hashes }}
9899
steps:
99100
- uses: actions/checkout@v4
100-
with:
101-
fetch-depth: 0
102101

103102
- uses: ./.github/actions/ci
104103
with:
@@ -109,6 +108,11 @@ jobs:
109108
with:
110109
workspace_path: packages/ai-providers/server-ai-langchain
111110

111+
- name: Attest build provenance
112+
uses: actions/attest@v4
113+
with:
114+
subject-path: 'packages/ai-providers/server-ai-langchain/dist/*'
115+
112116
- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
113117
name: 'Get PyPI token'
114118
with:
@@ -140,57 +144,28 @@ jobs:
140144
workspace_path: ${{ inputs.workspace_path }}
141145

142146
- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
143-
if: ${{ inputs.dry_run != true }}
147+
if: ${{ format('{0}', inputs.dry_run) != 'true' }}
144148
name: 'Get PyPI token'
145149
with:
146150
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
147151
ssm_parameter_pairs: '/production/common/releasing/pypi/token = PYPI_AUTH_TOKEN'
148152

149153
- name: Publish to PyPI
150-
if: ${{ inputs.dry_run != true }}
154+
if: ${{ format('{0}', inputs.dry_run) != 'true' }}
151155
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
152156
with:
153157
password: ${{ env.PYPI_AUTH_TOKEN }}
154158
packages-dir: ${{ inputs.workspace_path }}/dist/
155159

156-
release-server-ai-provenance:
157-
needs: ['release-please', 'release-server-ai']
158-
if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' }}
159-
permissions:
160-
actions: read # Needed for detecting the GitHub Actions environment.
161-
id-token: write # Needed for provenance signing.
162-
contents: write # Needed for uploading assets to the release.
163-
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
164-
with:
165-
base64-subjects: "${{ needs.release-server-ai.outputs.package-hashes }}"
166-
upload-assets: true
167-
upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-tag-name }}
168-
169-
release-server-ai-langchain-provenance:
170-
needs: ['release-please', 'release-server-ai-langchain']
171-
if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' }}
172-
permissions:
173-
actions: read # Needed for detecting the GitHub Actions environment.
174-
id-token: write # Needed for provenance signing.
175-
contents: write # Needed for uploading assets to the release.
176-
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0
177-
with:
178-
base64-subjects: "${{ needs.release-server-ai-langchain.outputs.package-hashes }}"
179-
upload-assets: true
180-
upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-langchain-tag-name }}
181-
182160
release-server-ai-openai:
183161
runs-on: ubuntu-latest
184162
needs: ['release-please']
185163
permissions:
186164
id-token: write # Needed for OIDC to get release secrets from AWS.
165+
attestations: write # Needed for actions/attest.
187166
if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' }}
188-
outputs:
189-
package-hashes: ${{ steps.build.outputs.package-hashes }}
190167
steps:
191168
- uses: actions/checkout@v4
192-
with:
193-
fetch-depth: 0
194169

195170
- uses: ./.github/actions/ci
196171
with:
@@ -201,6 +176,11 @@ jobs:
201176
with:
202177
workspace_path: packages/ai-providers/server-ai-openai
203178

179+
- name: Attest build provenance
180+
uses: actions/attest@v4
181+
with:
182+
subject-path: 'packages/ai-providers/server-ai-openai/dist/*'
183+
204184
- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
205185
name: 'Get PyPI token'
206186
with:
@@ -213,31 +193,15 @@ jobs:
213193
password: ${{ env.PYPI_AUTH_TOKEN }}
214194
packages-dir: packages/ai-providers/server-ai-openai/dist/
215195

216-
release-server-ai-openai-provenance:
217-
needs: ['release-please', 'release-server-ai-openai']
218-
if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' }}
219-
permissions:
220-
actions: read # Needed for detecting the GitHub Actions environment.
221-
id-token: write # Needed for provenance signing.
222-
contents: write # Needed for uploading assets to the release.
223-
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
224-
with:
225-
base64-subjects: "${{ needs.release-server-ai-openai.outputs.package-hashes }}"
226-
upload-assets: true
227-
upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-openai-tag-name }}
228-
229196
release-server-ai-optimization:
230197
runs-on: ubuntu-latest
231198
needs: ['release-please']
232199
permissions:
233200
id-token: write # Needed for OIDC to get release secrets from AWS.
201+
attestations: write # Needed for actions/attest.
234202
if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' }}
235-
outputs:
236-
package-hashes: ${{ steps.build.outputs.package-hashes }}
237203
steps:
238204
- uses: actions/checkout@v4
239-
with:
240-
fetch-depth: 0
241205

242206
- uses: ./.github/actions/ci
243207
with:
@@ -248,6 +212,11 @@ jobs:
248212
with:
249213
workspace_path: packages/optimization
250214

215+
- name: Attest build provenance
216+
uses: actions/attest@v4
217+
with:
218+
subject-path: 'packages/optimization/dist/*'
219+
251220
- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
252221
name: 'Get PyPI token'
253222
with:
@@ -259,16 +228,3 @@ jobs:
259228
with:
260229
password: ${{ env.PYPI_AUTH_TOKEN }}
261230
packages-dir: packages/optimization/dist/
262-
263-
release-server-ai-optimization-provenance:
264-
needs: ['release-please', 'release-server-ai-optimization']
265-
if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' }}
266-
permissions:
267-
actions: read # Needed for detecting the GitHub Actions environment.
268-
id-token: write # Needed for provenance signing.
269-
contents: write # Needed for uploading assets to the release.
270-
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
271-
with:
272-
base64-subjects: "${{ needs.release-server-ai-optimization.outputs.package-hashes }}"
273-
upload-assets: true
274-
upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-optimization-tag-name }}
Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,49 @@
1-
## Verifying SDK build provenance with the SLSA framework
1+
## Verifying SDK build provenance with GitHub artifact attestations
22

3-
LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages.
3+
LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages.
44

5-
As part of [SLSA requirements for level 3 compliance](https://slsa.dev/spec/v1.0/requirements), LaunchDarkly publishes provenance about our SDK package builds using [GitHub's generic SLSA3 provenance generator](https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#generation-of-slsa3-provenance-for-arbitrary-projects) for distribution alongside our packages. These attestations are available for download from the GitHub release page for the release version under Assets > `multiple.intoto.jsonl`.
5+
LaunchDarkly publishes provenance about our SDK package builds using [GitHub's `actions/attest` action](https://github.com/actions/attest). These attestations are stored in GitHub's attestation API and can be verified using the [GitHub CLI](https://cli.github.com/).
66

7-
To verify SLSA provenance attestations, we recommend using [slsa-verifier](https://github.com/slsa-framework/slsa-verifier). Example usage for verifying a package is included below:
7+
To verify build provenance attestations, we recommend using the [GitHub CLI `attestation verify` command](https://cli.github.com/manual/gh_attestation_verify). Example usage for verifying SDK packages is included below:
88

99
<!-- x-release-please-start-version -->
10-
1110
```
1211
# Set the version of the library to verify
1312
VERSION=0.16.1
1413
```
15-
1614
<!-- x-release-please-end -->
1715

1816
```
19-
# Download package from PyPi
17+
# Download package from PyPI
2018
$ pip download --only-binary=:all: launchdarkly-server-sdk-ai==${VERSION}
2119
22-
# Download provenance from Github release into same directory
23-
$ curl --location -O \
24-
https://github.com/launchdarkly/python-server-sdk-ai/releases/download/${VERSION}/multiple.intoto.jsonl
25-
26-
# Run slsa-verifier to verify provenance against package artifacts
27-
$ slsa-verifier verify-artifact \
28-
--provenance-path multiple.intoto.jsonl \
29-
--source-uri github.com/launchdarkly/python-server-sdk-ai \
30-
launchdarkly_server_sdk_ai-${VERSION}-py3-none-any.whl
20+
# Verify provenance using the GitHub CLI
21+
$ gh attestation verify launchdarkly_server_sdk_ai-${VERSION}-py3-none-any.whl --owner launchdarkly
3122
```
3223

3324
Below is a sample of expected output.
3425

3526
```
36-
Verified signature against tlog entry index 150910243 at URL: https://rekor.sigstore.dev/api/v1/log/entries/108e9186e8c5677ab3f14fc82cd3deb769e07ef812cadda623c08c77d4e51fc03124ee7542c470a1
37-
Verified build using builder "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.0.0" at commit 8e2d4094b4833d075e70dfce43bbc7176008c4a1
38-
Verifying artifact launchdarkly_server_sdk_ai-0.3.0-py3-none-any.whl: PASSED
27+
Loaded digest sha256:... for file://launchdarkly_server_sdk_ai-0.16.1-py3-none-any.whl
28+
Loaded 1 attestation from GitHub API
29+
30+
The following policy criteria will be enforced:
31+
- Predicate type must match:................ https://slsa.dev/provenance/v1
32+
- Source Repository Owner URI must match:... https://github.com/launchdarkly
33+
- Subject Alternative Name must match regex: (?i)^https://github.com/launchdarkly/
34+
- OIDC Issuer must match:................... https://token.actions.githubusercontent.com
35+
36+
✓ Verification succeeded!
37+
38+
The following 1 attestation matched the policy criteria
3939
40-
PASSED: SLSA verification passed
40+
- Attestation #1
41+
- Build repo:..... launchdarkly/python-server-sdk-ai
42+
- Build workflow:. .github/workflows/release-please.yml
43+
- Signer repo:.... launchdarkly/python-server-sdk-ai
44+
- Signer workflow: .github/workflows/release-please.yml
4145
```
4246

43-
Alternatively, to verify the provenance manually, the SLSA framework specifies [recommendations for verifying build artifacts](https://slsa.dev/spec/v1.0/verifying-artifacts) in their documentation.
47+
For more information, see [GitHub's documentation on verifying artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli).
4448

4549
**Note:** These instructions do not apply when building our libraries from source.

release-please-config.json

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,40 @@
77
"versioning": "default",
88
"bump-minor-pre-major": true,
99
"include-v-in-tag": false,
10-
"extra-files": ["src/ldai/__init__.py", "PROVENANCE.md"],
10+
"extra-files": [
11+
"src/ldai/__init__.py",
12+
"PROVENANCE.md"
13+
],
1114
"component": "launchdarkly-server-sdk-ai"
1215
},
1316
"packages/ai-providers/server-ai-langchain": {
1417
"release-type": "python",
1518
"versioning": "default",
1619
"bump-minor-pre-major": true,
1720
"include-v-in-tag": false,
18-
"extra-files": ["src/ldai_langchain/__init__.py"],
21+
"extra-files": [
22+
"src/ldai_langchain/__init__.py"
23+
],
1924
"component": "launchdarkly-server-sdk-ai-langchain"
2025
},
2126
"packages/ai-providers/server-ai-openai": {
2227
"release-type": "python",
2328
"versioning": "default",
2429
"bump-minor-pre-major": true,
2530
"include-v-in-tag": false,
26-
"extra-files": ["src/ldai_openai/__init__.py"],
31+
"extra-files": [
32+
"src/ldai_openai/__init__.py"
33+
],
2734
"component": "launchdarkly-server-sdk-ai-openai"
2835
},
2936
"packages/optimization": {
3037
"release-type": "python",
3138
"versioning": "default",
3239
"bump-minor-pre-major": true,
3340
"include-v-in-tag": false,
34-
"extra-files": ["src/ldai_optimization/__init__.py"],
41+
"extra-files": [
42+
"src/ldai_optimization/__init__.py"
43+
],
3544
"component": "launchdarkly-server-sdk-ai-optimization"
3645
}
3746
}

0 commit comments

Comments
 (0)