unstable_cli_options

This commit is contained in:
Jacob Finkelman 2022-12-12 17:49:22 +00:00
parent 5939ed0d4f
commit d8df1425ea
5 changed files with 138 additions and 47 deletions

View File

@ -682,7 +682,7 @@ unstable_cli_options!(
panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"), panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"), host_config: bool = ("Enable the [host] section in the .cargo/config.toml file"),
sparse_registry: bool = ("Support plain-HTTP-based crate registries"), sparse_registry: bool = ("Support plain-HTTP-based crate registries"),
registry_auth: bool = ("Authentication for alternative registries"), registry_auth: bool = ("Authentication for alternative registries, and generate registry authentication tokens using asymmetric cryptography"),
target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"), target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"), rustdoc_map: bool = ("Allow passing external documentation mappings to rustdoc"),
separate_nightlies: bool = (HIDDEN), separate_nightlies: bool = (HIDDEN),

View File

@ -44,6 +44,8 @@ pub enum RegistryCredentialConfig {
Token(String), Token(String),
/// Process used for fetching a token. /// Process used for fetching a token.
Process((PathBuf, Vec<String>)), Process((PathBuf, Vec<String>)),
/// Secret Key and subject for Asymmetric tokens.
AsymmetricKey((String, Option<String>)),
} }
impl RegistryCredentialConfig { impl RegistryCredentialConfig {
@ -59,6 +61,12 @@ impl RegistryCredentialConfig {
pub fn is_token(&self) -> bool { pub fn is_token(&self) -> bool {
matches!(self, Self::Token(..)) matches!(self, Self::Token(..))
} }
/// Returns `true` if the credential is [`Key`].
///
/// [`Key`]: Credential::Key
pub fn is_asymmetric_key(&self) -> bool {
matches!(self, Self::AsymmetricKey(..))
}
pub fn as_token(&self) -> Option<&str> { pub fn as_token(&self) -> Option<&str> {
if let Self::Token(v) = self { if let Self::Token(v) = self {
Some(&*v) Some(&*v)
@ -73,6 +81,13 @@ impl RegistryCredentialConfig {
None None
} }
} }
pub fn as_asymmetric_key(&self) -> Option<&(String, Option<String>)> {
if let Self::AsymmetricKey(v) = self {
Some(v)
} else {
None
}
}
} }
pub struct PublishOpts<'cfg> { pub struct PublishOpts<'cfg> {

View File

@ -26,6 +26,8 @@ pub fn registry_credential_config(
index: Option<String>, index: Option<String>,
token: Option<String>, token: Option<String>,
credential_process: Option<config::PathAndArgs>, credential_process: Option<config::PathAndArgs>,
secret_key: Option<String>,
secret_key_subject: Option<String>,
#[serde(rename = "default")] #[serde(rename = "default")]
_default: Option<String>, _default: Option<String>,
} }
@ -46,26 +48,44 @@ pub fn registry_credential_config(
let RegistryConfig { let RegistryConfig {
token, token,
credential_process, credential_process,
secret_key,
secret_key_subject,
.. ..
} = config.get::<RegistryConfig>("registry")?; } = config.get::<RegistryConfig>("registry")?;
let credential_process = let credential_process =
credential_process.filter(|_| config.cli_unstable().credential_process); credential_process.filter(|_| config.cli_unstable().credential_process);
let secret_key = secret_key.filter(|_| config.cli_unstable().registry_auth);
let secret_key_subject = secret_key_subject.filter(|_| config.cli_unstable().registry_auth);
return Ok(match (token, credential_process) { let err_both = |token_key: &str, proc_key: &str| {
(Some(_), Some(_)) => { Err(format_err!(
return Err(format_err!( "both `{token_key}` and `{proc_key}` \
"both `token` and `credential-process` \ were specified in the config`.\n\
were specified in the config`.\n\ Only one of these values may be set, remove one or the other to proceed.",
Only one of these values may be set, remove one or the other to proceed.", ))
)) };
} return Ok(
(Some(token), _) => RegistryCredentialConfig::Token(token), match (token, credential_process, secret_key, secret_key_subject) {
(_, Some(process)) => RegistryCredentialConfig::Process(( (Some(_), Some(_), _, _) => return err_both("token", "credential-process"),
process.path.resolve_program(config), (Some(_), _, Some(_), _) => return err_both("token", "secret-key"),
process.args, (_, Some(_), Some(_), _) => return err_both("credential-process", "secret-key"),
)), (_, _, None, Some(_)) => {
(None, None) => RegistryCredentialConfig::None, return Err(format_err!(
}); "`secret-key-subject` was set but `secret-key` was not in the config.\n\
Ether set the `secret-key` or remove the `secret-key-subject`."
));
}
(Some(token), _, _, _) => RegistryCredentialConfig::Token(token),
(_, Some(process), _, _) => RegistryCredentialConfig::Process((
process.path.resolve_program(config),
process.args,
)),
(None, None, Some(key), subject) => {
RegistryCredentialConfig::AsymmetricKey((key, subject))
}
(None, None, None, _) => RegistryCredentialConfig::None,
},
);
} }
// Find the SourceId's name by its index URL. If environment variables // Find the SourceId's name by its index URL. If environment variables
@ -133,52 +153,71 @@ pub fn registry_credential_config(
} }
} }
let (token, credential_process) = if let Some(name) = &name { let (token, credential_process, secret_key, secret_key_subject) = if let Some(name) = &name {
log::debug!("found alternative registry name `{name}` for {sid}"); log::debug!("found alternative registry name `{name}` for {sid}");
let RegistryConfig { let RegistryConfig {
token, token,
secret_key,
secret_key_subject,
credential_process, credential_process,
.. ..
} = config.get::<RegistryConfig>(&format!("registries.{name}"))?; } = config.get::<RegistryConfig>(&format!("registries.{name}"))?;
let credential_process = let credential_process =
credential_process.filter(|_| config.cli_unstable().credential_process); credential_process.filter(|_| config.cli_unstable().credential_process);
(token, credential_process) let secret_key = secret_key.filter(|_| config.cli_unstable().registry_auth);
let secret_key_subject = secret_key_subject.filter(|_| config.cli_unstable().registry_auth);
(token, credential_process, secret_key, secret_key_subject)
} else { } else {
log::debug!("no registry name found for {sid}"); log::debug!("no registry name found for {sid}");
(None, None) (None, None, None, None)
}; };
let name = name.as_deref(); let name = name.as_deref();
Ok(match (token, credential_process) { let err_both = |token_key: &str, proc_key: &str| {
(Some(_), Some(_)) => { Err(format_err!(
return { "both `{token_key}` and `{proc_key}` \
Err(format_err!( were specified in the config for registry `{name}`.\n\
"both `token` and `credential-process` \ Only one of these values may be set, remove one or the other to proceed.",
were specified in the config for registry `{name}`.\n\ name = name.unwrap()
Only one of these values may be set, remove one or the other to proceed.", ))
name = name.unwrap() };
)) Ok(
match (token, credential_process, secret_key, secret_key_subject) {
(Some(_), Some(_), _, _) => return err_both("token", "credential-process"),
(Some(_), _, Some(_), _) => return err_both("token", "secret-key"),
(_, Some(_), Some(_), _) => return err_both("credential-process", "secret-key"),
(_, _, None, Some(_)) => {
return Err(format_err!(
"`secret-key-subject` was set but `secret-key` was not in the config \
for registry `{}`.\n\
Ether set the `secret-key` or remove the `secret-key-subject`.",
name.unwrap()
));
} }
} (Some(token), _, _, _) => RegistryCredentialConfig::Token(token),
(Some(token), _) => RegistryCredentialConfig::Token(token), (_, Some(process), _, _) => RegistryCredentialConfig::Process((
(_, Some(process)) => { process.path.resolve_program(config),
RegistryCredentialConfig::Process((process.path.resolve_program(config), process.args)) process.args,
} )),
(None, None) => { (None, None, Some(key), subject) => {
// If we couldn't find a registry-specific credential, try the global credential process. RegistryCredentialConfig::AsymmetricKey((key, subject))
if let Some(process) = config
.get::<Option<config::PathAndArgs>>("registry.credential-process")?
.filter(|_| config.cli_unstable().credential_process)
{
RegistryCredentialConfig::Process((
process.path.resolve_program(config),
process.args,
))
} else {
RegistryCredentialConfig::None
} }
} (None, None, None, _) => {
}) // If we couldn't find a registry-specific credential, try the global credential process.
if let Some(process) = config
.get::<Option<config::PathAndArgs>>("registry.credential-process")?
.filter(|_| config.cli_unstable().credential_process)
{
RegistryCredentialConfig::Process((
process.path.resolve_program(config),
process.args,
))
} else {
RegistryCredentialConfig::None
}
}
},
)
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -288,6 +327,9 @@ fn auth_token_optional(config: &Config, sid: &SourceId) -> CargoResult<Option<St
RegistryCredentialConfig::Process(process) => { RegistryCredentialConfig::Process(process) => {
run_command(config, &process, sid, Action::Get)?.unwrap() run_command(config, &process, sid, Action::Get)?.unwrap()
} }
RegistryCredentialConfig::AsymmetricKey((_secret_key, _secret_key_subject)) => {
todo!("sign the token")
}
}; };
cache.insert(url.clone(), token.clone()); cache.insert(url.clone(), token.clone());

