Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign docker images using github attestations #8685

Merged
merged 1 commit into from
Jan 31, 2025

Conversation

mjpieters
Copy link
Contributor

@mjpieters mjpieters commented Oct 29, 2024

GitHub attestation signing uses the GitHub action ID token to retrieve an ephemeral code signing certificate from Fulcio, and store the signature in the Rekor transparency log.

Once an image has been successfully signed, you should be able to verify the signature with:

gh attestation verify --owner astral-uv oci://ghcr.io/astral-sh/uv:latest

Closes #8670

@mjpieters
Copy link
Contributor Author

A word of warning: I am not 100% certain I fully understand everything that the build-docker.yml pipeline does. I'd appreciate some feedback on how the digest of the final docker-republish image manifest is shared with the signing step, for example.

@zanieb
Copy link
Member

zanieb commented Oct 29, 2024

cc @samypr100 (as always, no obligation)

@samypr100
Copy link
Collaborator

samypr100 commented Oct 30, 2024

Note, I'm running a release job on my fork with these changes (9b162d) as I'd like to see it working E2E

https://github.com/samypr100/uv/actions/runs/11593512252

@samypr100
Copy link
Collaborator

@mjpieters

Note, I'm running a release job on my fork with these changes (9b162d) as I'd like to see it working E2E

https://github.com/samypr100/uv/actions/runs/11593512252

@mjpieters signed images are here https://github.com/samypr100/uv/pkgs/container/uv in case you'd like to verify the signatures are working as expected

@mjpieters
Copy link
Contributor Author

@mjpieters

Note, I'm running a release job on my fork with these changes (9b162d) as I'd like to see it working E2E
https://github.com/samypr100/uv/actions/runs/11593512252

@mjpieters signed images are here https://github.com/samypr100/uv/pkgs/container/uv in case you'd like to verify the signatures are working as expected

Excellent! I was working my way through understanding cargo dist in my own fork to try this out but hadn't gotten as far as actually seeing the workflow complete.

The first thing I notice is that the the .sig files are now obscuring the actual docker images on that page, which is a bummer.

The signatures are valid however!

% cosign verify ghcr.io/samypr100/uv:latest --certificate-identity-regexp='.*' --certificate-oidc-issuer-regexp='.*' --output-file /tmp/samypr100-uv-verify.json

Verification for ghcr.io/samypr100/uv:latest --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - The code-signing certificate was verified using trusted certificate authority certificates

% jq -r '.[].optional.Subject' /tmp/samypr100-uv-verify.json
https://github.com/samypr100/uv/.github/workflows/build-docker.yml@refs/heads/docker-test
https://github.com/samypr100/uv/.github/workflows/build-docker.yml@refs/heads/docker-test
https://github.com/samypr100/uv/.github/workflows/build-docker.yml@refs/heads/docker-test

I'll have to see what can be done about those signature files in the registry.

@mjpieters
Copy link
Contributor Author

mjpieters commented Oct 30, 2024

