|
| 1 | +- Feature Name: `cargo_token_from_process` |
| 2 | +- Start Date: 2019-07-22 |
| 3 | +- RFC PR: [rust-lang/rfcs#2730](https://github.com/rust-lang/rfcs/pull/2730) |
| 4 | +- Cargo Issue: [rust-lang/cargo#8933](https://github.com/rust-lang/cargo/issues/8933) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Add a cargo setting to fetch registry authentication tokens by calling an |
| 10 | +external process. |
| 11 | + |
| 12 | +# Motivation |
| 13 | +[motivation]: #motivation |
| 14 | + |
| 15 | +Some interactions with a registry require an authentication token, and Cargo |
| 16 | +currently stores such token in plaintext in the [`.cargo/credentials`][creds] |
| 17 | +file. While Cargo properly sets permissions on that file to only allow the |
| 18 | +current user to read it, that's not enough to prevent other processes ran by |
| 19 | +the same user from reading the token. |
| 20 | + |
| 21 | +This RFC aims to provide a way to configure Cargo to instead fetch the token |
| 22 | +from any secrets storage system, for example a password manager or the system |
| 23 | +keyring. |
| 24 | + |
| 25 | +[creds]: https://doc.rust-lang.org/stable/cargo/reference/config.html#credentials |
| 26 | + |
| 27 | +# Guide-level explanation |
| 28 | +[guide-level-explanation]: #guide-level-explanation |
| 29 | + |
| 30 | +Suppose a user has their authentication token stored in a password manager, and |
| 31 | +the password manager provides a command, `/usr/bin/cargo-creds`, to decrypt and |
| 32 | +print that token in a secure way. Instead of storing the token in plaintext, |
| 33 | +the user can add this snippet to their own Cargo config to authenticate with |
| 34 | +crates.io: |
| 35 | + |
| 36 | +```toml |
| 37 | +[registry] |
| 38 | +credential-process = "/usr/bin/cargo-creds" |
| 39 | +``` |
| 40 | + |
| 41 | +When authentication is required, Cargo will execute the command to acquire the |
| 42 | +token, which will never be stored by Cargo on disk. |
| 43 | + |
| 44 | +It will be possible to use `credential-process` on both crates.io and alternative |
| 45 | +registries. |
| 46 | + |
| 47 | +# Reference-level explanation |
| 48 | +[reference-level-explanation]: #reference-level-explanation |
| 49 | + |
| 50 | +A new key, `credential-process`, will be added to the `[registry]` and |
| 51 | +`[registries.NAME]` sections of the configuration file. When a `token` key is |
| 52 | +also present, the latter will take precedence over `credential-process` to |
| 53 | +maintain backward compatibility, and a warning will be issued to let the user |
| 54 | +know about that. |
| 55 | + |
| 56 | +The `registry.credential-process` value will be used for all registries. If a |
| 57 | +specific registry specifies the value in the `registries` table, then that |
| 58 | +will take precedence. |
| 59 | + |
| 60 | +The `credential-process` key accepts either a string containing the executable |
| 61 | +and arguments or an array containing the executable name and the arguments. |
| 62 | +This follows Cargo's convention for executables defined in config. |
| 63 | + |
| 64 | +There are special strings in the `credential-process` that Cargo will replace |
| 65 | +with a given value: |
| 66 | + |
| 67 | +* `{name}` — Name of the registry. |
| 68 | +* `{api_url}` — The API URL. |
| 69 | +* `{action}` — The authentication action (described below). |
| 70 | + |
| 71 | +```toml |
| 72 | +[registry] |
| 73 | +credential-process = 'cargo osxkeychain {action}' |
| 74 | + |
| 75 | +[registries.my-registry] |
| 76 | +credential-process = ['/path/to/myscript', '{name}'] |
| 77 | +``` |
| 78 | + |
| 79 | +There are two different kinds of token processes that Cargo supports. The |
| 80 | +simple "basic" kind will only be called by Cargo when it needs a token. This |
| 81 | +is intended for simple and easy integration with password managers, that can |
| 82 | +often use pre-existing tooling. The more advanced "Cargo" kind supports |
| 83 | +different actions passed as a command-line argument. This is intended for more |
| 84 | +pleasant integration experience, at the expense of requiring a Cargo-specific |
| 85 | +process to glue to the password manager. Cargo will determine which kind is |
| 86 | +supported by the `credential-process` definition. If it contains the |
| 87 | +`{action}` argument, then it uses the advanced style, otherwise it assumes it |
| 88 | +only supports the "basic" kind. |
| 89 | + |
| 90 | +## Basic authenticator |
| 91 | + |
| 92 | +A basic authenticator is a process that returns a token on stdout. Newlines |
| 93 | +will be trimmed. The process inherits the user's stdin and stderr. It should |
| 94 | +exit 0 on success, and nonzero on error. |
| 95 | + |
| 96 | +With this form, `cargo login` and `cargo logout` are not supported and return |
| 97 | +an error if used. |
| 98 | + |
| 99 | +## Cargo authenticator |
| 100 | + |
| 101 | +The protocol between the Cargo and the process is very basic, intended to |
| 102 | +ensure the credential process is kept as simple as possible. Cargo will |
| 103 | +execute the process with the `{action}` argument indicating which action to |
| 104 | +perform: |
| 105 | + |
| 106 | +* `store` — Store the given token in secure storage. |
| 107 | +* `get` — Get a token from storage. |
| 108 | +* `erase` — Remove a token from storage. |
| 109 | + |
| 110 | +The `cargo login` command will use `store` to save a token. Commands that |
| 111 | +require authentication, like `cargo publish`, will use `get` to retrieve a |
| 112 | +token. A new command, `cargo logout` will be added which will use the `erase` |
| 113 | +command to remove a token. |
| 114 | + |
| 115 | +The process inherits the user's stderr, so the process can display messages. |
| 116 | +Some values are passed in via environment variables (see below). The expected |
| 117 | +interactions are: |
| 118 | + |
| 119 | +* `store` — The token is sent to the process's stdin, terminated by a newline. |
| 120 | + The process should store the token keyed off the registry name. If the |
| 121 | + process fails, it should exit with a nonzero exit status. |
| 122 | + |
| 123 | +* `get` — The process should send the token to its stdout (trailing newline |
| 124 | + will be trimmed). The process inherits the user's stdin, should it need to |
| 125 | + receive input. |
| 126 | + |
| 127 | + If the process is unable to fulfill the request, it should exit with a |
| 128 | + nonzero exit code. |
| 129 | + |
| 130 | +* `erase` — The process should remove the token associated with the registry |
| 131 | + name. If the token is not found, the process should exit with a 0 exit |
| 132 | + status. |
| 133 | + |
| 134 | +## Environment |
| 135 | + |
| 136 | +The following environment variables will be provided to the executed command: |
| 137 | + |
| 138 | +* `CARGO` — Path to the `cargo` binary executing the command. |
| 139 | +* `CARGO_REGISTRY_NAME` — Name of the registry the authentication token is for. |
| 140 | +* `CARGO_REGISTRY_API_URL` — The URL of the registry API. |
| 141 | + |
| 142 | +# Drawbacks |
| 143 | +[drawbacks]: #drawbacks |
| 144 | + |
| 145 | +*No known drawbacks yet.* |
| 146 | + |
| 147 | +# Rationale and alternatives |
| 148 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 149 | + |
| 150 | +The solution proposed by this RFC isn't tied to any secret storage services and |
| 151 | +can be adapted to work with virtually any secret storage the user might rely |
| 152 | +on, while being relatively easy to understand and use. |
| 153 | + |
| 154 | +# Prior art |
| 155 | +[prior-art]: #prior-art |
| 156 | + |
| 157 | +Multiple command line tools implement this system or a similar one to retrieve |
| 158 | +authentication tokens or other secrets: |
| 159 | + |
| 160 | +* [awscli][awscli] includes the `credentials_process` setting which calls |
| 161 | + a process with arguments provided by the user. The process is expected to |
| 162 | + emit JSON that contains the access key. |
| 163 | +* [Docker CLI][docker] offers "credential stores", programs the Docker CLI |
| 164 | + calls with specific arguments expecting JSON output. Implementations are |
| 165 | + provided for common storage systems, and the protocol is documented for users |
| 166 | + who want to integrate with their custom system. |
| 167 | +* [Ansible Vault][ansible] allows to specify an executable file as the |
| 168 | + decryption password, executing it when needed. |
| 169 | +* [Git] has a credential mechanism using store/get/erase arguments, and |
| 170 | + `key=value` parameters send and received with the process. |
| 171 | + |
| 172 | +[awscli]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html |
| 173 | +[docker]: https://docs.docker.com/engine/reference/commandline/login/#credentials-store |
| 174 | +[ansible]: https://docs.ansible.com/ansible/latest/user_guide/vault.html#providing-vault-passwords |
| 175 | +[git]: https://git-scm.com/docs/gitcredentials#_custom_helpers |
| 176 | + |
| 177 | +# Unresolved questions |
| 178 | +[unresolved-questions]: #unresolved-questions |
| 179 | + |
| 180 | +*No known unresolved questions yet.* |
| 181 | + |
| 182 | +# Future possibilities |
| 183 | +[future-possibilities]: #future-possibilities |
| 184 | + |
| 185 | +To allow for a better user experience for users of popular secret storages, |
| 186 | +Cargo can provide built-in support for common systems. It is proposed that a |
| 187 | +`credential-process` with a `cargo:` prefix will use some internal support. For |
| 188 | +example, `credential-process = 'cargo:system-keychain'`. |
| 189 | + |
| 190 | +Additionally, the community could create Cargo plugins that implement |
| 191 | +different storage systems. For example, a hypothetical Cargo plugin could be |
| 192 | +specified as `credential-process = 'cargo credential-1password {action}'`. |
| 193 | + |
| 194 | +Encrypting the stored tokens or alternate authentication methods are out of the |
| 195 | +scope of this RFC, but could be proposed in the future to provide additional |
| 196 | +security for our users. |
| 197 | + |
| 198 | +Future RFCs introducing new kinds of secrets used by Cargo (i.e. 2FA codes) |
| 199 | +could also add support for fetching those secrets from a process, in a similar |
| 200 | +way to this RFC. Defining how that should work is outside the scope of this RFC |
| 201 | +though. |
0 commit comments