Skip to content

Commit 1d03cda

Browse files
committed
Sign docker images using cosign
cosign uses the GitHub action ID token to retrieve an ephemeral code signing certificate from Fulcio, and store the signature in the Rekor transparency log.
1 parent d517b1c commit 1d03cda

File tree

3 files changed

+49
-1
lines changed

3 files changed

+49
-1
lines changed

.github/workflows/build-docker.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ jobs:
3939

4040
# Login to DockerHub first, to avoid rate-limiting
4141
- uses: docker/login-action@v3
42+
# PRs from forks don't have access to secrets, disable this step in that case.
43+
if: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }}
4244
with:
4345
username: astralshbot
4446
password: ${{ secrets.DOCKERHUB_TOKEN_RO }}
@@ -164,6 +166,10 @@ jobs:
164166
needs:
165167
- docker-publish
166168
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
169+
permissions:
170+
packages: write
171+
attestations: write # needed to push image attestations to the Github attestation store
172+
id-token: write # needed for signing the images with GitHub OIDC Token
167173
strategy:
168174
fail-fast: false
169175
matrix:
@@ -198,6 +204,8 @@ jobs:
198204
username: astralshbot
199205
password: ${{ secrets.DOCKERHUB_TOKEN_RO }}
200206

207+
- uses: sigstore/[email protected]
208+
201209
- uses: docker/setup-buildx-action@v3
202210

203211
- uses: docker/login-action@v3
@@ -260,6 +268,7 @@ jobs:
260268
${{ env.TAG_PATTERNS }}
261269
262270
- name: Build and push
271+
id: build-and-push
263272
uses: docker/build-push-action@v6
264273
with:
265274
context: .
@@ -272,6 +281,13 @@ jobs:
272281
labels: ${{ steps.meta.outputs.labels }}
273282
annotations: ${{ steps.meta.outputs.annotations }}
274283

284+
- name: Generate artifact attestation
285+
uses: actions/attest-build-provenance@v2
286+
with:
287+
subject-name: ${{ env.UV_BASE_IMG}}
288+
subject-digest: ${{ steps.build-and-push.outputs.digest }}
289+
# push-to-registry is explicitly not enabled to maintain full control over the top image
290+
275291
# This is effectively a duplicate of `docker-publish` to make https://github.com/astral-sh/uv/pkgs/container/uv
276292
# show the uv base image first since GitHub always shows the last updated image digests
277293
# This works by annotating the original digests (previously non-annotated) which triggers an update to ghcr.io
@@ -283,6 +299,10 @@ jobs:
283299
needs:
284300
- docker-publish-extra
285301
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
302+
permissions:
303+
packages: write
304+
attestations: write # needed to push image attestations to the Github attestation store
305+
id-token: write # needed for signing the images with GitHub OIDC Token
286306
steps:
287307
# Login to DockerHub first, to avoid rate-limiting
288308
- uses: docker/login-action@v3
@@ -330,3 +350,29 @@ jobs:
330350
"${annotations[@]}" \
331351
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
332352
$(printf '${{ env.UV_BASE_IMG }}@sha256:%s ' *)
353+
354+
- name: Share manifest digest
355+
id: manifest-digest
356+
# To sign the manifest, we need it's digest. Unfortunately "docker
357+
# buildx imagetools create" does not (yet) have a clean way of sharing
358+
# the digest of the manifest it creates (see docker/buildx#2407), so
359+
# we use a separate command to retrieve it.
360+
# imagetools inspect [TAG] --format '{{json .Manifest}}' gives us
361+
# the machine readable JSON description of the manifest, and the
362+
# jq command extracts the digest from this. The digest is then
363+
# sent to the Github step output file for sharing with other steps.
364+
run: |
365+
digest="$(
366+
docker buildx imagetools inspect \
367+
"${UV_BASE_IMG}:${DOCKER_METADATA_OUTPUT_VERSION}" \
368+
--format '{{json .Manifest}}' \
369+
| jq -r '.digest'
370+
)"
371+
echo "digest=${digest}" >> "$GITHUB_OUTPUT"
372+
373+
- name: Generate artifact attestation
374+
uses: actions/attest-build-provenance@v2
375+
with:
376+
subject-name: ${{ env.UV_BASE_IMG}}
377+
subject-digest: ${{ steps.manifest-digest.outputs.digest }}
378+
# push-to-registry is explicitly not enabled to maintain full control over the top image

.github/workflows/release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ jobs:
107107
plan: ${{ needs.plan.outputs.val }}
108108
secrets: inherit
109109
permissions:
110+
"attestations": "write"
110111
"contents": "read"
112+
"id-token": "write"
111113
"packages": "write"
112114

113115
# Build and package all the platform-agnostic(ish) things

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ publish-jobs = ["./publish-pypi"]
335335
# Post-announce jobs to run in CI
336336
post-announce-jobs = ["./publish-docs"]
337337
# Custom permissions for GitHub Jobs
338-
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read" } }
338+
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read", id-token = "write", attestations = "write" } }
339339
# Whether to install an updater program
340340
install-updater = false
341341
# Path that installers should place binaries in

0 commit comments

Comments
 (0)