diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index d096f3cd..fa68c42f 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -4,10 +4,6 @@ inputs: workspace_path: description: 'Path to the package to build.' required: true -outputs: - package-hashes: - description: "base64-encoded sha256 hashes of distribution files" - value: ${{ steps.package-hashes.outputs.package-hashes }} runs: using: composite @@ -15,10 +11,3 @@ runs: - name: Build distribution files shell: bash run: make -C ${{ inputs.workspace_path }} build - - - name: Hash build files for provenance - id: package-hashes - shell: bash - working-directory: ${{ inputs.workspace_path }}/dist - run: | - echo "package-hashes=$(sha256sum * | base64 -w0)" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3051c2d2..70b00332 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -58,13 +58,10 @@ jobs: needs: ['release-please'] permissions: id-token: write # Needed for OIDC to get release secrets from AWS. + attestations: write # Needed for actions/attest. if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' }} - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/ci with: @@ -75,6 +72,11 @@ jobs: with: workspace_path: packages/sdk/server-ai + - name: Attest build provenance + uses: actions/attest@v4 + with: + subject-path: 'packages/sdk/server-ai/dist/*' + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' with: @@ -92,13 +94,10 @@ jobs: needs: ['release-please'] permissions: id-token: write # Needed for OIDC to get release secrets from AWS. + attestations: write # Needed for actions/attest. if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' }} - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/ci with: @@ -109,6 +108,11 @@ jobs: with: workspace_path: packages/ai-providers/server-ai-langchain + - name: Attest build provenance + uses: actions/attest@v4 + with: + subject-path: 'packages/ai-providers/server-ai-langchain/dist/*' + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' with: @@ -140,57 +144,28 @@ jobs: workspace_path: ${{ inputs.workspace_path }} - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 - if: ${{ inputs.dry_run != true }} + if: ${{ format('{0}', inputs.dry_run) != 'true' }} name: 'Get PyPI token' with: aws_assume_role: ${{ vars.AWS_ROLE_ARN }} ssm_parameter_pairs: '/production/common/releasing/pypi/token = PYPI_AUTH_TOKEN' - name: Publish to PyPI - if: ${{ inputs.dry_run != true }} + if: ${{ format('{0}', inputs.dry_run) != 'true' }} uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: password: ${{ env.PYPI_AUTH_TOKEN }} packages-dir: ${{ inputs.workspace_path }}/dist/ - release-server-ai-provenance: - needs: ['release-please', 'release-server-ai'] - if: ${{ needs.release-please.outputs.package-server-ai-released == 'true' }} - permissions: - actions: read # Needed for detecting the GitHub Actions environment. - id-token: write # Needed for provenance signing. - contents: write # Needed for uploading assets to the release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 - with: - base64-subjects: "${{ needs.release-server-ai.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-tag-name }} - - release-server-ai-langchain-provenance: - needs: ['release-please', 'release-server-ai-langchain'] - if: ${{ needs.release-please.outputs.package-server-ai-langchain-released == 'true' }} - permissions: - actions: read # Needed for detecting the GitHub Actions environment. - id-token: write # Needed for provenance signing. - contents: write # Needed for uploading assets to the release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0 - with: - base64-subjects: "${{ needs.release-server-ai-langchain.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-langchain-tag-name }} - release-server-ai-openai: runs-on: ubuntu-latest needs: ['release-please'] permissions: id-token: write # Needed for OIDC to get release secrets from AWS. + attestations: write # Needed for actions/attest. if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' }} - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/ci with: @@ -201,6 +176,11 @@ jobs: with: workspace_path: packages/ai-providers/server-ai-openai + - name: Attest build provenance + uses: actions/attest@v4 + with: + subject-path: 'packages/ai-providers/server-ai-openai/dist/*' + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' with: @@ -213,31 +193,15 @@ jobs: password: ${{ env.PYPI_AUTH_TOKEN }} packages-dir: packages/ai-providers/server-ai-openai/dist/ - release-server-ai-openai-provenance: - needs: ['release-please', 'release-server-ai-openai'] - if: ${{ needs.release-please.outputs.package-server-ai-openai-released == 'true' }} - permissions: - actions: read # Needed for detecting the GitHub Actions environment. - id-token: write # Needed for provenance signing. - contents: write # Needed for uploading assets to the release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 - with: - base64-subjects: "${{ needs.release-server-ai-openai.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-openai-tag-name }} - release-server-ai-optimization: runs-on: ubuntu-latest needs: ['release-please'] permissions: id-token: write # Needed for OIDC to get release secrets from AWS. + attestations: write # Needed for actions/attest. if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' }} - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes }} steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: ./.github/actions/ci with: @@ -248,6 +212,11 @@ jobs: with: workspace_path: packages/optimization + - name: Attest build provenance + uses: actions/attest@v4 + with: + subject-path: 'packages/optimization/dist/*' + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: 'Get PyPI token' with: @@ -259,16 +228,3 @@ jobs: with: password: ${{ env.PYPI_AUTH_TOKEN }} packages-dir: packages/optimization/dist/ - - release-server-ai-optimization-provenance: - needs: ['release-please', 'release-server-ai-optimization'] - if: ${{ needs.release-please.outputs.package-server-ai-optimization-released == 'true' }} - permissions: - actions: read # Needed for detecting the GitHub Actions environment. - id-token: write # Needed for provenance signing. - contents: write # Needed for uploading assets to the release. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 - with: - base64-subjects: "${{ needs.release-server-ai-optimization.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-please.outputs.package-server-ai-optimization-tag-name }} diff --git a/packages/sdk/server-ai/PROVENANCE.md b/packages/sdk/server-ai/PROVENANCE.md index def920c4..1483ebec 100644 --- a/packages/sdk/server-ai/PROVENANCE.md +++ b/packages/sdk/server-ai/PROVENANCE.md @@ -1,45 +1,49 @@ -## Verifying SDK build provenance with the SLSA framework +## Verifying SDK build provenance with GitHub artifact attestations -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. +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. -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`. +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/). -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: +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: - ``` # Set the version of the library to verify VERSION=0.16.1 ``` - ``` -# Download package from PyPi +# Download package from PyPI $ pip download --only-binary=:all: launchdarkly-server-sdk-ai==${VERSION} -# Download provenance from Github release into same directory -$ curl --location -O \ - https://github.com/launchdarkly/python-server-sdk-ai/releases/download/${VERSION}/multiple.intoto.jsonl - -# Run slsa-verifier to verify provenance against package artifacts -$ slsa-verifier verify-artifact \ ---provenance-path multiple.intoto.jsonl \ ---source-uri github.com/launchdarkly/python-server-sdk-ai \ -launchdarkly_server_sdk_ai-${VERSION}-py3-none-any.whl +# Verify provenance using the GitHub CLI +$ gh attestation verify launchdarkly_server_sdk_ai-${VERSION}-py3-none-any.whl --owner launchdarkly ``` Below is a sample of expected output. ``` -Verified signature against tlog entry index 150910243 at URL: https://rekor.sigstore.dev/api/v1/log/entries/108e9186e8c5677ab3f14fc82cd3deb769e07ef812cadda623c08c77d4e51fc03124ee7542c470a1 -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 -Verifying artifact launchdarkly_server_sdk_ai-0.3.0-py3-none-any.whl: PASSED +Loaded digest sha256:... for file://launchdarkly_server_sdk_ai-0.16.1-py3-none-any.whl +Loaded 1 attestation from GitHub API + +The following policy criteria will be enforced: +- Predicate type must match:................ https://slsa.dev/provenance/v1 +- Source Repository Owner URI must match:... https://github.com/launchdarkly +- Subject Alternative Name must match regex: (?i)^https://github.com/launchdarkly/ +- OIDC Issuer must match:................... https://token.actions.githubusercontent.com + +✓ Verification succeeded! + +The following 1 attestation matched the policy criteria -PASSED: SLSA verification passed +- Attestation #1 + - Build repo:..... launchdarkly/python-server-sdk-ai + - Build workflow:. .github/workflows/release-please.yml + - Signer repo:.... launchdarkly/python-server-sdk-ai + - Signer workflow: .github/workflows/release-please.yml ``` -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. +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). **Note:** These instructions do not apply when building our libraries from source. diff --git a/release-please-config.json b/release-please-config.json index ef66f9fa..cf0d738a 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -7,7 +7,10 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "extra-files": ["src/ldai/__init__.py", "PROVENANCE.md"], + "extra-files": [ + "src/ldai/__init__.py", + "PROVENANCE.md" + ], "component": "launchdarkly-server-sdk-ai" }, "packages/ai-providers/server-ai-langchain": { @@ -15,7 +18,9 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "extra-files": ["src/ldai_langchain/__init__.py"], + "extra-files": [ + "src/ldai_langchain/__init__.py" + ], "component": "launchdarkly-server-sdk-ai-langchain" }, "packages/ai-providers/server-ai-openai": { @@ -23,7 +28,9 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "extra-files": ["src/ldai_openai/__init__.py"], + "extra-files": [ + "src/ldai_openai/__init__.py" + ], "component": "launchdarkly-server-sdk-ai-openai" }, "packages/optimization": { @@ -31,7 +38,9 @@ "versioning": "default", "bump-minor-pre-major": true, "include-v-in-tag": false, - "extra-files": ["src/ldai_optimization/__init__.py"], + "extra-files": [ + "src/ldai_optimization/__init__.py" + ], "component": "launchdarkly-server-sdk-ai-optimization" } }