credential provider implementation - #12334
Conversation
|
r? @ehuss (rustbot has picked a reviewer for you, use r? to override) |
ehuss
left a comment
There was a problem hiding this comment.
This is looking great!
This is just a partial review. I'll try to finish the rest tomorrowish.
|
@arlosi Your most recent commit seems to have introduced a bunch of formatting changes. |
|
Oh, I think the let-else formatting changes just landed in nightly. Lemme look at that. |
|
Posted #12351 to update the formatting. |
ehuss
left a comment
There was a problem hiding this comment.
Review part 2. I think I've gone through everything except the tests, will try to get to those tomorrow.
| } | ||
| } | ||
|
|
||
| fn main() { |
There was a problem hiding this comment.
In the future, how hard would it be to translate the 1password provider back to a standalone executable? We haven't decided if we are going to support these providers directly. One option was to migrate these to a separate repo (perhaps under my name) if we decide that rust-lang isn't going to support these. The maintenance burden isn't too bad, so I'm not sure if it matters (and we can document that we can drop providers in the future if we can't support it).
There was a problem hiding this comment.
It's very easy to translate back. Just need to change it back to a bin crate and add:
fn main() {
cargo_credential::main(OnePasswordCredential);
}I think I'd also prefer that 1password was a standalone provider that you could cargo install.
|
☔ The latest upstream changes (presumably #12351) made this pull request unmergeable. Please resolve the merge conflicts. |
e592978 to
3b8e2c0
Compare
|
☔ The latest upstream changes (presumably #12310) made this pull request unmergeable. Please resolve the merge conflicts. |
There was a shared struct that was used for both I've also renamed the |
|
Looks great, thanks! If there are thoughts about what needs followup, please file new issues, but I can't think of anything right now. @bors r+ |
|
☀️ Test successful - checks-actions |
Update cargo 8 commits in 1b15556767f4b78a64e868eedf4073c423f02b93..7ac9416d82cd4fc5e707c9ec3574d22dff6466e5 2023-07-18 14:44:47 +0000 to 2023-07-24 14:29:38 +0000 - fix(cargo-credential): should enable feature `serde/derive` (rust-lang/cargo#12396) - fix: encode URL params correctly for SourceId in Cargo.lock (rust-lang/cargo#12280) - docs: format config override caveat as a note (rust-lang/cargo#12392) - credential provider implementation (rust-lang/cargo#12334) - feat(crates-io): expose HTTP headers and Error type (rust-lang/cargo#12310) - chore: Don't update test data (rust-lang/cargo#12380) - fix: only skip mtime check on `~/.cargo/{git,registry}` (rust-lang/cargo#12369) - Update docs for artifact JSON debuginfo levels. (rust-lang/cargo#12376) Since rust-lang/cargo#12334 makes built-in credential providers part of the cargo binary, it's no longer needed to build them in bootstrap.
Update cargo 8 commits in 1b15556767f4b78a64e868eedf4073c423f02b93..7ac9416d82cd4fc5e707c9ec3574d22dff6466e5 2023-07-18 14:44:47 +0000 to 2023-07-24 14:29:38 +0000 - fix(cargo-credential): should enable feature `serde/derive` (rust-lang/cargo#12396) - fix: encode URL params correctly for SourceId in Cargo.lock (rust-lang/cargo#12280) - docs: format config override caveat as a note (rust-lang/cargo#12392) - credential provider implementation (rust-lang/cargo#12334) - feat(crates-io): expose HTTP headers and Error type (rust-lang/cargo#12310) - chore: Don't update test data (rust-lang/cargo#12380) - fix: only skip mtime check on `~/.cargo/{git,registry}` (rust-lang/cargo#12369) - Update docs for artifact JSON debuginfo levels. (rust-lang/cargo#12376) Since rust-lang/cargo#12334 makes built-in credential providers part of the cargo binary, it's no longer needed to build them in bootstrap.
| if !std::io::stdin().is_terminal() { | ||
| let token = std::io::read_to_string(std::io::stdin()).unwrap_or_default(); | ||
| if !token.is_empty() { | ||
| token_from_stdin = Some(token); | ||
| } | ||
| } |
There was a problem hiding this comment.
Just a friendly heads up - this is causing our CI jobs to hang if we provide the token via the command-line instead of stdin.
I'm happy to open a bug for this, but just wanted to make sure you were aware.
There was a problem hiding this comment.
Sorry for the inconvenience!
I didn't quite follow the PR at that time. Is that a regression? If yes, please file an issue with reproduction, and we can then arrange a fix!
There was a problem hiding this comment.
Yes, this is a regression.
Sounds good, thanks :)
login: allow passing additional args to provider As part of moving asymmetric token support to a credential provider in #12334, support for passing `--key-subject` to `cargo login` was removed. This change allows passing additional arguments to credential providers when running `cargo login`. For example: `cargo login -- --key-subject foo`. The asymmetric token provider (`cargo:paseto`) is updated to take advantage of this and re-enables setting `--key-subject` from `cargo login`. r? `@Eh2406` cc #8933
login: allow passing additional args to provider As part of moving asymmetric token support to a credential provider in #12334, support for passing `--key-subject` to `cargo login` was removed. This change allows passing additional arguments to credential providers when running `cargo login`. For example: `cargo login -- --key-subject foo`. The asymmetric token provider (`cargo:paseto`) is updated to take advantage of this and re-enables setting `--key-subject` from `cargo login`. r? `@Eh2406` cc #8933
# `[registry]` vs `[registries.<name>]` per-key semantics Review notes for [rust-lang#17335] (min-publish-age stabilization), comparing how each config key behaves across the two tables. Verified against PR head `7b009184` with repro tests in `tests/testsuite/registry_table_confusion.rs` and the PR's own `tests/testsuite/min_publish_age.rs`. [rust-lang#17335]: rust-lang#17335 (comment) | Key | `[registry]` | `[registries.crates-io]` | `[registries.<alt>]` | Evidence | |---|---|---|---|---| | `token` | works for crates.io; does **not** follow `registry.default` | **silently ignored** (config and `CARGO_REGISTRIES_CRATES_IO_TOKEN` env) | works | `registries_crates_io_token_ignored_for_crates_io`, `registry_token_does_not_follow_registry_default` | | `credential-provider` | works for crates.io | **silently ignored** | works | `registries_crates_io_credential_provider_ignored` | | `secret-key` / `secret-key-subject` | works for crates.io | silently ignored (same code path as `token`) | works | code: `src/util/auth/mod.rs:212` returns `[registry]` for crates.io before any name lookup | | `min-publish-age` (new) | crates.io only; ignored for alt registries even with `registry.default` pointing at them | **honored; overrides `registry.min-publish-age`** | honored | `registry_alt_ignores_min_publish_age`, `registries_crates_io_overrides_registry_default` | | `global-min-publish-age` (new) | fallback for **all** registries | silently ignored (not a `RegistryConfig` field) | silently ignored | code: `src/context/schema.rs:530` | | `protocol` | silently ignored (not a `GlobalRegistryConfig` field) | honored | parsed but unused (`_protocol`) | code: `SourceId::crates_io_is_sparse` reads `registries.crates-io.protocol` only | | `index` | hard error: "no longer supported" | silently ignored (`SourceId::alt_registry` short-circuits `crates-io`) | required; defines the registry | code: `check_registry_index_not_set`, `src/workspace/source_id.rs:296` | | `default` | selects target registry for `publish`/`login`/`owner`/`yank` when `--registry`/`--index` absent | silently ignored | n/a | code: `src/util/command_prelude.rs:919` | | `global-credential-providers` | fallback providers for **all** registries | silently ignored | silently ignored | code: `src/util/auth/mod.rs:57` | Key asymmetries: * `min-publish-age` is the first key where `[registries.crates-io]` both works and overrides its `[registry]` counterpart; for the auth keys, `[registries.crates-io]` is dead config. * Auth resolves crates.io **by URL first** (`is_crates_io()`, `src/util/auth/mod.rs:212`); min-publish-age resolves **by name first** (`alt_registry_key()`, `src/resolver/version_prefs.rs:279`). A named mirror of crates.io's index gets `[registry]` credentials but `registries.<mirror>.min-publish-age`. * `registries.<name>.global-min-publish-age` and `registry.protocol` are silent no-ops — the same "subtle `s` distinction" concern raised in [rust-lang#12334](rust-lang#12334 (comment)).
# `[registry]` vs `[registries.<name>]` per-key semantics Review notes for [rust-lang#17335] (min-publish-age stabilization), comparing how each config key behaves across the two tables. Verified against PR head `7b009184` with repro tests in `tests/testsuite/registry_table_confusion.rs` and the PR's own `tests/testsuite/min_publish_age.rs`. [rust-lang#17335]: rust-lang#17335 (comment) | Key | `[registry]` | `[registries.crates-io]` | `[registries.<alt>]` | Evidence | |---|---|---|---|---| | `token` | works for crates.io; does **not** follow `registry.default` | **silently ignored** (config and `CARGO_REGISTRIES_CRATES_IO_TOKEN` env) | works | `registries_crates_io_token_ignored_for_crates_io`, `registry_token_does_not_follow_registry_default` | | `credential-provider` | works for crates.io | **silently ignored** | works | `registries_crates_io_credential_provider_ignored` | | `secret-key` / `secret-key-subject` | works for crates.io | silently ignored (same code path as `token`) | works | code: `src/util/auth/mod.rs:212` returns `[registry]` for crates.io before any name lookup | | `min-publish-age` (new) | crates.io only; ignored for alt registries even with `registry.default` pointing at them | **honored; overrides `registry.min-publish-age`** | honored | `registry_alt_ignores_min_publish_age`, `registries_crates_io_overrides_registry_default` | | `global-min-publish-age` (new) | fallback for **all** registries | silently ignored (not a `RegistryConfig` field) | silently ignored | code: `src/context/schema.rs:530` | | `protocol` | silently ignored (not a `GlobalRegistryConfig` field) | honored | parsed but unused (`_protocol`) | code: `SourceId::crates_io_is_sparse` reads `registries.crates-io.protocol` only | | `index` | hard error: "no longer supported" | silently ignored (`SourceId::alt_registry` short-circuits `crates-io`) | required; defines the registry | code: `check_registry_index_not_set`, `src/workspace/source_id.rs:296` | | `default` | selects target registry for `publish`/`login`/`owner`/`yank` when `--registry`/`--index` absent | silently ignored | n/a | code: `src/util/command_prelude.rs:919` | | `global-credential-providers` | fallback providers for **all** registries | silently ignored | silently ignored | code: `src/util/auth/mod.rs:57` | Key asymmetries: * `min-publish-age` is the first key where `[registries.crates-io]` both works and overrides its `[registry]` counterpart; for the auth keys, `[registries.crates-io]` is dead config. * Auth resolves crates.io **by URL first** (`is_crates_io()`, `src/util/auth/mod.rs:212`); min-publish-age resolves **by name first** (`alt_registry_key()`, `src/resolver/version_prefs.rs:279`). A named mirror of crates.io's index gets `[registry]` credentials but `registries.<mirror>.min-publish-age`. * `registries.<name>.global-min-publish-age` and `registry.protocol` are silent no-ops — the same "subtle `s` distinction" concern raised in [rust-lang#12334](rust-lang#12334 (comment)).
The current credential process protocol only allows sending the credential without any additional information. This changes the protocol in two important ways: Cargo will tell the credential provider what the token is needed for, and the credential provider can tell Cargo how the token can be used.
Since the credential provider knows why Cargo needs a token (
publishfor example), it can produce a signed token specifically for that operation. This would enable a credential process to produce an asymmetric token, or a token with restricted scope such as PASETO or Biscuit.The credential process can also indicate back to Cargo if the token can be cached in-memory for subsequent requests. For example, if a credential provider integrates with an SSO identity provider that provides short-lived tokens, Cargo will only continue to use the token while it is valid.
Summary of changes
credential-processtocredential-providerin config.cargo:paseto).registry-authtocredential-processcargo:token).cargo:basic).registry.credential-providers) as a list.crates.iono longer also acts as a fallback for other registries.[credential-alias]table for defining aliases of credential providers.http_registryrequests, passing them through to the cred provider.Everything remains unstable under the
-Zcredential-processflag.How to review this:
I recommend starting with the changes in
unstable.mdfor a more detailed description.Open questions
www-authenticate