-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
crates.io token scopes #2947
Merged
Turbo87
merged 20 commits into
rust-lang:master
from
pietroalbini:crates-io-token-scopes
Nov 8, 2022
Merged
crates.io token scopes #2947
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
39b738a
add initial draft of the crates.io token scopes RFC
pietroalbini d0ee1db
fix typo
pietroalbini d4cad47
clarify that token scopes allow interacting with future matches
pietroalbini 4caaba6
improve wording of the motivation section
pietroalbini 905a789
split the publish scope in publish-new and publish-update
pietroalbini 8e5a9ad
change the meaning of * from .+ to .*
pietroalbini 804b6ee
update examples using *
pietroalbini 5c43b7d
mentionn separate confirmation of token actions as a future possibility
pietroalbini cffcaf8
fix regex
pietroalbini 0db28df
add the legacy scope, replacing legacy tokens
pietroalbini 04b48cb
clarify only non-alpha are quoted
pietroalbini f5f12a7
add future possibility for tokens owned by a team
pietroalbini 7cede63
token-scopes: Align endpoint table
Turbo87 826ebc0
token-scopes: Fix typos
Turbo87 67d07c6
token-scopes: Remove endpoint scope preselection paragraph
Turbo87 48d8cf4
token-scopes: Rephrase "crates scope" guide to only allow wildcards a…
Turbo87 fb55797
token-scopes: Split "no change to cargo" part into dedicated paragraph
Turbo87 1d9527e
token-scopes: Remove regular expression implementation details
Turbo87 6aebe17
Merge pull request #2 from Turbo87/token-scopes-improvements
pietroalbini 2804940
token scopes: Adjust RFC number and metadata
Turbo87 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
# crates.io token scopes RFC | ||
|
||
- Feature Name: `crates_io_token_scopes` | ||
- Start Date: (fill me in with today's date, YYYY-MM-DD) | ||
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) | ||
- crates.io issue: [rust-lang/crates.io#0000](https://github.com/rust-lang/crates.io/issues/0000) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
This RFC proposes implementing scopes for crates.io tokens, allowing users to | ||
choose which endpoints the token is allowed to call and which crates it's | ||
allowed to affect. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
While the current implementation of API tokens for crates.io works fine for | ||
developers using the `cargo` CLI on their workstations, it's not acceptable for | ||
CI scenarios, such as publishing crates automatically once a git tag is pushed. | ||
|
||
The implementation of scoped tokens would allow users to restrict the actions a | ||
token can do, decreasing the risk in case of automation bugs or token | ||
compromise. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
During token creation, the user will be prompted to select the permissions the | ||
token will have. Two sets of independent scopes will be available: the | ||
endpoints the token is authorized to call, and the crates the token is allowed | ||
to act on. | ||
|
||
## Endpoint scopes | ||
|
||
The user will be able to choose one or more endpoint scopes. This RFC proposes | ||
adding the following endpoint scopes: | ||
|
||
* **publish-new**: allows publishing new crates | ||
* **publish-update**: allows publishing a new version for existing crates the | ||
user owns | ||
* **yank**: allows yanking and unyanking existing versions of the user's crates | ||
* **change-owners**: allows inviting new owners or removing existing owners | ||
* **legacy**: allows accessing all the endpoints on crates.io except for | ||
creating new tokens, like tokens created befores the implementation of this | ||
RFC. | ||
|
||
More endpoint scopes might be added in the future without the need of a | ||
dedicated RFC. | ||
|
||
The crates.io UI will pre-select the scopes needed by the `cargo` CLI, which at | ||
the time of writing this RFC are `publish-new`, `publish-update`, `yank` and | ||
`change-owners`. The user will have to explicitly opt into extra scopes or the | ||
legacy permission model. | ||
|
||
Tokens created before the implementation of this RFC will default to the legacy | ||
scope. | ||
|
||
## Crates scope | ||
|
||
The user will be able to opt into limiting which crates the token can act on by | ||
defining a crates scope. | ||
|
||
The crates scope can be left empty to allow the token to act on all the crates | ||
Turbo87 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
owned by the user, or it can contain the comma-separated list of crate names | ||
Turbo87 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
the token can interact with. Crate names can contain `*` to match zero or more | ||
characters. | ||
pietroalbini marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
For example, a crates scope of `lazy_static,serde*` allows the token to act on | ||
the `lazy_static` crate or any present or future crates starting with `serde` | ||
(including `serde` itself), if the user is an owner of those crates. | ||
|
||
The crates scope will allow access to all present and future crates matching | ||
it. When an endpoint that doesn't interact with crates is called by a token | ||
with a crates scope, the crates scope will be ignored and the call will be | ||
authorized. | ||
|
||
Tokens created before the implementation of this RFC will default to an empty | ||
crate scope filter (equivalent to no restrictions). | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
Endpoint scopes and crates scope are two completly separate systems, and can be | ||
used independently from one another. Token scopes will be implemented entirely | ||
on the crates.io side, and there will be no change to `cargo` or alternate | ||
registries. | ||
|
||
## Endpoint scopes | ||
|
||
The scopes proposed by this RFC allow access to the following endpoints: | ||
|
||
| Endpoint | Required scope | | ||
| --- | --- | | ||
| `PUT /crates/new` (new crates) | **publish-new** | | ||
| `PUT /crates/new` (existing crates) | **publish-update** | | ||
| `DELETE /crates/:crate_id/:version/yank` | **yank** | | ||
| `PUT /crates/:crate_id/:version/unyank` | **yank** | | ||
| `PUT /crates/:crate_id/owners` | **change-owners** | | ||
| `DELETE /crates/:crate_id/owners` | **change-owners** | | ||
| everything except `PUT /me/tokens` | **legacy** | | ||
|
||
Removing an endpoint from a scope or adding an existing endpoint to an existing | ||
scope will be considered a breaking change. Adding newly created endpoints to | ||
an existing scope will be allowed only at the moment of their creation, if the | ||
crates.io team believes the new endpoint won't grant more privileges than the | ||
existing set of endpoints in that scope. | ||
|
||
## Crates scope | ||
|
||
The pattern for the crate scope is desugared into a regular expression, | ||
Turbo87 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
following these rules: | ||
|
||
* **`^(`** is added at the start of the pattern, and **`)$`** is added at the end of it. | ||
* **`,`** is desugared into `|`, separating multiple patterns. | ||
* **`*`** is desugared into `.*`, matching zero or more characters greedily. | ||
* All other non-alphanumeric characters are quoted to prevent them from having | ||
a special meaning. | ||
|
||
As an example, the following pattern: | ||
|
||
``` | ||
foo,bar-* | ||
``` | ||
|
||
... is desugared into the following regex: | ||
|
||
``` | ||
^(foo|bar\-.*)$ | ||
``` | ||
|
||
Any combination of those characters is allowed, but crates.io might define a | ||
complexity limit for the generated regular expressions. | ||
|
||
The pattern will be evaluated during each API call, and if no match is found | ||
the request will be denied. Because it's evaluated every time, a crates scope | ||
will allow interacting with matching crates published after token creation. | ||
|
||
The check for the crates scope is separate from crate ownership: having a scope | ||
that technically permits to interact with a crate the user doesn't own will be | ||
accepted by the backend, but a warning will be displayed if the pattern doesn't | ||
match any crate owned by the user. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
No drawbacks are known at the time of writing the RFC. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
An alternative implementation for endpoint scopes could be to allow users to | ||
directly choose every endpoint they want to allow for their token, without | ||
having to choose whole groups at a time. This would result in more granularity | ||
and possibly better security, but it would make the UX to create new tokens way | ||
more complex (requiring more choices and a knowledge of the crates.io API). | ||
|
||
An alternative implementation for crate scopes could be to have the user select | ||
the crates they want to allow in the UI instead of having to write a pattern. | ||
That would make creating a token harder for people with lots of crates (which, | ||
in the RFC author's opinion, are more likely to need crate scopes than a person | ||
with just a few crates), and it wouldn't allow new crates matching the pattern | ||
but uploaded after the token's creation from being accessed. | ||
|
||
Finally an alternative could be to do nothing, and encourage users to create | ||
"machine accounts" for each set of crates they own. A drawback of this is that | ||
GitHub's terms of service limit how many accounts a single person could have. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
The endpoint scopes system is heavily inspired by GitHub, while the rest of the | ||
proposal is similar to nuget. Here is how popular package registries implements | ||
scoping: | ||
|
||
* [nuget] (package registry for the .NET ecosystem) implements three endpoint | ||
scopes (publish new packages, publish new versions, unlist packages), has a | ||
mandatory expiration and supports specifying which packages the token applies | ||
to, either by checking boxes or defining a single glob pattern. | ||
[(documentation)][nuget-docs] | ||
* [npm] (package registry for JavaScript) implements a binary | ||
"read-only"/"read-write" permission model, also allowing to restrict the IP | ||
ranges allowed to access the token, but does not allow restricting the | ||
packages a token is allowed to change. [(documentation)][npm-docs] | ||
* [pypi] (package registry for Python) only implements the "upload packages" | ||
permission, and allows to scope each token to a *single* package. | ||
[(documentation)][pypi-docs] | ||
* [rubygems] (package registry for Ruby) and [packagist] (package registry for | ||
PHP) don't implement any kind of scoping for the API tokens. | ||
|
||
[nuget]: https://www.nuget.org/ | ||
[nuget-docs]: https://docs.microsoft.com/en-us/nuget/nuget-org/scoped-api-keys | ||
[npm]: https://www.npmjs.com | ||
[npm-docs]: https://docs.npmjs.com/creating-and-viewing-authentication-tokens | ||
[pypi]: https://pypi.org | ||
[pypi-docs]: https://pypi.org/help/#apitoken | ||
[rubygems]: https://rubygems.org/ | ||
[packagist]: https://packagist.org/ | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
* Are there more scopes that would be useful to implement from the start? | ||
* Is the current behavior of crate scopes on endpoints that don't interact with | ||
crates the best, or should a token with crate scopes prevent access to | ||
endpoints that don't act on crates? | ||
|
||
# Future possibilities | ||
pietroalbini marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[future-possibilities]: #future-possibilities | ||
|
||
A future extension to the crates.io authorization model could be adding an | ||
optional expiration to tokens, to limit the damage over time if a token ends up | ||
being leaked. | ||
|
||
Another extension could be an API endpoint that programmatically creates | ||
short-lived tokens (similar to what AWS STS does for AWS Access Keys), allowing | ||
to develop services that provide tokens with a short expiration time to CI | ||
builds. Such tokens would need to have the same set or a subset of the parent | ||
token's scopes: this RFC should consider that use case and avoid the | ||
implementation of solutions that would make the check hard. | ||
|
||
To increase the security of CI environments even more, we could implement an | ||
option to require a separate confirmation for the actions executed by tokens. | ||
For example, we could send a confirmation email with a link the owners have to | ||
click to actually publish the crate uploaded by CI, preventing any mailicious | ||
action with stolen tokens. | ||
|
||
To remove the need for machine accounts, a future RFC could propose adding API | ||
tokens owned by teams, granting access to all resources owned by that team and | ||
allowing any team member to revoke them. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I propose an additional legacy scope. Removing an endpoint from this scope is not considered a breaking change. During the implementation stage, endpoints can be promoted to their own scope or make cookie-only. Ideally, this scope goes away entirely before landing in production.