Skip to content

Commit

Permalink
feat: stabilize credential-process and registry-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
arlosi committed Sep 8, 2023
1 parent 4518131 commit 42701ba
Show file tree
Hide file tree
Showing 24 changed files with 549 additions and 447 deletions.
17 changes: 14 additions & 3 deletions credential/cargo-credential-1password/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
# cargo-credential-1password

This is the implementation for the Cargo credential helper for [1password].
See the [credential-process] documentation for how to use this.
A Cargo [credential provider] for [1password].

`cargo-credential-1password` uses the 1password `op` CLI to store the token. You must
install the `op` CLI from the [1password
website](https://1password.com/downloads/command-line/). You must run `op signin`
at least once with the appropriate arguments (such as `op signin my.1password.com user@example.com`),
unless you provide the sign-in-address and email arguments. The master password will be required on each request
unless the appropriate `OP_SESSION` environment variable is set. It supports
the following command-line arguments:
* `--account`: The account shorthand name to use.
* `--vault`: The vault name to use.
* `--sign-in-address`: The sign-in-address, which is a web address such as `my.1password.com`.
* `--email`: The email address to sign in with.

[1password]: https://1password.com/
[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process
[credential provider]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html
4 changes: 3 additions & 1 deletion credential/cargo-credential-libsecret/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
This is the implementation for the Cargo credential helper for [GNOME libsecret].
See the [credential-process] documentation for how to use this.

This credential provider is built-in to cargo as `cargo:libsecret`.

[GNOME libsecret]: https://wiki.gnome.org/Projects/Libsecret
[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process
[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html
5 changes: 4 additions & 1 deletion credential/cargo-credential-macos-keychain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
This is the implementation for the Cargo credential helper for [macOS Keychain].
See the [credential-process] documentation for how to use this.

This credential provider is built-in to cargo as `cargo:macos-keychain`.

[macOS Keychain]: https://support.apple.com/guide/keychain-access/welcome/mac
[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process
[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html

4 changes: 3 additions & 1 deletion credential/cargo-credential-wincred/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
This is the implementation for the Cargo credential helper for [Windows Credential Manager].
See the [credential-process] documentation for how to use this.

This credential provider is built-in to cargo as `cargo:wincred`.

[Windows Credential Manager]: https://support.microsoft.com/en-us/windows/accessing-credential-manager-1b5c916a-6a16-889f-8581-fc16e8165ac0
[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process
[credential-process]: https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html
2 changes: 1 addition & 1 deletion credential/cargo-credential/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ provides an interface to store tokens for authorizing access to a registry
such as https://crates.io/.

Documentation about credential processes may be found at
https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process
https://doc.rust-lang.org/nightly/cargo/reference/credential-provider-protocol.html

Example implementations may be found at
https://github.com/rust-lang/cargo/tree/master/credential
Expand Down
12 changes: 8 additions & 4 deletions src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,6 @@ unstable_cli_options!(
check_cfg: Option<(/*features:*/ bool, /*well_known_names:*/ bool, /*well_known_values:*/ bool, /*output:*/ bool)> = ("Specify scope of compile-time checking of `cfg` names/values"),
codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"),
config_include: bool = ("Enable the `include` key in config files"),
credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"),
direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"),
doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"),
dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
Expand All @@ -745,7 +744,6 @@ unstable_cli_options!(
panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
registry_auth: bool = ("Authentication for alternative registries"),
rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
rustdoc_scrape_examples: bool = ("Allows Rustdoc to scrape code examples from reverse-dependencies"),
script: bool = ("Enable support for single-file, `.rs` packages"),
Expand Down Expand Up @@ -819,6 +817,12 @@ const STABILIZED_TERMINAL_WIDTH: &str =

const STABILISED_SPARSE_REGISTRY: &str = "The sparse protocol is now the default for crates.io";

const STABILIZED_CREDENTIAL_PROCESS: &str =
"Authentication with a credential provider is always available.";

const STABILIZED_REGISTRY_AUTH: &str =
"Authenticated registries are available if a credential provider is configured.";

fn deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
where
D: serde::Deserializer<'de>,
Expand Down Expand Up @@ -1084,6 +1088,8 @@ impl CliUnstable {
"sparse-registry" => stabilized_warn(k, "1.68", STABILISED_SPARSE_REGISTRY),
"terminal-width" => stabilized_warn(k, "1.68", STABILIZED_TERMINAL_WIDTH),
"doctest-in-workspace" => stabilized_warn(k, "1.72", STABILIZED_DOCTEST_IN_WORKSPACE),
"credential-process" => stabilized_warn(k, "1.74", STABILIZED_CREDENTIAL_PROCESS),
"registry-auth" => stabilized_warn(k, "1.74", STABILIZED_REGISTRY_AUTH),

// Unstable features
// Sorted alphabetically:
Expand All @@ -1101,7 +1107,6 @@ impl CliUnstable {
}
"codegen-backend" => self.codegen_backend = parse_empty(k, v)?,
"config-include" => self.config_include = parse_empty(k, v)?,
"credential-process" => self.credential_process = parse_empty(k, v)?,
"direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?,
"doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?,
"dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
Expand All @@ -1122,7 +1127,6 @@ impl CliUnstable {
"panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
"profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
"publish-timeout" => self.publish_timeout = parse_empty(k, v)?,
"registry-auth" => self.registry_auth = parse_empty(k, v)?,
"rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
"rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?,
"separate-nightlies" => self.separate_nightlies = parse_empty(k, v)?,
Expand Down
4 changes: 1 addition & 3 deletions src/cargo/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@
//! This is the `#[cargo_test]` proc-macro used by the test suite to define tests.
//! - [`credential`](https://github.com/rust-lang/cargo/tree/master/credential)
//! This subdirectory contains several packages for implementing the
//! experimental
//! [credential-process](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#credential-process)
//! feature.
//! [credential providers](https://doc.rust-lang.org/nightly/cargo/reference/registry-authentication.html).
//! - [`mdman`](https://github.com/rust-lang/cargo/tree/master/crates/mdman)
//! ([nightly docs](https://doc.rust-lang.org/nightly/nightly-rustc/mdman/index.html)):
//! This is a utility for generating cargo's man pages. See [Building the man
Expand Down
1 change: 1 addition & 0 deletions src/cargo/ops/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ fn registry(
None,
operation,
vec![],
false,
)?)
} else {
None
Expand Down
1 change: 1 addition & 0 deletions src/cargo/ops/registry/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
None,
operation,
vec![],
false,
)?));
}

Expand Down
1 change: 1 addition & 0 deletions src/cargo/sources/registry/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pub(super) fn download(
None,
Operation::Read,
vec![],
true,
)?)
} else {
None
Expand Down
14 changes: 3 additions & 11 deletions src/cargo/sources/registry/http_remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,9 +547,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
return Poll::Ready(Ok(LoadResponse::NotFound));
}
StatusCode::Unauthorized
if !self.auth_required
&& path == Path::new(RegistryConfig::NAME)
&& self.config.cli_unstable().registry_auth =>
if !self.auth_required && path == Path::new(RegistryConfig::NAME) =>
{
debug!(target: "network", "re-attempting request for config.json with authorization included.");
self.fresh.remove(path);
Expand Down Expand Up @@ -612,10 +610,6 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
}
}

if !self.config.cli_unstable().registry_auth {
self.auth_required = false;
}

// Looks like we're going to have to do a network request.
self.start_fetch()?;

Expand Down Expand Up @@ -654,6 +648,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
self.login_url.as_ref(),
Operation::Read,
self.auth_error_headers.clone(),
true,
)?;
headers.append(&format!("Authorization: {}", authorization))?;
trace!(target: "network", "including authorization for {}", full_url);
Expand Down Expand Up @@ -724,10 +719,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
}

fn config(&mut self) -> Poll<CargoResult<Option<RegistryConfig>>> {
let mut cfg = ready!(self.config()?).clone();
if !self.config.cli_unstable().registry_auth {
cfg.auth_required = false;
}
let cfg = ready!(self.config()?).clone();
Poll::Ready(Ok(Some(cfg)))
}

Expand Down
5 changes: 1 addition & 4 deletions src/cargo/sources/registry/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,7 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
match ready!(self.load(Path::new(""), Path::new(RegistryConfig::NAME), None)?) {
LoadResponse::Data { raw_data, .. } => {
trace!("config loaded");
let mut cfg: RegistryConfig = serde_json::from_slice(&raw_data)?;
if !self.config.cli_unstable().registry_auth {
cfg.auth_required = false;
}
let cfg: RegistryConfig = serde_json::from_slice(&raw_data)?;
Poll::Ready(Ok(Some(cfg)))
}
_ => Poll::Ready(Ok(None)),
Expand Down
53 changes: 38 additions & 15 deletions src/cargo/util/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ impl RegistryConfigExtended {
fn credential_provider(
config: &Config,
sid: &SourceId,
require_cred_provider_config: bool,
show_warnings: bool,
) -> CargoResult<Vec<Vec<String>>> {
let warn = |message: String| {
Expand All @@ -88,7 +89,9 @@ fn credential_provider(
};

let cfg = registry_credential_config_raw(config, sid)?;
let mut global_provider_defined = true;
let default_providers = || {
global_provider_defined = false;
if config.cli_unstable().asymmetric_token {
// Enable the PASETO provider
vec![
Expand All @@ -101,7 +104,7 @@ fn credential_provider(
};
let global_providers = config
.get::<Option<Vec<Value<String>>>>("registry.global-credential-providers")?
.filter(|p| !p.is_empty() && config.cli_unstable().credential_process)
.filter(|p| !p.is_empty())
.map(|p| {
p.iter()
.rev()
Expand All @@ -112,14 +115,14 @@ fn credential_provider(
.unwrap_or_else(default_providers);
tracing::debug!(?global_providers);

let providers = match cfg {
match cfg {
// If there's a specific provider configured for this registry, use it.
Some(RegistryConfig {
credential_provider: Some(provider),
token,
secret_key,
..
}) if config.cli_unstable().credential_process => {
}) => {
let provider = resolve_credential_alias(config, provider);
if let Some(token) = token {
if provider[0] != "cargo:token" {
Expand All @@ -139,7 +142,7 @@ fn credential_provider(
))?;
}
}
vec![provider]
return Ok(vec![provider]);
}

// Warning for both `token` and `secret-key`, stating which will be ignored
Expand Down Expand Up @@ -173,7 +176,6 @@ fn credential_provider(
// One or both of the below individual warnings will trigger
}
}
global_providers
}

// Check if a `token` is configured that will be ignored.
Expand All @@ -191,7 +193,6 @@ fn credential_provider(
token.definition
))?;
}
global_providers
}

// Check if a asymmetric token is configured that will be ignored.
Expand All @@ -210,13 +211,18 @@ fn credential_provider(
token.definition
))?;
}
global_providers
}

// If we couldn't find a registry-specific provider, use the fallback provider list.
None | Some(RegistryConfig { .. }) => global_providers,
None | Some(RegistryConfig { .. }) => {}
};
Ok(providers)
if !global_provider_defined && require_cred_provider_config {
bail!(
"authenticated registries require a credential-provider to be configured\n\
see https://doc.rust-lang.org/cargo/reference/registry-authentication.html for details"
);
}
Ok(global_providers)
}

/// Get the credential configuration for a `SourceId`.
Expand Down Expand Up @@ -393,7 +399,7 @@ impl AuthorizationError {
// Only display the _TOKEN environment variable suggestion if the `cargo:token` credential
// provider is available for the source. Otherwise setting the environment variable will
// have no effect.
let display_token_env_help = credential_provider(config, &sid, false)?
let display_token_env_help = credential_provider(config, &sid, false, false)?
.iter()
.any(|p| p.first().map(String::as_str) == Some("cargo:token"));
Ok(AuthorizationError {
Expand Down Expand Up @@ -476,6 +482,7 @@ fn credential_action(
action: Action<'_>,
headers: Vec<String>,
args: &[&str],
require_cred_provider_config: bool,
) -> CargoResult<CredentialResponse> {
let name = if sid.is_crates_io() {
Some(CRATES_IO_REGISTRY)
Expand All @@ -487,7 +494,7 @@ fn credential_action(
name,
headers,
};
let providers = credential_provider(config, sid, true)?;
let providers = credential_provider(config, sid, require_cred_provider_config, true)?;
let mut any_not_found = false;
for provider in providers {
let args: Vec<&str> = provider
Expand Down Expand Up @@ -549,8 +556,15 @@ pub fn auth_token(
login_url: Option<&Url>,
operation: Operation<'_>,
headers: Vec<String>,
require_cred_provider_config: bool,
) -> CargoResult<String> {
match auth_token_optional(config, sid, operation, headers)? {
match auth_token_optional(
config,
sid,
operation,
headers,
require_cred_provider_config,
)? {
Some(token) => Ok(token.expose()),
None => Err(AuthorizationError::new(
config,
Expand All @@ -568,6 +582,7 @@ fn auth_token_optional(
sid: &SourceId,
operation: Operation<'_>,
headers: Vec<String>,
require_cred_provider_config: bool,
) -> CargoResult<Option<Secret<String>>> {
tracing::trace!("token requested for {}", sid.display_registry_name());
let mut cache = config.credential_cache();
Expand All @@ -588,7 +603,14 @@ fn auth_token_optional(
}
}

let credential_response = credential_action(config, sid, Action::Get(operation), headers, &[]);
let credential_response = credential_action(
config,
sid,
Action::Get(operation),
headers,
&[],
require_cred_provider_config,
);
if let Some(e) = credential_response.as_ref().err() {
if let Some(e) = e.downcast_ref::<cargo_credential::Error>() {
if matches!(e, cargo_credential::Error::NotFound) {
Expand Down Expand Up @@ -627,7 +649,7 @@ fn auth_token_optional(

/// Log out from the given registry.
pub fn logout(config: &Config, sid: &SourceId) -> CargoResult<()> {
let credential_response = credential_action(config, sid, Action::Logout, vec![], &[]);
let credential_response = credential_action(config, sid, Action::Logout, vec![], &[], false);
if let Some(e) = credential_response.as_ref().err() {
if let Some(e) = e.downcast_ref::<cargo_credential::Error>() {
if matches!(e, cargo_credential::Error::NotFound) {
Expand Down Expand Up @@ -656,7 +678,8 @@ pub fn login(
options: LoginOptions<'_>,
args: &[&str],
) -> CargoResult<()> {
let credential_response = credential_action(config, sid, Action::Login(options), vec![], args)?;
let credential_response =
credential_action(config, sid, Action::Login(options), vec![], args, false)?;
let CredentialResponse::Login = credential_response else {
bail!("credential provider produced unexpected response for `login` request: {credential_response:?}")
};
Expand Down
2 changes: 2 additions & 0 deletions src/doc/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
* [Source Replacement](reference/source-replacement.md)
* [External Tools](reference/external-tools.md)
* [Registries](reference/registries.md)
* [Registry Authentication](reference/registry-authentication.md)
* [Credential Provider Protocol](reference/credential-provider-protocol.md)
* [Running a Registry](reference/running-a-registry.md)
* [Registry Index](reference/registry-index.md)
* [Registry Web API](reference/registry-web-api.md)
Expand Down
Loading

0 comments on commit 42701ba

Please sign in to comment.