diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index 25734c4..7f88eed 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -9,6 +9,10 @@ inputs: description: 'Which ghc version to use when building the package' required: false default: 8.10.7 +outputs: + package-hashes: + description: "base64-encoded sha256 hashes of distribution files" + value: ${{ steps.package-hashes.outputs.package-hashes }} runs: using: composite @@ -26,6 +30,17 @@ runs: shell: bash run: stack --no-terminal --resolver=${{ inputs.resolver }} sdist + - name: Setup dist directory + shell: bash + run: echo "STACK_DIR=$(stack --no-terminal path --dist-dir --resolver=${{ inputs.resolver }})" >> $GITHUB_ENV + + - name: Hash build files for provenance + id: package-hashes + shell: bash + working-directory: ${{ env.STACK_DIR }} + run: | + echo "package-hashes=$(sha256sum *tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT" + - name: Run tests shell: bash run: stack --no-terminal --resolver=${{ inputs.resolver }} test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cf2fa5..f456ac7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: include: - resolver: lts-18.28 @@ -41,6 +42,7 @@ jobs: runs-on: macos-latest strategy: + fail-fast: false matrix: include: - resolver: lts-18.28 diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index cbcccdc..88f161b 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -14,6 +14,8 @@ jobs: permissions: id-token: write contents: read + outputs: + package-hashes: ${{ steps.ci.outputs.package-hashes }} steps: - uses: actions/checkout@v4 @@ -21,6 +23,7 @@ jobs: - uses: ./.github/actions/setup-cache - name: Build and Test + id: ci uses: ./.github/actions/ci - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.0.0 @@ -34,3 +37,14 @@ jobs: with: token: ${{ env.HACKAGE_TOKEN }} dry_run: ${{ inputs.dry_run }} + + release-provenance: + needs: [ 'build-publish' ] + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.7.0 + with: + base64-subjects: "${{ needs.build-publish.outputs.package-hashes }}" + upload-assets: ${{ !inputs.dry_run }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3344066..a7f4f0c 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -13,6 +13,10 @@ jobs: id-token: write # Needed if using OIDC to get release secrets. contents: write # Contents and pull-requests are for release-please to make releases. pull-requests: write + outputs: + release-created: ${{ steps.release.outputs.release_created }} + upload-tag-name: ${{ steps.release.outputs.tag_name }} + package-hashes: ${{ steps.ci.outputs.package-hashes }} steps: - uses: google-github-actions/release-please-action@v3 @@ -48,6 +52,7 @@ jobs: if: ${{ steps.release.outputs.releases_created }} - uses: ./.github/actions/ci + id: ci if: ${{ steps.release.outputs.releases_created }} - uses: ./.github/actions/build-docs @@ -65,3 +70,16 @@ jobs: # If publishing somewhere else, then get the token from SSM. If you need both github, # and another token, then add more tokens to the composite action. token: ${{secrets.GITHUB_TOKEN}} + + release-provenance: + needs: [ 'release-package' ] + if: ${{ needs.release-package.outputs.release-created }} + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.7.0 + with: + base64-subjects: "${{ needs.release-package.outputs.package-hashes }}" + upload-assets: true + upload-tag-name: ${{ needs.release-package.outputs.upload-tag-name }} diff --git a/PROVENANCE.md b/PROVENANCE.md new file mode 100644 index 0000000..05cf415 --- /dev/null +++ b/PROVENANCE.md @@ -0,0 +1,5 @@ +## Verifying SDK build provenance with the SLSA framework + +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. + +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`. diff --git a/README.md b/README.md index 35b4f40..1548ebd 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ We run integration tests for all our SDKs using a centralized test harness. This We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK. +## Verifying SDK build provenance with the SLSA framework + +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. To learn more, see the [provenance guide](PROVENANCE.md). + ## About LaunchDarkly * LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can: