Skip to content
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

Fetching cargo registry tokens from external processes #2730

Merged
merged 8 commits into from
Dec 4, 2020
201 changes: 201 additions & 0 deletions text/2730-cargo-token-from-process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
- Feature Name: `cargo_token_from_process`
- Start Date: 2019-07-22
- RFC PR: [rust-lang/rfcs#2730](https://github.com/rust-lang/rfcs/pull/2730)
- Cargo Issue: [rust-lang/cargo#8933](https://github.com/rust-lang/cargo/issues/8933)

# Summary
[summary]: #summary

Add a cargo setting to fetch registry authentication tokens by calling an
external process.

# Motivation
[motivation]: #motivation

Some interactions with a registry require an authentication token, and Cargo
currently stores such token in plaintext in the [`.cargo/credentials`][creds]
file. While Cargo properly sets permissions on that file to only allow the
current user to read it, that's not enough to prevent other processes ran by
the same user from reading the token.

This RFC aims to provide a way to configure Cargo to instead fetch the token
from any secrets storage system, for example a password manager or the system
keyring.

[creds]: https://doc.rust-lang.org/stable/cargo/reference/config.html#credentials

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

Suppose a user has their authentication token stored in a password manager, and
the password manager provides a command, `/usr/bin/cargo-creds`, to decrypt and
print that token in a secure way. Instead of storing the token in plaintext,
the user can add this snippet to their own Cargo config to authenticate with
crates.io:

```toml
[registry]
credential-process = "/usr/bin/cargo-creds"
```

When authentication is required, Cargo will execute the command to acquire the
token, which will never be stored by Cargo on disk.

It will be possible to use `credential-process` on both crates.io and alternative
registries.

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

A new key, `credential-process`, will be added to the `[registry]` and
`[registries.NAME]` sections of the configuration file. When a `token` key is
also present, the latter will take precedence over `credential-process` to
maintain backward compatibility, and a warning will be issued to let the user
know about that.

The `registry.credential-process` value will be used for all registries. If a
specific registry specifies the value in the `registries` table, then that
will take precedence.

The `credential-process` key accepts either a string containing the executable
and arguments or an array containing the executable name and the arguments.
This follows Cargo's convention for executables defined in config.

There are special strings in the `credential-process` that Cargo will replace
with a given value:

* `{name}` — Name of the registry.
* `{api_url}` — The API URL.
* `{action}` — The authentication action (described below).

```toml
[registry]
credential-process = 'cargo osxkeychain {action}'

[registries.my-registry]
credential-process = ['/path/to/myscript', '{name}']
```

There are two different kinds of token processes that Cargo supports. The
simple "basic" kind will only be called by Cargo when it needs a token. This
is intended for simple and easy integration with password managers, that can
often use pre-existing tooling. The more advanced "Cargo" kind supports
different actions passed as a command-line argument. This is intended for more
pleasant integration experience, at the expense of requiring a Cargo-specific
process to glue to the password manager. Cargo will determine which kind is
supported by the `credential-process` definition. If it contains the
`{action}` argument, then it uses the advanced style, otherwise it assumes it
only supports the "basic" kind.

## Basic authenticator

A basic authenticator is a process that returns a token on stdout. Newlines
will be trimmed. The process inherits the user's stdin and stderr. It should
exit 0 on success, and nonzero on error.

With this form, `cargo login` and `cargo logout` are not supported and return
an error if used.

## Cargo authenticator

The protocol between the Cargo and the process is very basic, intended to
ensure the credential process is kept as simple as possible. Cargo will
execute the process with the `{action}` argument indicating which action to
perform:

* `store` — Store the given token in secure storage.
* `get` — Get a token from storage.
* `erase` — Remove a token from storage.

The `cargo login` command will use `store` to save a token. Commands that
require authentication, like `cargo publish`, will use `get` to retrieve a
token. A new command, `cargo logout` will be added which will use the `erase`
command to remove a token.

The process inherits the user's stderr, so the process can display messages.
Some values are passed in via environment variables (see below). The expected
interactions are:

* `store` — The token is sent to the process's stdin, terminated by a newline.
The process should store the token keyed off the registry name. If the
process fails, it should exit with a nonzero exit status.

* `get` — The process should send the token to its stdout (trailing newline
will be trimmed). The process inherits the user's stdin, should it need to
receive input.

If the process is unable to fulfill the request, it should exit with a
nonzero exit code.

* `erase` — The process should remove the token associated with the registry
name. If the token is not found, the process should exit with a 0 exit
status.

## Environment

The following environment variables will be provided to the executed command:

* `CARGO` — Path to the `cargo` binary executing the command.
* `CARGO_REGISTRY_NAME` — Name of the registry the authentication token is for.
* `CARGO_REGISTRY_API_URL` — The URL of the registry API.

# Drawbacks
[drawbacks]: #drawbacks

*No known drawbacks yet.*

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

The solution proposed by this RFC isn't tied to any secret storage services and
can be adapted to work with virtually any secret storage the user might rely
on, while being relatively easy to understand and use.

# Prior art
[prior-art]: #prior-art

Multiple command line tools implement this system or a similar one to retrieve
authentication tokens or other secrets:

* [awscli][awscli] includes the `credentials_process` setting which calls
a process with arguments provided by the user. The process is expected to
emit JSON that contains the access key.
* [Docker CLI][docker] offers "credential stores", programs the Docker CLI
calls with specific arguments expecting JSON output. Implementations are
provided for common storage systems, and the protocol is documented for users
who want to integrate with their custom system.
* [Ansible Vault][ansible] allows to specify an executable file as the
decryption password, executing it when needed.
* [Git] has a credential mechanism using store/get/erase arguments, and
`key=value` parameters send and received with the process.

[awscli]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html
[docker]: https://docs.docker.com/engine/reference/commandline/login/#credentials-store
[ansible]: https://docs.ansible.com/ansible/latest/user_guide/vault.html#providing-vault-passwords
[git]: https://git-scm.com/docs/gitcredentials#_custom_helpers

# Unresolved questions
[unresolved-questions]: #unresolved-questions

*No known unresolved questions yet.*

# Future possibilities
[future-possibilities]: #future-possibilities

To allow for a better user experience for users of popular secret storages,
Cargo can provide built-in support for common systems. It is proposed that a
`credential-process` with a `cargo:` prefix will use some internal support. For
example, `credential-process = 'cargo:system-keychain'`.

Additionally, the community could create Cargo plugins that implement
different storage systems. For example, a hypothetical Cargo plugin could be
specified as `credential-process = 'cargo credential-1password {action}'`.

Encrypting the stored tokens or alternate authentication methods are out of the
scope of this RFC, but could be proposed in the future to provide additional
security for our users.

Future RFCs introducing new kinds of secrets used by Cargo (i.e. 2FA codes)
could also add support for fetching those secrets from a process, in a similar
way to this RFC. Defining how that should work is outside the scope of this RFC
though.