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