Not speaking authoritatively here, just my personal opinion, though I am a PyPI administrator so I’ve got some sense of what the PyPI admins might think
. To be clear though, this is a quick look and not any sort of official Yes or No.
I believe the key requirement beyond the technical, is the reliability and security of a given OIDC provider. These providers effectively have the ability to release as any project, and thus their ability to secure and reliably operate the software and infrastructure is of upmost importance.
I’ve never looked at Codeberg before, though I have vaguely heard of Forgejo (which appears to be forked from Gitea, which I have heard of).
I have a number of concerns though from a quick search.
- Codeberg looks pretty new (2019 it appears?), there’s nothing inherently wrong with that, but 7 years isn’t a large amount of time to be able to look back on how well they’ve handled issues in the past.
- I can’t figure out how to actually contact Codeberg itself about any security issues that I might have. The codeberg docs suggest reporting security issues to Forgejo directly rather than to them, but that seems to presume the issue is related to Forgejo directly and isn’t an issue with the Codeberg instance specifically. Codeberg also seems to run a number of pieces of software that are not Forgejo, so it’s unclear how to handle security issues with the non Forgejo components. [1]
- It appears that Codeberg has two different CI options they’re running. One is based on Forgejo Actions and the other is based on Woodpecker. The Woodpecker CI appears to be their primary CI system.
- The Woodpecker option seems to be the primary option, as the Forgejo action one is billed as “very alpha”. As far as I can tell Woodpecker does not provide a machine identity to each CI execution like GitHub/GitLab does, and it only supports static secrets stored in a job. That eliminates Woodpecker as an option for Trusted Publishing on a technical basis before we can even look at non-technical basis.
- Forgejo Actions do seem to support machine identity that we would need, but support for that landed about a month ago and has yet to be released, and Codeberg appears to track the stable release. So at the moment Forgejo actions on Codeberg also don’t provide what we need on a technical level, but will in the future.
- Looking the Codeberg documentation on Forgejo actions, it says “Due to outstanding security issues and bus factor (we need more maintainers for our CI service), we are currently providing hosted Actions in limited fashion”, which does not inspire a lot of confidence in it.
So for me, my gut is that it feels like Codeberg is too new and hasn’t yet established themselves well enough to integrate, particularly given that the technical features we’d even need to do that integration don’t exist on Codeberg yet and have only existed in the software itself for about a month. The fact that I cannot find a way to contact Codeberg with security issues other than the general help@codeberg.org email address (which is marked as responses might take a long time), also does not inspire a lot of confidence in the security maturity of the org.
I think the mission here of Codeberg is great, and I assume things will mature over time, if for no other reason than some of these things are so new they don’t even exist yet
.
This is an interesting statement though, because it suggests you don’t think that Forgejo Actions is mature enough to trust it with your own API tokens, but that seems like a smaller amount of trust from the PyPI side than integrating as a trusted publisher would be.
It’s not well documented, but something that’s non-obvious is that PyPI’s API tokens are actually quite powerful primitives to build this kind of system on top of externally to PyPI itself.
PyPI’s API tokens are Macaroons, and one of the big benefits of Macaroons is that if you possess a macaroon, you can further restrict that API token by adding additional “caveats” [2] to that API token, which creates what is effectively a sub token of the original one. This idea of generating sub tokens can recurse effectively infinitely [3], so if you create a sub token and give that out, then someone who has that sub token can create a sub sub token with further restrictions.
Creating these restricted sub tokens does not require talking to PyPI at all or doing any sort of network request, it’s entirely done locally, in memory. It’s honestly quite cool!
The only thing that the PyPI UI for creating tokens is able to do that you, as a person who possesses a token, isn’t able to do, is create a completely brand new token from scratch [4]. Limiting a token to a project in the PyPI UI is just taking a freshly minted API token that is bound to your user, and adding some caveats to it for you before handing it to you. In the case of a User scoped token, the caveat that we add is basically a no-op (it just asserts that the user that is linked to the macaroon in the PyPI DB hasn’t changed), so it’s effectively an “empty” token with full permissions of your user account to upload– but again you can add your own caveats locally to restrict it further!
We don’t support a ton of caveats currently, basically only 2 (sorta 3?) useful ones:
- Expiration: Not Before/Not After, pretty basic caveat for time boxing a sub token’s lifetime.
- Project Names: This caveat takes a list of project names, and the sub token is limited to only one of the projects named in that caveat.
- Project IDs: Basically the same thing as Project Names, but instead of names it uses the UUID of the Project from PyPI’s database, main benefit is this prevents rebinding attacks where a project gets deleted and recreated by someone else. Not super relevant for a user token since a user token is also bound to only projects your user has, and I’m not sure that it’s possible for a user to get the UUID for a project other than by inspecting an existing token. [5]
Adding more to PyPI is pretty easy, you just implement a dataclass with a verify() method, and then once that lands PyPI would support that caveat type as well.
If you want to inspect a token or restrict it with additional caveats, there’s an unofficial library called pypitoken that makes that pretty easy to do.
A few random important tidbits about Macaroons:
- The design of Macaroons prevents someone from removing existing caveats on a token, they can only append.
- All caveats (even the same one repeated multiple times) are independently checked and they all must evaluate to
True. - Caveats can only restrict the powers of a token, never increase them, so arbitrarily appending caveats is always safe and can only reduce the scope of a given token.
- Macaroons further divides caveats into “first party” and “third party”, currently PyPI only supports first party (which means caveats that PyPI has to natively add support for). It’d be nice to support third party caveats too but it hasn’t been a priority [6].
So you can actually get pretty close to Trusted Publishing from an arbitrary platform without any support from PyPI itself:
- Create an account scoped token (either for your own user, or a robot account that has permission to upload to all projects).
- Create a web service that holds that account scoped token, that accepts OIDC authentication from Codeberg (or any other platform), verifies the claims in the JWT, and then takes the account scoped token and adds a caveat that creates a sub token limited to only the project (or projects) that the JWT claims give permission to do and returns it.
- Have the CI platform pass that token from (2) as the API token for the upload.
And that’s basically it
this is pretty much exactly how trusted publishing works on PyPI, except PyPI gets to skip (1) because PyPI has the ability to mint arbitrary tokens for any project and some minor differences like on PyPI it will appear as if your user uploaded the project whereas PyPI has special support for treating Trusted Publishing as a user-less action.
The only thing you really lose out on is that upload provenance attestations require trusted publishing and are not supported from arbitrary uploads. You can of course have your web service publish their own upload attestations, but they won’t appear on PyPI so they’re not as useful. I’d love to find a way to reasonably support arbitrary sources for upload attestations, but that’s also not been a priority for me.
Anyways, Macaroons are great, and I love talking about them
, but hopefully even if Codeberg isn’t yet ready for being trusted for trusted publishing, you can see how you can get pretty close using the primitives that PyPI already has!
Ironically the security.txt on Codeberg says only to contact Forgejo for Forgejo issues, and to contact the administrators for issues specific to any individual instance for other issues– but I can’t figure out how to do that! ↩︎
Just a fancy Macaroon word for restriction. ↩︎
Although there are practical limits, such as the size of the API token, the number of unique caveat types that are supported, etc. ↩︎
Macaroons start off with a secret known only to PyPI. Some implementations of Macaroons use a single secret to create a “omnipotent” token and then use caveats to restrict that to individual users, etc. That’s a tiny bit simpler and more “pure”, but has problems with revocation, since sub tokens can be minted at will, PyPI has no idea what sub tokens exist, so revocation is difficult and typically requires setting up some sort of revocation service. When we implemented this for PyPI we instead chose to create a fresh secret for each new API token we created, and strongly link that secret to an individual identity (either a user or a OIDC identity) and instead of an omnipotent token, it starts out limited to only what that identity can do. ↩︎
This is used by trusted publishing to prevent rebinding attacks since the tokens minted for trusted publishing aren’t constrained by a user. ↩︎
Third party caveats provide a way for a user to add a restriction to a token to say that the user also has to provide an additional (and specific) token from a third party service. This could be used to say, make a service that wouldn’t give the user that additional token unless it could find the files uploaded to a Code Forge’s releases with signatures or build provenance. ↩︎