RFC 2730: Cargo: fetch authentication tokens from an external process

tools (cargo | security | registry)

Summary

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

Motivation

Some interactions with a registry require an authentication token, and Cargo currently stores such token in plaintext in the .cargo/credentials 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.

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:

[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

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:

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

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:

Environment

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

Drawbacks

No known drawbacks yet.

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

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

Unresolved questions

No known unresolved questions yet.

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.