litellm/.github/workflows/create-release.yml
lee-mcfaul2 ab81dd40ea
ci: add OIDC-rooted keyless release pipeline with SLSA L3 provenance
Publishes LiteLLM to PyPI and GHCR entirely over OIDC, with no
long-lived signing keys or registry credentials:

- PyPI: OIDC Trusted Publisher upload with PEP 740 attestations,
  SLSA L3 build provenance via actions/attest-build-provenance,
  and detached keyless cosign signatures on the sdist and wheel.
- Docker: a reusable build-push-sign workflow for the three images
  (litellm, -database, -non-root), keyless cosign signing, and
  SLSA L3 provenance attached as an OCI referrer.
- Consumer-style verify jobs that re-check every signature and
  attestation the way a downstream user would (gh attestation
  verify, cosign verify), so a broken pipeline fails loudly.
- A regression test enforcing the supply-chain invariants:
  SHA-pinned actions, keyless-only, OIDC-only, no static key.

The static cosign.pub is removed; keyless verification roots in
Fulcio/Rekor, not a checked-in public key.
2026-05-16 00:13:24 -04:00

146 lines
5.8 KiB
YAML

name: Create Release
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g. 1.84.0, 1.84.0rc1, 1.84.0.dev42, 1.84.0-dev.2, 1.84.0.post1; legacy v1.83.10-stable still accepted)"
required: true
type: string
commit_hash:
description: "Full 40-char commit SHA to target"
required: true
type: string
permissions: {}
jobs:
release:
name: Create Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Validate inputs
env:
TAG: ${{ inputs.tag }}
COMMIT_HASH: ${{ inputs.commit_hash }}
run: |
if ! echo "${COMMIT_HASH}" | grep -qE '^[0-9a-f]{40}$'; then
echo "::error::commit_hash must be a full 40-character commit SHA"
exit 1
fi
if ! echo "${TAG}" | grep -qE '^v?[0-9]+\.[0-9]+\.[0-9]+'; then
echo "::error::tag must start with X.Y.Z (optional leading v), e.g. 1.84.0, 1.84.0rc1, 1.84.0.dev42, or v1.83.10-stable"
exit 1
fi
- name: Create release
env:
TAG: ${{ inputs.tag }}
COMMIT_HASH: ${{ inputs.commit_hash }}
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const tag = process.env.TAG;
const commitHash = process.env.COMMIT_HASH;
// Mark RC / dev / nightly / alpha / beta tags as GitHub pre-releases.
// Accept both PEP 440 (`.dev`) and SemVer (`-dev`) separators so tags
// like `1.84.0.dev2` and `1.84.0-dev.2` are both detected.
// PEP 440 post-releases (e.g. `1.84.0.post1`) and legacy `-stable[.patch.N]`
// are stable maintenance releases, not pre-releases.
const isPrerelease = /(?:rc|nightly|alpha|beta|[-.]dev)/i.test(tag);
const verifySection = [
`## Verifying release artifacts`,
``,
`All LiteLLM release artifacts (PyPI sdist+wheel and GHCR Docker images)`,
`are signed keyless via [Sigstore](https://sigstore.dev) and ship with`,
`[SLSA Build L3 provenance](https://slsa.dev). Each signature is bound to`,
`the exact GitHub Actions workflow file at the exact tag that produced it.`,
`Verification works fully offline against the public Sigstore TUF root.`,
``,
`### Docker image signature`,
``,
'```bash',
`cosign verify \\`,
` --certificate-identity-regexp='^https://github\\.com/BerriAI/litellm/\\.github/workflows/_publish-container\\.yml@refs/tags/${tag}$' \\`,
` --certificate-oidc-issuer='https://token.actions.githubusercontent.com' \\`,
` ghcr.io/berriai/litellm:${tag}`,
'```',
``,
`### Docker image SLSA build provenance`,
``,
'```bash',
`gh attestation verify oci://ghcr.io/berriai/litellm:${tag} --owner BerriAI`,
'```',
``,
`### PyPI publish attestation (PEP 740)`,
``,
'```bash',
`# pip 24.1+ automatically verifies PEP 740 attestations on install.`,
`pip install --index-url https://pypi.org/simple/ litellm==${tag.replace(/^v/, '')}`,
'```',
``,
`### PyPI SLSA build provenance (GitHub native)`,
``,
'```bash',
`# Download wheel + sdist from this release first, then:`,
`gh attestation verify <downloaded-wheel-or-sdist> --owner BerriAI`,
'```',
``,
`### PyPI cosign detached signatures (offline-verifiable)`,
``,
'```bash',
`# Each .whl and .tar.gz on this release has a sibling .sigstore bundle.`,
`cosign verify-blob \\`,
` --bundle <artifact>.sigstore \\`,
` --new-bundle-format \\`,
` --certificate-identity-regexp='^https://github\\.com/BerriAI/litellm/\\.github/workflows/publish_to_pypi\\.yml@refs/tags/${tag}$' \\`,
` --certificate-oidc-issuer='https://token.actions.githubusercontent.com' \\`,
` <artifact>`,
'```',
``,
`### Offline / airgap verification`,
``,
`Keyless verification works fully offline given the artifact, the signed`,
`bundle, and a pre-staged Sigstore TUF root (~10 KB). See`,
`[cosign offline verification](https://docs.sigstore.dev/cosign/verifying/verify/#offline-verification).`,
``,
].join('\n');
try {
const response = await github.rest.repos.createRelease({
draft: true,
generate_release_notes: true,
target_commitish: commitHash,
name: tag,
owner: context.repo.owner,
prerelease: isPrerelease,
repo: context.repo.repo,
tag_name: tag,
});
const updatedBody = verifySection + (response.data.body ?? '');
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: response.data.id,
body: updatedBody,
draft: false,
});
} catch (error) {
core.setFailed(error.message);
}
create-branch:
name: Create Release Branch
needs: release
permissions:
contents: write
uses: ./.github/workflows/create-release-branch.yml
with:
tag: ${{ inputs.tag }}
commit_hash: ${{ inputs.commit_hash }}