So cosign pushes the signature as a new 'container' to the registry, tagged with sha256-$DIGEST.sig, with the signature contained in the manifest (see https://hackmd.io/@maelvls/how-cosign-uses-registries-to-store-signatures for an overview). It's these tags that are shown at the top of the page by ghcr.io, because it only ever shows the latest tags that have been pushed. The GH packages API and UI (which include the ghcr container registry) is very very sparse and doesn't offer any control over this.

I've looked at a number of other projects that use cosign to sign their containers (of which there are a very large number), and all the ones I looked at so far show these tags at the top. E.g. the Dependabot update bundler (a Github project!) has signature tags at the top. You see this on other registries too, e.g. the boxyhq jackson container on the docker hub.

I see but a few options here:

  • Accept that the signature tags are going to be pushed last and end up on top.
  • Experiment with the Github packages API to delete and then restore specific tags, to see if that puts them back at the top again.
  • Tag the containers after signing. Build and push the images, store the digests, sign the digests, then handle the version and 'latest' tags afterwards.
  • Tell cosign to push signatures to a separate registry. This would complicate verification of the images, I don't quite know how that'd work.
  • Decline this PR and forgo the security of having signed containers (which would be a huge pity, security is important, especially in supply-chain tools like uv).

@zanieb
Copy link
Member

zanieb commented Oct 30, 2024

We're already pushing the latest tag separately for this reason, can we push the signature before that final image tag?

@mjpieters
Copy link
Contributor Author

We're already pushing the latest tag separately for this reason, can we push the signature before that final image tag?

You push not only a tag, but a manifest as well. You'd have to separate the two steps; push the manifest without tags and then tag the digest of the manifest afterwards. It could be that you can retag the same digest; I'll try this out in my own fork registry.

@samypr100
Copy link
Collaborator

We're already pushing the latest tag separately for this reason, can we push the signature before that final image tag?

You push not only a tag, but a manifest as well. You'd have to separate the two steps; push the manifest without tags and then tag the digest of the manifest afterwards. It could be that you can retag the same digest; I'll try this out in my own fork registry.

I think the main issue here is how GH ranks packages and gives no control over the ordering to orgs/users of what's displayed, so we have to rely on these ordering hacks related to publishing.

@mjpieters
Copy link
Contributor Author

I think the main issue here is how GH ranks packages and gives no control over the ordering to orgs/users of what's displayed, so we have to rely on these ordering hacks related to publishing.

and from my experiments it is clear that it is order that the manifest were pushed to the registry that is used to list them. I tried deleting then restoring, and retagging (moving tags to another manifest then back again to the right manifest), and nothing shifts the signature from the top.

So the only thing that would work is to build and not push, sign (and let cosign push the signature manifest), and only then push the actual image manifest.

Comment on lines +42 to +43
# PRs from forks don't have access to secrets, disable this step in that case.
if: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }}
Copy link
Contributor Author

@mjpieters mjpieters Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this in here so this workflow would at least build the docker image in this PR. The jobs otherwise would just fail with a Password required error.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, I think there's 3 other places in this file missing this conditional (if we're in favor of adding it)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think there was any point as those 3 all are part of jobs that only run on release.

Copy link
Member

@zanieb zanieb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I might wait for @samypr100 to give it a look too.

.github/workflows/build-docker.yml Outdated Show resolved Hide resolved
.github/workflows/build-docker.yml Outdated Show resolved Hide resolved
Comment on lines +42 to +43
# PRs from forks don't have access to secrets, disable this step in that case.
if: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, I think there's 3 other places in this file missing this conditional (if we're in favor of adding it)

@samypr100
Copy link
Collaborator

samypr100 commented Jan 31, 2025

Good news is that this approach does keep the tags working as-is.

I tried it on a test run here https://github.com/samypr100/uv/actions/runs/13066181068

Which did keep the package versions tags intact https://github.com/samypr100/uv/pkgs/container/uv

Verifications also work!

$ gh attestation verify --owner samypr100 oci://ghcr.io/samypr100/uv:latest                          [23:14:17]
Loaded digest sha256:ebd769519134c730eb1af61ddd06a8f5629f5082d0c2d8f02a6d577fe4b2a9dc for oci://ghcr.io/samypr100/uv:latest
Loaded 1 attestation from GitHub API
✓ Verification succeeded!

sha256:ebd769519134c730eb1af61ddd06a8f5629f5082d0c2d8f02a6d577fe4b2a9dc was attested by:
REPO          PREDICATE_TYPE                  WORKFLOW
samypr100/uv  https://slsa.dev/provenance/v1  .github/workflows/build-docker.yml@refs/heads/cosign-docker-images

$ gh attestation verify --owner samypr100 oci://ghcr.io/samypr100/uv:0.5.26                          [23:14:23]
Loaded digest sha256:ebd769519134c730eb1af61ddd06a8f5629f5082d0c2d8f02a6d577fe4b2a9dc for oci://ghcr.io/samypr100/uv:0.5.26
Loaded 1 attestation from GitHub API
✓ Verification succeeded!

sha256:ebd769519134c730eb1af61ddd06a8f5629f5082d0c2d8f02a6d577fe4b2a9dc was attested by:
REPO          PREDICATE_TYPE                  WORKFLOW
samypr100/uv  https://slsa.dev/provenance/v1  .github/workflows/build-docker.yml@refs/heads/cosign-docker-images

