From 71a4f289cf9642cbf007b7802fad5b05133e1ed6 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Sat, 12 Sep 2026 18:08:23 +0200 Subject: [PATCH] Fix nested runtime panic during SSH key authentication --- crates/storage/src/git/ssh_push_tests.rs | 15 +++++++++- crates/storage/src/ssh.rs | 35 +++++++++++++++--------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/crates/storage/src/git/ssh_push_tests.rs b/crates/storage/src/git/ssh_push_tests.rs index c52e0e7..4c1e067 100644 --- a/crates/storage/src/git/ssh_push_tests.rs +++ b/crates/storage/src/git/ssh_push_tests.rs @@ -755,8 +755,21 @@ fn non_fast_forward_and_cancellation_fail_without_blind_replay() { local.add_remote("origin", remote.url()).expect("remote"); let control = GitOperationControl::default(); let cancel = control.clone(); + let observed = server.observed.clone(); let cancellation = thread::spawn(move || { - thread::sleep(Duration::from_millis(100)); + let deadline = std::time::Instant::now() + Duration::from_secs(5); + while observed + .lock() + .expect("observed") + .receive_requests + .is_empty() + { + assert!( + std::time::Instant::now() < deadline, + "push request not received" + ); + thread::sleep(Duration::from_millis(1)); + } cancel.cancel(); }); assert!(matches!( diff --git a/crates/storage/src/ssh.rs b/crates/storage/src/ssh.rs index 7367ae2..37f6a49 100644 --- a/crates/storage/src/ssh.rs +++ b/crates/storage/src/ssh.rs @@ -162,13 +162,11 @@ impl SshSession { control.report(GitProgressPhase::Authenticating)?; let user = connection.user.as_deref().unwrap_or("git"); let authenticated = match &connection.identity { - SshIdentitySource::KeyFile(path) => runtime.block_on(authenticate_key_file( - &mut handle, - user, - path, - passphrases, - control, - ))?, + SshIdentitySource::KeyFile(path) => { + // Synchronous secret stores may drive their own runtime. + let key = load_identity(path, passphrases)?; + runtime.block_on(authenticate_key_file(&mut handle, user, key, control))? + } SshIdentitySource::Agent { fingerprint, socket, @@ -770,13 +768,10 @@ impl client::Handler for HostVerifier { } } -async fn authenticate_key_file( - handle: &mut client::Handle, - user: &str, +fn load_identity( path: &Path, passphrases: &dyn SshPassphraseProvider, - control: &GitOperationControl, -) -> Result { +) -> Result { let metadata = fs::symlink_metadata(path).map_err(|_| GitError::SshIdentityMissing { path: path.to_owned(), })?; @@ -802,6 +797,15 @@ async fn authenticate_key_file( fingerprint: fingerprint.clone(), })?; } + Ok(key) +} + +async fn authenticate_key_file( + handle: &mut client::Handle, + user: &str, + key: PrivateKey, + control: &GitOperationControl, +) -> Result { let hash = rsa_hash(handle, key.public_key(), control).await?; let result = controlled( handle.authenticate_publickey(user, PrivateKeyWithHashAlg::new(Arc::new(key), hash)), @@ -1230,7 +1234,12 @@ mod tests { &self, fingerprint: &SshFingerprint, ) -> Result { - self.0 + // Model a synchronous secret store that drives its own async backend. + static RUNTIME: std::sync::OnceLock = + std::sync::OnceLock::new(); + RUNTIME + .get_or_init(|| tokio::runtime::Runtime::new().expect("secret store runtime")) + .block_on(async { self.0 }) .map(|value| SecretBytes::new(value.to_vec())) .ok_or_else(|| GitError::SshKeyPassphraseUnavailable { fingerprint: fingerprint.clone(),