Skip to content

Commit 8917837

Browse files
committed
Auto merge of #8934 - ehuss:token-process, r=alexcrichton
Implement external credential process. (RFC 2730) This adds a config setting for an external process to run to fetch the token for a registry. See `unstable.md` for more details. As part of this, it adds a new `logout` command. This is currently gated on nightly with the appropriate `-Z` flag. I have included four sample wrappers that integrate with the macOS Keychain, Windows Credential Manager, GNOME libsecret, and 1password. I'm not sure if we'll ultimately ship these, but I would like to. Primarily this provided a proof-of-concept to see if the design works. **Patch Walkthrough** This is a brief overview of the changes: - Adds the `logout` command. With `cargo logout -Z unstable-options`, this allows removing the `token` from `.cargo/credentials`. With `cargo logout -Z credential-process`, this launches the process with the `erase` argument to remove the token from storage. - Credential-process handling is in the `ops/registry/auth.rs` module. I think it is pretty straightforward, it just launches the process with the appropriate store/get/erase argument. - `ops::registry::registry()` now returns the `RegistryConfig` to make it easier to pass the config information around. - `crates/credential/cargo-credential` is a helper crate for writing credential processes. - A special shorthand of the `cargo:` prefix for a credential process will launch the named process from the `libexec` directory in the sysroot (or, more specifically, the `libexec` directory next to the `cargo` process). For example `credential-process = "cargo:macos-keychain"`. My intent is to bundle these in the pre-built rust-lang distributions, and this should "just work" when used with rustup. I'm not sure how that will work with other Rust distributions, but I'm guessing they can figure it out. This should make it much easier for users to get started, but does add some integration complexity. **Questions** - I'm on the fence about the name `credential-process` vs `credentials-process`, which sounds more natural? (Or something else?) - I'm uneasy about the behavior when both `token` and `credential-process` is specified (see `warn_both_token_and_process` test). Currently it issues a warning and uses `token`. Does that make sense? What about the case where you have `registries.foo.token` for a specific registry, but then have a general `registry.credential-process` for the default (it currently warns and uses the token, maybe it should not warn?)? - I am still pretty uneasy with writing FFI wrappers, so maybe those could get a little extra scrutiny? They seem to work, but I have not extensively tested them (I tried login, publish, and logout). I have not previously used these APIs, so I am not familiar with them. - Testing the wrappers I think will be quite difficult, because some require TTY interaction (and 1password requires an online account). Or, for example in the macOS case, it has GUI dialog box where I can use my fingerprint scanner. Right now, I just build them in CI to make sure they compile. - 1password is a little weird in that it passes the token on the command-line, which is not very secure on some systems (other processes can see these sometimes). The only alternative I can think of is to not support `cargo login` and require the user to manually enter the token in the 1password GUI. I don't think the concern is too large (1password themselves seem to think it is acceptable). Should this be OK? - I'm a little uneasy with the design of `cargo login`, where it passes the token in stdin. If the wrapper requires stdin for user interaction (such as entering a password to unlock), this is quite awkward. There is a hack in the 1password example that demonstrates using `/dev/tty` and `CONIN$`, which *seems* to work, but I'm worried is fragile. I'm not very comfortable passing the token in environment variables, because those can be visible to other processes (like CLI args), but in some situations that shouldn't be too risky. Another option is to use a separate file descriptor/handle to pass the token in. Implementing that in Rust in a cross-platform way is not terribly easy, so I wanted to open this up for discussion first.
2 parents c68c622 + eabef56 commit 8917837

File tree

27 files changed

+2128
-96
lines changed

27 files changed

+2128
-96
lines changed