$ gh attestation verify --owner samypr100 oci://ghcr.io/samypr100/uv:0.5                             [23:14:27]
Loaded digest sha256:ebd769519134c730eb1af61ddd06a8f5629f5082d0c2d8f02a6d577fe4b2a9dc for oci://ghcr.io/samypr100/uv:0.5
Loaded 1 attestation from GitHub API
✓ Verification succeeded!

sha256:ebd769519134c730eb1af61ddd06a8f5629f5082d0c2d8f02a6d577fe4b2a9dc was attested by:
REPO          PREDICATE_TYPE                  WORKFLOW
samypr100/uv  https://slsa.dev/provenance/v1  .github/workflows/build-docker.yml@refs/heads/cosign-docker-images

$ gh attestation verify --owner samypr100 oci://ghcr.io/samypr100/uv:python3.13-bookworm             [23:14:41]
Loaded digest sha256:5537896c0c8bd2351350d32dea35c590997c418e954fb5b31d6fcbfd96126562 for oci://ghcr.io/samypr100/uv:python3.13-bookworm
Loaded 1 attestation from GitHub API
✓ Verification succeeded!

sha256:5537896c0c8bd2351350d32dea35c590997c418e954fb5b31d6fcbfd96126562 was attested by:
REPO          PREDICATE_TYPE                  WORKFLOW
samypr100/uv  https://slsa.dev/provenance/v1  .github/workflows/build-docker.yml@refs/heads/cosign-docker-images

...

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.
@zanieb zanieb added releases Related to building and distributing release artifacts of uv security labels Jan 31, 2025
@zanieb zanieb merged commit 47f80a6 into astral-sh:main Jan 31, 2025
96 checks passed
@zanieb
Copy link
Member

zanieb commented Jan 31, 2025

Would you mind following with a brief guide on how to verify images in https://docs.astral.sh/uv/guides/integration/docker/ ?

@mjpieters mjpieters changed the title Sign docker images using cosign Sign docker images using github attestations Jan 31, 2025
@mjpieters mjpieters deleted the cosign-docker-images branch January 31, 2025 15:54
@mjpieters
Copy link
Contributor Author

Would you mind following with a brief guide on how to verify images in https://docs.astral.sh/uv/guides/integration/docker/ ?

I'll see what I can do!

@mjpieters
Copy link
Contributor Author

@zanieb PR up at #11140

tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Feb 4, 2025
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [astral-sh/uv](https://github.com/astral-sh/uv) | patch | `0.5.25` -> `0.5.27` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>astral-sh/uv (astral-sh/uv)</summary>

### [`v0.5.27`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0527)

[Compare Source](astral-sh/uv@0.5.26...0.5.27)

##### Enhancements

-   Avoid setting permissions during tar extraction ([#&#8203;11191](astral-sh/uv#11191))
-   Remove warnings for missing lower bounds ([#&#8203;11195](astral-sh/uv#11195))
-   Update PubGrub to set-based outdated priority tracking ([#&#8203;11169](astral-sh/uv#11169))
-   Improve error messages for `uv pip install` with `--extra` or `--all-extras` and invalid sources ([#&#8203;11193](astral-sh/uv#11193))
-   Sign Docker images using GitHub attestations ([#&#8203;8685](astral-sh/uv#8685))

##### Preview features

-   Don't expand self-referential extras in the build backend ([#&#8203;11142](astral-sh/uv#11142))

##### Performance

-   Filter discovered Python executables by source before querying ([#&#8203;11143](astral-sh/uv#11143))
-   Optimize exclusion computation for markers ([#&#8203;11158](astral-sh/uv#11158))
-   Use Astral-maintained `tokio-tar` fork ([#&#8203;11174](astral-sh/uv#11174))
-   Remove unneeded `.clone()` ([#&#8203;11127](astral-sh/uv#11127))

##### Bug fixes

-   Fix relative paths in bytecode compilation ([#&#8203;11177](astral-sh/uv#11177))
-   Percent-decode URLs in canonical comparisons ([#&#8203;11088](astral-sh/uv#11088))
-   Respect concurrency limits in parallel index fetch ([#&#8203;11182](astral-sh/uv#11182))
-   Use wire JSON schema for conflict items ([#&#8203;11196](astral-sh/uv#11196))
-   Use explicit `_GLibCVersion` tuple in uv-python crate ([#&#8203;11122](astral-sh/uv#11122))

##### Documentation

-   Add Git SHA locking behavior to docs ([#&#8203;11125](astral-sh/uv#11125))
-   Add best-practice flags to `pip install` example in troubleshooting guide ([#&#8203;11194](astral-sh/uv#11194))
-   Set `VIRTUAL_ENV` in Jupyter kernels ([#&#8203;11155](astral-sh/uv#11155))
-   Add instructions for deactivating an environment ([#&#8203;11200](astral-sh/uv#11200))

### [`v0.5.26`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0526)

[Compare Source](astral-sh/uv@0.5.25...0.5.26)

##### Enhancements

-   Add support for `uvx python` ([#&#8203;11076](astral-sh/uv#11076))
-   Allow `--no-dev --invert` in `uv tree` ([#&#8203;11068](astral-sh/uv#11068))
-   Update `uv python install --reinstall` to reinstall all previous versions ([#&#8203;11072](astral-sh/uv#11072))
-   Consistently write log messages with capitalized first word ([#&#8203;11111](astral-sh/uv#11111))
-   Suggest `--build-backend` when `--backend` is passed to `uv init` ([#&#8203;10958](astral-sh/uv#10958))
-   Improve retry trace message ([#&#8203;11108](astral-sh/uv#11108))

##### Performance

-   Remove unnecessary UTF-8 conversion in hash parsing ([#&#8203;11110](astral-sh/uv#11110))

##### Bug fixes

-   Ignore non-hash fragments in HTML API responses ([#&#8203;11107](astral-sh/uv#11107))
-   Avoid resolving symbolic links when querying Python interpreters ([#&#8203;11083](astral-sh/uv#11083))
-   Avoid sharing state between universal and non-universal resolves ([#&#8203;11051](astral-sh/uv#11051))
-   Error when `--script` is passing a non-PEP 723 script ([#&#8203;11118](astral-sh/uv#11118))
-   Make metadata deserialization failures non-fatal in the cache ([#&#8203;11105](astral-sh/uv#11105))
-   Mark metadata as dynamic when reading from built wheel cache ([#&#8203;11046](astral-sh/uv#11046))
-   Propagate credentials for `<index>/simple` to `<index>/...` endpoints ([#&#8203;11074](astral-sh/uv#11074))
-   Fix conflicting extra bug during `uv sync` ([#&#8203;11075](astral-sh/uv#11075))

##### Documentation

-   Add PyTorch XPU instructions to the PyTorch guide ([#&#8203;11109](astral-sh/uv#11109))
-   Add docs for signal handling ([#&#8203;11041](astral-sh/uv#11041))
-   Explain build frontend vs. build backend ([#&#8203;11094](astral-sh/uv#11094))
-   Fix formatting of `RUST_LOG` documentation ([#&#8203;10053](astral-sh/uv#10053))
-   Fix typo in `--no-deps` description ([#&#8203;11073](astral-sh/uv#11073))
-   Reflow CLI documentation comments ([#&#8203;11040](astral-sh/uv#11040))
-   Shorten "Using existing Python versions" nav item so it fits on one line ([#&#8203;11077](astral-sh/uv#11077))
-   Some minor touch-ups to the Python install guide ([#&#8203;11116](astral-sh/uv#11116))
-   Update Dependabot tracking issue link ([#&#8203;11054](astral-sh/uv#11054))
-   Update documentation for running in a container ([#&#8203;11052](astral-sh/uv#11052))
-   Upgrade PyTorch version in documentation ([#&#8203;11114](astral-sh/uv#11114))
-   Use `sys_platform` in lieu of `platform_system` in PyTorch docs ([#&#8203;11113](astral-sh/uv#11113))
-   Use positive (rather than negative) markers in PyTorch examples ([#&#8203;11112](astral-sh/uv#11112))
-   Fix unnecessary backslashes in brackets ([#&#8203;11059](astral-sh/uv#11059))
-   Suggest setting copy link mode in GitLab integration guide ([#&#8203;11067](astral-sh/uv#11067))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNDMuMCIsInVwZGF0ZWRJblZlciI6IjM5LjE1Ni4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
zanieb pushed a commit that referenced this pull request Feb 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
releases Related to building and distributing release artifacts of uv security
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FR: signed docker images
3 participants