Skip to content

Commit 04398a4

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 04398a4

File tree

3 files changed

+47
-1
lines changed

3 files changed

+47
-1
lines changed

.github/workflows/build-docker.yml

Lines changed: 44 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:
@@ -260,6 +266,7 @@ jobs:
260266
${{ env.TAG_PATTERNS }}
261267
262268
- name: Build and push
269+
id: build-and-push
263270
uses: docker/build-push-action@v6
264271
with:
265272
context: .
@@ -272,6 +279,13 @@ jobs:
272279
labels: ${{ steps.meta.outputs.labels }}
273280
annotations: ${{ steps.meta.outputs.annotations }}
274281

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