.github/workflows/main.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ jobs:
1818
- run: rustup update stable && rustup default stable
1919
- run: rustup component add rustfmt
2020
- run: cargo fmt --all -- --check
21-
- run: cd crates/cargo-test-macro && cargo fmt --all -- --check
22-
- run: cd crates/cargo-test-support && cargo fmt --all -- --check
23-
- run: cd crates/crates-io && cargo fmt --all -- --check
24-
- run: cd crates/resolver-tests && cargo fmt --all -- --check
25-
- run: cd crates/cargo-platform && cargo fmt --all -- --check
26-
- run: cd crates/mdman && cargo fmt --all -- --check
21+
- run: |
22+
for manifest in `find crates -name Cargo.toml`
23+
do
24+
echo check fmt for $manifest
25+
cargo fmt --all --manifest-path $manifest -- --check
26+
done
2727
2828
test:
2929
runs-on: ${{ matrix.os }}
@@ -58,7 +58,7 @@ jobs:
5858
- run: rustup target add ${{ matrix.other }}
5959
- run: rustup component add rustc-dev llvm-tools-preview rust-docs
6060
if: startsWith(matrix.rust, 'nightly')
61-
- run: sudo apt update -y && sudo apt install gcc-multilib -y
61+
- run: sudo apt update -y && sudo apt install gcc-multilib libsecret-1-0 libsecret-1-dev -y
6262
if: matrix.os == 'ubuntu-latest'
6363
- run: rustup component add rustfmt || echo "rustfmt not available"
6464

@@ -67,6 +67,13 @@ jobs:
6767
- run: cargo test --features 'deny-warnings' -p cargo-test-support
6868
- run: cargo test -p cargo-platform
6969
- run: cargo test --manifest-path crates/mdman/Cargo.toml
70+
- run: cargo build --manifest-path crates/credential/cargo-credential-1password/Cargo.toml
71+
- run: cargo build --manifest-path crates/credential/cargo-credential-gnome-secret/Cargo.toml
72+
if: matrix.os == 'ubuntu-latest'
73+
- run: cargo build --manifest-path crates/credential/cargo-credential-macos-keychain/Cargo.toml
74+
if: matrix.os == 'macos-latest'
75+
- run: cargo build --manifest-path crates/credential/cargo-credential-wincred/Cargo.toml
76+
if: matrix.os == 'windows-latest'
7077

7178
resolver:
7279
runs-on: ubuntu-latest

crates/cargo-test-support/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,6 +1544,10 @@ fn substitute_macros(input: &str) -> String {
15441544
("[INSTALLED]", " Installed"),
15451545
("[REPLACED]", " Replaced"),
15461546
("[BUILDING]", " Building"),
1547+
("[LOGIN]", " Login"),
1548+
("[LOGOUT]", " Logout"),
1549+
("[YANK]", " Yank"),
1550+
("[OWNER]", " Owner"),
15471551
];
15481552
let mut result = input.to_owned();
15491553
for &(pat, subst) in &macros {

crates/cargo-test-support/src/paths.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,27 @@ pub trait CargoPathExt {
110110
}
111111

112112
impl CargoPathExt for Path {
113-
/* Technically there is a potential race condition, but we don't
114-
* care all that much for our tests
115-
*/
116113
fn rm_rf(&self) {
117-
if self.exists() {
114+
let meta = match self.symlink_metadata() {
115+
Ok(meta) => meta,
116+
Err(e) => {
117+
if e.kind() == ErrorKind::NotFound {
118+
return;
119+
}
120+
panic!("failed to remove {:?}, could not read: {:?}", self, e);
121+
}
122+
};
123+
// There is a race condition between fetching the metadata and
124+
// actually performing the removal, but we don't care all that much
125+
// for our tests.
126+
if meta.is_dir() {
118127
if let Err(e) = remove_dir_all::remove_dir_all(self) {
119128
panic!("failed to remove {:?}: {:?}", self, e)
120129
}
130+
} else {
131+
if let Err(e) = fs::remove_file(self) {
132+
panic!("failed to remove {:?}: {:?}", self, e)
133+
}
121134
}
122135
}
123136

crates/credential/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Cargo Credential Packages
2+
3+
This directory contains Cargo packages for handling storage of tokens in a
4+
secure manner.
5+
6+
`cargo-credential` is a generic library to assist writing a credential
7+
process. The other directories contain implementations that integrate with
8+
specific credential systems.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "cargo-credential-1password"
3+
version = "0.1.0"
4+
authors = ["The Rust Project Developers"]
5+
edition = "2018"
6+
license = "MIT OR Apache-2.0"
7+
repository = "https://github.com/rust-lang/cargo"
8+
description = "A Cargo credential process that stores tokens in a 1password vault."
9+
10+
[dependencies]
11+
cargo-credential = { path = "../cargo-credential" }
12+
serde = { version = "1.0.117", features = ["derive"] }
13+
serde_json = "1.0.59"

0 commit comments

Comments
 (0)