Skip to content

Commit 0d84bbc

Browse files
authored
Merge pull request #2947 from pietroalbini/crates-io-token-scopes
crates.io token scopes
2 parents 01d542f + 2804940 commit 0d84bbc

File tree

1 file changed

+202
-0
lines changed

1 file changed

+202
-0
lines changed

text/2947-crates-io-token-scopes.md

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# crates.io token scopes RFC
2+
3+
- Feature Name: `crates_io_token_scopes`
4+
- Start Date: 2020-06-24
5+
- RFC PR: [rust-lang/rfcs#2947](https://github.com/rust-lang/rfcs/pull/2947)
6+
- crates.io issue: [rust-lang/crates.io#5443](https://github.com/rust-lang/crates.io/issues/5443)
7+
8+
# Summary
9+
[summary]: #summary
10+
11+
This RFC proposes implementing scopes for crates.io tokens, allowing users to
12+
choose which endpoints the token is allowed to call and which crates it's
13+
allowed to affect.
14+
15+
# Motivation
16+
[motivation]: #motivation
17+
18+
While the current implementation of API tokens for crates.io works fine for
19+
developers using the `cargo` CLI on their workstations, it's not acceptable for
20+
CI scenarios, such as publishing crates automatically once a git tag is pushed.
21+
22+
The implementation of scoped tokens would allow users to restrict the actions a
23+
token can do, decreasing the risk in case of automation bugs or token
24+
compromise.
25+
26+
# Guide-level explanation
27+
[guide-level-explanation]: #guide-level-explanation
28+
29+
During token creation, the user will be prompted to select the permissions the
30+
token will have. Two sets of independent scopes will be available: the
31+
endpoints the token is authorized to call, and the crates the token is allowed
32+
to act on.
33+
34+
## Endpoint scopes
35+
36+
The user will be able to choose one or more endpoint scopes. This RFC proposes
37+
adding the following endpoint scopes:
38+
39+
* **publish-new**: allows publishing new crates
40+
* **publish-update**: allows publishing a new version for existing crates the
41+
user owns
42+
* **yank**: allows yanking and unyanking existing versions of the user's crates
43+
* **change-owners**: allows inviting new owners or removing existing owners
44+
* **legacy**: allows accessing all the endpoints on crates.io except for
45+
creating new tokens, like tokens created before the implementation of this
46+
RFC.
47+
48+
More endpoint scopes might be added in the future without the need of a
49+
dedicated RFC.
50+
51+
Tokens created before the implementation of this RFC will default to the legacy
52+
scope.
53+
54+
## Crates scope
55+
56+
The user will be able to opt into limiting which crates the token can act on by
57+
defining a crates scope.
58+
59+
The crates scope can contain a list of crate name patterns the token can
60+
interact with. Crate name patterns can either be regular crate names or they
61+
can end with a `*` character to match zero or more characters.
62+
63+
For example, a crate name pattern of `lazy_static` will only make the token
64+
apply to the corresponding crate, while `serde*` allows the token to act on
65+
any present or future crates starting with `serde` (including `serde` itself),
66+
but only if the user is an owner of those crates.
67+
68+
The crates scope will allow access to all present and future crates matching
69+
it. When an endpoint that doesn't interact with crates is called by a token
70+
with a crates scope, the crates scope will be ignored and the call will be
71+
authorized, unless limited by an endpoint scope (see above).
72+
73+
Tokens created before the implementation of this RFC will default to an empty
74+
crate scope filter (equivalent to no restrictions).
75+
76+
# Reference-level explanation
77+
[reference-level-explanation]: #reference-level-explanation
78+
79+
Endpoint scopes and crates scope are two completely separate systems, and can be
80+
used independently of one another.
81+
82+
Token scopes will be implemented entirely on the crates.io side, and there will
83+
be no change necessary to `cargo` or alternate registries.
84+
85+
## Endpoint scopes
86+
87+
The scopes proposed by this RFC allow access to the following endpoints:
88+
89+
| Endpoint | Required scope |
90+
|------------------------------------------|--------------------|
91+
| `PUT /crates/new` (new crates) | **publish-new** |
92+
| `PUT /crates/new` (existing crates) | **publish-update** |
93+
| `DELETE /crates/:crate_id/:version/yank` | **yank** |
94+
| `PUT /crates/:crate_id/:version/unyank` | **yank** |
95+
| `PUT /crates/:crate_id/owners` | **change-owners** |
96+
| `DELETE /crates/:crate_id/owners` | **change-owners** |
97+
| everything except `PUT /me/tokens` | **legacy** |
98+
99+
Removing an endpoint from a scope or adding an existing endpoint to an existing
100+
scope will be considered a breaking change. Adding newly created endpoints to
101+
an existing scope will be allowed only at the moment of their creation, if the
102+
crates.io team believes the new endpoint won't grant more privileges than the
103+
existing set of endpoints in that scope.
104+
105+
## Crates scope
106+
107+
The patterns will be evaluated during each API call, and if no match is found
108+
the request will be denied. Because it's evaluated every time, a crates scope
109+
will allow interacting with matching crates published after token creation.
110+
111+
The check for the crates scope is separate from crate ownership: having a scope
112+
that technically permits to interact with a crate the user doesn't own will be
113+
accepted by the backend, but a warning will be displayed if the pattern doesn't
114+
match any crate owned by the user.
115+
116+
# Drawbacks
117+
[drawbacks]: #drawbacks
118+
119+
No drawbacks are known at the time of writing the RFC.
120+
121+
# Rationale and alternatives
122+
[rationale-and-alternatives]: #rationale-and-alternatives
123+
124+
An alternative implementation for endpoint scopes could be to allow users to
125+
directly choose every endpoint they want to allow for their token, without
126+
having to choose whole groups at a time. This would result in more granularity
127+
and possibly better security, but it would make the UX to create new tokens way
128+
more complex (requiring more choices and a knowledge of the crates.io API).
129+
130+
An alternative implementation for crate scopes could be to have the user select
131+
the crates they want to allow in the UI instead of having to write a pattern.
132+
That would make creating a token harder for people with lots of crates (which,
133+
in the RFC author's opinion, are more likely to need crate scopes than a person
134+
with just a few crates), and it wouldn't allow new crates matching the pattern
135+
but uploaded after the token's creation from being accessed.
136+
137+
Finally, an alternative could be to do nothing, and encourage users to create
138+
"machine accounts" for each set of crates they own. A drawback of this is that
139+
GitHub's terms of service limit how many accounts a single person could have.
140+
141+
# Prior art
142+
[prior-art]: #prior-art
143+
144+
The endpoint scopes system is heavily inspired by GitHub, while the rest of the
145+
proposal is similar to nuget. Here is how popular package registries implements
146+
scoping:
147+
148+
* [nuget] (package registry for the .NET ecosystem) implements three endpoint
149+
scopes (publish new packages, publish new versions, unlist packages), has a
150+
mandatory expiration and supports specifying which packages the token applies
151+
to, either by checking boxes or defining a single glob pattern.
152+
[(documentation)][nuget-docs]
153+
* [npm] (package registry for JavaScript) implements a binary
154+
"read-only"/"read-write" permission model, also allowing to restrict the IP
155+
ranges allowed to access the token, but does not allow restricting the
156+
packages a token is allowed to change. [(documentation)][npm-docs]
157+
* [pypi] (package registry for Python) only implements the "upload packages"
158+
permission, and allows to scope each token to a *single* package.
159+
[(documentation)][pypi-docs]
160+
* [rubygems] (package registry for Ruby) and [packagist] (package registry for
161+
PHP) don't implement any kind of scoping for the API tokens.
162+
163+
[nuget]: https://www.nuget.org/
164+
[nuget-docs]: https://docs.microsoft.com/en-us/nuget/nuget-org/scoped-api-keys
165+
[npm]: https://www.npmjs.com
166+
[npm-docs]: https://docs.npmjs.com/creating-and-viewing-authentication-tokens
167+
[pypi]: https://pypi.org
168+
[pypi-docs]: https://pypi.org/help/#apitoken
169+
[rubygems]: https://rubygems.org/
170+
[packagist]: https://packagist.org/
171+
172+
# Unresolved questions
173+
[unresolved-questions]: #unresolved-questions
174+
175+
* Are there more scopes that would be useful to implement from the start?
176+
* Is the current behavior of crate scopes on endpoints that don't interact with
177+
crates the best, or should a token with crate scopes prevent access to
178+
endpoints that don't act on crates?
179+
180+
# Future possibilities
181+
[future-possibilities]: #future-possibilities
182+
183+
A future extension to the crates.io authorization model could be adding an
184+
optional expiration to tokens, to limit the damage over time if a token ends up
185+
being leaked.
186+
187+
Another extension could be an API endpoint that programmatically creates
188+
short-lived tokens (similar to what AWS STS does for AWS Access Keys), allowing
189+
to develop services that provide tokens with a short expiration time to CI
190+
builds. Such tokens would need to have the same set or a subset of the parent
191+
token's scopes: this RFC should consider that use case and avoid the
192+
implementation of solutions that would make the check hard.
193+
194+
To increase the security of CI environments even more, we could implement an
195+
option to require a separate confirmation for the actions executed by tokens.
196+
For example, we could send a confirmation email with a link the owners have to
197+
click to actually publish the crate uploaded by CI, preventing any malicious
198+
action with stolen tokens.
199+
200+
To remove the need for machine accounts, a future RFC could propose adding API
201+
tokens owned by teams, granting access to all resources owned by that team and
202+
allowing any team member to revoke them.

0 commit comments

Comments
 (0)