mirror of
https://github.com/bitwarden/browser
synced 2026-02-05 03:03:26 +00:00
Better ssh agent default socket paths on linux and macos
This commit is contained in:
5
apps/desktop/desktop_native/Cargo.lock
generated
5
apps/desktop/desktop_native/Cargo.lock
generated
@@ -578,9 +578,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
@@ -905,6 +905,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"cbc",
|
||||
"cfg-if",
|
||||
"core-foundation",
|
||||
"desktop_objc",
|
||||
"dirs",
|
||||
|
||||
@@ -26,6 +26,7 @@ bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", re
|
||||
byteorder = "=1.5.0"
|
||||
bytes = "=1.10.1"
|
||||
cbc = "=0.1.2"
|
||||
cfg-if = "=1.0.3"
|
||||
core-foundation = "=0.10.0"
|
||||
dirs = "=6.0.0"
|
||||
ed25519 = "=2.2.3"
|
||||
|
||||
@@ -26,6 +26,7 @@ bitwarden-russh = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
cbc = { workspace = true, features = ["alloc"] }
|
||||
cfg-if = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
ed25519 = { workspace = true, features = ["pkcs8"] }
|
||||
futures = { workspace = true }
|
||||
|
||||
@@ -14,7 +14,8 @@ const ENV_BITWARDEN_SSH_AUTH_SOCK: &str = "BITWARDEN_SSH_AUTH_SOCK";
|
||||
|
||||
const FLATPACK_DATA_DIR: &str = ".var/app/com.bitwarden.desktop/data";
|
||||
|
||||
const SOCKFILE_NAME: &str = ".bitwarden-ssh-agent.sock";
|
||||
const LEGACY_SOCKFILE_NAME: &str = ".bitwarden-ssh-agent.sock";
|
||||
const SOCKFILE_NAME: &str = "ssh-agent.sock";
|
||||
|
||||
impl BitwardenDesktopAgent {
|
||||
/// Starts the Bitwarden Desktop SSH Agent server.
|
||||
@@ -27,50 +28,52 @@ impl BitwardenDesktopAgent {
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let agent_state = BitwardenDesktopAgent::new(auth_request_tx, auth_response_rx);
|
||||
|
||||
let socket_path = get_socket_path()?;
|
||||
let socket_paths = get_socket_paths()?;
|
||||
|
||||
// if the socket is already present and wasn't cleanly removed during a previous
|
||||
// runtime, remove it before beginning anew.
|
||||
remove_path(&socket_path)?;
|
||||
for socket_path in &socket_paths {
|
||||
// if the socket is already present and wasn't cleanly removed during a previous
|
||||
// runtime, remove it before beginning anew.
|
||||
remove_path(socket_path)?;
|
||||
|
||||
println!("[SSH Agent Native Module] Starting SSH Agent server {socket_path:?}");
|
||||
println!("[SSH Agent Native Module] Starting SSH Agent server {socket_path:?}");
|
||||
|
||||
match UnixListener::bind(socket_path.clone()) {
|
||||
Ok(listener) => {
|
||||
// Only the current user should be able to access the socket
|
||||
set_user_permissions(&socket_path)?;
|
||||
match UnixListener::bind(socket_path) {
|
||||
Ok(listener) => {
|
||||
// Only the current user should be able to access the socket
|
||||
set_user_permissions(socket_path)?;
|
||||
|
||||
let stream = PeercredUnixListenerStream::new(listener);
|
||||
let stream = PeercredUnixListenerStream::new(listener);
|
||||
|
||||
let cloned_agent_state = agent_state.clone();
|
||||
let cloned_keystore = cloned_agent_state.keystore.clone();
|
||||
let cloned_cancellation_token = cloned_agent_state.cancellation_token.clone();
|
||||
let cloned_agent_state = agent_state.clone();
|
||||
let cloned_keystore = cloned_agent_state.keystore.clone();
|
||||
let cloned_cancellation_token = cloned_agent_state.cancellation_token.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = ssh_agent::serve(
|
||||
stream,
|
||||
cloned_agent_state.clone(),
|
||||
cloned_keystore,
|
||||
cloned_cancellation_token,
|
||||
)
|
||||
.await;
|
||||
tokio::spawn(async move {
|
||||
let _ = ssh_agent::serve(
|
||||
stream,
|
||||
cloned_agent_state.clone(),
|
||||
cloned_keystore,
|
||||
cloned_cancellation_token,
|
||||
)
|
||||
.await;
|
||||
|
||||
cloned_agent_state
|
||||
cloned_agent_state
|
||||
.is_running
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
println!("[SSH Agent Native Module] SSH Agent server exited");
|
||||
});
|
||||
|
||||
agent_state
|
||||
.is_running
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
println!("[SSH Agent Native Module] SSH Agent server exited");
|
||||
});
|
||||
|
||||
agent_state
|
||||
.is_running
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
println!("[SSH Agent Native Module] SSH Agent is running: {socket_path:?}");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[SSH Agent Native Module] Error while starting agent server: {e}");
|
||||
return Err(e.into());
|
||||
println!("[SSH Agent Native Module] SSH Agent is running: {socket_path:?}");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[SSH Agent Native Module] Error while starting agent server: {e}");
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,14 +84,30 @@ impl BitwardenDesktopAgent {
|
||||
// one of the following:
|
||||
// - only the env var socket path if it is defined
|
||||
// - the $HOME path and our well known extension
|
||||
fn get_socket_path() -> Result<PathBuf, anyhow::Error> {
|
||||
fn get_socket_paths() -> Result<Vec<PathBuf>, anyhow::Error> {
|
||||
if let Ok(path) = std::env::var(ENV_BITWARDEN_SSH_AUTH_SOCK) {
|
||||
Ok(PathBuf::from(path))
|
||||
Ok(vec![PathBuf::from(path)])
|
||||
} else {
|
||||
println!(
|
||||
"[SSH Agent Native Module] {ENV_BITWARDEN_SSH_AUTH_SOCK} not set, using default path"
|
||||
);
|
||||
get_default_socket_path()
|
||||
|
||||
let mut paths = vec![get_legacy_default_socket_path()?];
|
||||
|
||||
let basedir = get_socket_basedir()?;
|
||||
let socket_path = get_default_socket_path(basedir);
|
||||
|
||||
// create the bitwarden subdir if needed
|
||||
fs::create_dir_all(
|
||||
socket_path
|
||||
.parent()
|
||||
.ok_or(anyhow!("Malformed default socket path."))?,
|
||||
)
|
||||
.map_err(|e| anyhow!(format!("Error creating {socket_path:?}: {e}")))?;
|
||||
|
||||
paths.push(socket_path);
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +116,7 @@ fn is_flatpak() -> bool {
|
||||
}
|
||||
|
||||
// use the $HOME directory
|
||||
fn get_default_socket_path() -> Result<PathBuf, anyhow::Error> {
|
||||
fn get_legacy_default_socket_path() -> Result<PathBuf, anyhow::Error> {
|
||||
let Ok(Some(mut ssh_agent_directory)) = my_home() else {
|
||||
println!("[SSH Agent Native Module] Could not determine home directory");
|
||||
return Err(anyhow!("Could not determine home directory."));
|
||||
@@ -107,11 +126,37 @@ fn get_default_socket_path() -> Result<PathBuf, anyhow::Error> {
|
||||
ssh_agent_directory = ssh_agent_directory.join(FLATPACK_DATA_DIR);
|
||||
}
|
||||
|
||||
ssh_agent_directory = ssh_agent_directory.join(SOCKFILE_NAME);
|
||||
ssh_agent_directory = ssh_agent_directory.join(LEGACY_SOCKFILE_NAME);
|
||||
|
||||
Ok(ssh_agent_directory)
|
||||
}
|
||||
|
||||
// use the platform's base dir
|
||||
fn get_default_socket_path(mut path: PathBuf) -> PathBuf {
|
||||
path.push("bitwarden");
|
||||
path.push(SOCKFILE_NAME);
|
||||
path
|
||||
}
|
||||
|
||||
fn get_socket_basedir() -> Result<PathBuf, anyhow::Error> {
|
||||
// currently the only delineation between mac and linux is this logic.
|
||||
// if we need to expand that, we can make unix.rs the platform agnostic shared logic,
|
||||
// and separate out into linux.rs and macos.rs
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
std::env::var("XDG_RUNTIME_DIR")
|
||||
.map(PathBuf::from)
|
||||
.map_err(|_| { anyhow!("$XDG_RUNTIME_DIR is not defined.") })
|
||||
|
||||
} else if #[cfg(target_os = "macos")]{
|
||||
Ok(std::env::temp_dir())
|
||||
|
||||
} else {
|
||||
anyhow!("Unsupported platform");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_user_permissions(path: &PathBuf) -> Result<(), anyhow::Error> {
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(0o600))
|
||||
.map_err(|e| anyhow!("Could not set socket permissions for {path:?}: {e}"))
|
||||
@@ -132,8 +177,8 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_socket_path_success() {
|
||||
let path = get_default_socket_path().unwrap();
|
||||
fn test_default_legacy_socket_path_success() {
|
||||
let path = get_legacy_default_socket_path().unwrap();
|
||||
let expected = PathBuf::from_iter([
|
||||
std::env::var("HOME").unwrap(),
|
||||
".bitwarden-ssh-agent.sock".to_string(),
|
||||
@@ -141,6 +186,17 @@ mod tests {
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_socket_path_success() {
|
||||
let mut expected = PathBuf::from("/");
|
||||
expected.push("bitwarden");
|
||||
expected.push("ssh-agent.sock");
|
||||
|
||||
let path = get_default_socket_path(PathBuf::from("/"));
|
||||
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
|
||||
fn rand_file_in_temp() -> PathBuf {
|
||||
let mut path = std::env::temp_dir();
|
||||
let s: String = rand::rng()
|
||||
|
||||
Reference in New Issue
Block a user