View File

@ -1360,6 +1360,26 @@ impl Config {
); );
} }
if toml_v
.get("registry")
.and_then(|v| v.as_table())
.and_then(|t| t.get("secret-key"))
.is_some()
{
bail!(
"registry.secret-key cannot be set through --config for security reasons"
);
} else if let Some((k, _)) = toml_v
.get("registries")
.and_then(|v| v.as_table())
.and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
{
bail!(
"registries.{}.secret-key cannot be set through --config for security reasons",
k
);
}
CV::from_toml(Definition::Cli(None), toml_v) CV::from_toml(Definition::Cli(None), toml_v)
.with_context(|| format!("failed to convert --config argument `{arg}`"))? .with_context(|| format!("failed to convert --config argument `{arg}`"))?
}; };

View File

@ -435,6 +435,20 @@ fn no_disallowed_values() {
config.unwrap_err(), config.unwrap_err(),
"registries.crates-io.token cannot be set through --config for security reasons", "registries.crates-io.token cannot be set through --config for security reasons",
); );
let config = ConfigBuilder::new()
.config_arg("registry.secret-key=\"hello\"")
.build_err();
assert_error(
config.unwrap_err(),
"registry.secret-key cannot be set through --config for security reasons",
);
let config = ConfigBuilder::new()
.config_arg("registries.crates-io.secret-key=\"hello\"")
.build_err();
assert_error(
config.unwrap_err(),
"registries.crates-io.secret-key cannot be set through --config for security reasons",
);
} }
#[cargo_test] #[cargo_test]