Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 82 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ permissions:
id-token: write

jobs:
# Immutable releases reject asset uploads once a release is published, so the
# release must be a draft while this workflow attaches assets. It is published
# by the publish-release job at the end.
check-draft-release:
runs-on: ubuntu-latest
permissions:
contents: write # needed to read draft releases
steps:
- name: Check that the release is a draft
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ inputs.tag }}
run: |
if ! is_draft=$(gh release view "$TAG" --json isDraft --jq .isDraft); then
echo "::error::No release found for $TAG. Create it as a draft (do not publish it), then rerun."
exit 1
fi
if [ "$is_draft" != "true" ]; then
echo "::error::Release $TAG is already published. Assets can only be added to a draft release."
exit 1
fi
# build-lbox:
# permissions:
# actions: read
Expand All @@ -39,6 +61,7 @@ jobs:
# secrets: inherit
build:
# needs: ['build-lbox']
needs: ['check-draft-release']
runs-on: ubuntu-latest
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
Expand Down Expand Up @@ -67,17 +90,35 @@ jobs:
with:
name: build
path: ./dist
# The SLSA generators must be referenced by tag, not SHA: they use the tag to
# fetch and verify their builder, and slsa-verifier checks it.
provenance_python:
needs: [build]
permissions:
actions: read
contents: write
contents: write # Required even with upload-assets: false, see slsa-framework/slsa-github-generator#2044
id-token: write # Needed to access the workflow's OIDC identity.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0
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
upload-tag-name: ${{ inputs.tag }} # Tag from the initiation of the workflow
provenance-name: multiple.intoto.jsonl
upload-assets: false # Uploaded to the draft release by publish-python-package-to-release
attest_python:
needs: [build]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Needed to access the workflow's OIDC identity.
attestations: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: build
path: ./dist
- name: Attest build provenance
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: dist/*
test-build:
if: ${{ !inputs.skip-tests }}
needs: ['build']
Expand Down Expand Up @@ -147,7 +188,13 @@ jobs:
rye run pytest tests/data
publish-python-package-to-release:
runs-on: ubuntu-latest
needs: ['build']
# Upload only after tests pass, so a failed test run leaves the draft empty.
needs: ['build', 'provenance_python', 'test-build']
if: |
always() &&
needs.build.result == 'success' &&
needs.provenance_python.result == 'success' &&
(needs.test-build.result == 'success' || needs.test-build.result == 'skipped')
permissions:
contents: write
steps:
Expand All @@ -158,11 +205,18 @@ jobs:
with:
name: build
path: ./artifact
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: multiple.intoto.jsonl
path: ./artifact
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
# --clobber lets "Re-run failed jobs" retry a partial upload. The release
# is still a draft here (check-draft-release), so nothing public changes.
- name: Upload dist to release
run: |
gh release upload ${{ inputs.tag }} ./artifact/*
gh release upload "$TAG" ./artifact/* --clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
pypi-publish:
runs-on: ubuntu-latest
needs: ['build', 'test-build']
Expand All @@ -184,6 +238,25 @@ jobs:
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
with:
packages-dir: artifact/
publish-release:
runs-on: ubuntu-latest
needs: ['publish-python-package-to-release', 'attest_python', 'pypi-publish']
# always() is needed because pypi-publish can follow a skipped test-build.
if: |
always() &&
needs.publish-python-package-to-release.result == 'success' &&
needs.attest_python.result == 'success' &&
needs.pypi-publish.result == 'success'
permissions:
contents: write
steps:
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ inputs.tag }}
run: |
gh release edit "$TAG" --draft=false --verify-tag
container-publish:
runs-on: ubuntu-latest
needs: ['build', 'test-build']
Expand Down Expand Up @@ -242,11 +315,13 @@ jobs:

provenance_container:
needs: [container-publish]
# always() is needed because container-publish can follow a skipped test-build.
if: always() && needs.container-publish.result == 'success'
permissions:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
with:
image: ${{ needs. container-publish.outputs.image }}
digest: ${{ needs. container-publish.outputs.digest }}
Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ As a note, the repository is organized as a monorepo, with each module residing

Your contributions will be released as soon as it is approved and merged into the `develop` branch. Please consult the [Labelbox](https://docs.labelbox.com/docs/contacting-customer-support) team for if you need a more specific timeframe of when your contributions may be released.

For maintainers cutting a release:

1. Push the release tag (for example `v7.18.0`).
2. Create a GitHub release for that tag with the release notes and save it as a **draft**. Do not publish it.
3. Run the Github Publish Workflow with that tag. It uploads the wheel and sdist to the draft release, attests them, publishes to PyPI, and then publishes the release.

The release must stay a draft until the workflow publishes it: releases are immutable once published, so no assets can be added afterwards. If the workflow fails, the release stays a draft. Fix the problem, then use **Re-run failed jobs** on the same run. Do not start a new run once the PyPI publish has succeeded: PyPI rejects a version that already exists, so the new run cannot publish the release. If that happens, publish the draft release by hand.

## Github Workflows

![workflow](./docs/github-workflow.svg)
Expand Down
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,30 @@ c.InteractiveShellApp.exec_lines = [

## Provenance

To enhance the software supply chain security of Labelbox's users, as of v.3.72.2, every Labelbox SDK release contains a [SLSA Level 3 Provenance](https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md) document.
This document provides detailed information about the build process, including the repository and branch from which the package was generated.
To enhance the software supply chain security of Labelbox's users, every Labelbox SDK release is built by the [publish workflow](.github/workflows/publish.yml) and comes with signed build provenance:

By using the [SLSA framework's official verifier](https://github.com/slsa-framework/slsa-verifier), you can verify the provenance document to ensure that the package is from a trusted source. Verifying the provenance helps confirm that the package has not been tampered with and was built in a secure environment.
* A [SLSA Level 3 Provenance](https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md) document (`multiple.intoto.jsonl`) attached to the GitHub release.
* A [GitHub artifact attestation](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations) for the wheel and sdist, which records the repository, workflow and commit that built them.
* A [PyPI attestation](https://docs.pypi.org/attestations/) for each file published to PyPI.

Example of usage for the v.3.72.2 release wheel:
Verifying the provenance confirms that the package was built from this repository by our release workflow and has not been tampered with. For example, for the wheel of a given release:

```
export VERSION=3.72.2 # sdk release version
export TAG=v.3.72.2 # github tag
export VERSION=<version> # sdk release version
export TAG=v${VERSION} # github tag
pip download --no-deps labelbox==${VERSION}

# GitHub attestation, with the GitHub CLI
gh attestation verify ./labelbox-${VERSION}-py3-none-any.whl \
--repo Labelbox/labelbox-python \
--signer-workflow Labelbox/labelbox-python/.github/workflows/publish.yml

# SLSA provenance, with slsa-verifier
curl --location -O \
https://github.com/Labelbox/labelbox-python/releases/download/${TAG}/multiple.intoto.jsonl

slsa-verifier verify-artifact --source-branch develop --builder-id 'https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v2.0.0' --source-uri "git+https://github.com/Labelbox/labelbox-python" --provenance-path multiple.intoto.jsonl ./labelbox-${VERSION}-py3-none-any.whl
slsa-verifier verify-artifact --source-branch develop \
--source-uri "git+https://github.com/Labelbox/labelbox-python" \
--provenance-path multiple.intoto.jsonl ./labelbox-${VERSION}-py3-none-any.whl
```

GitHub attestations start with the first release after v7.17.0. Releases v7.7.0 through v7.17.0 have only the PyPI attestation, because the SLSA provenance upload was broken for them. Releases v.3.72.2 through v7.6.0 have the SLSA provenance and verify the same way.
Loading