Add SSH port forwards to settings (#24474)
Closes #6920 Release Notes: - Added ability to specify port forwarding settings for remote connections
This commit is contained in:
parent
58b0a6c4af
commit
5f6311171f
6 changed files with 158 additions and 6 deletions
|
@ -30,6 +30,7 @@ paths.workspace = true
|
|||
parking_lot.workspace = true
|
||||
prost.workspace = true
|
||||
rpc = { workspace = true, features = ["gpui"] }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
shlex.workspace = true
|
||||
|
|
|
@ -29,6 +29,8 @@ use rpc::{
|
|||
AnyProtoClient, EntityMessageSubscriber, ErrorExt, ProtoClient, ProtoMessageHandlerSet,
|
||||
RpcError,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::{
|
||||
fs,
|
||||
process::{self, Child, Stdio},
|
||||
|
@ -59,6 +61,16 @@ pub struct SshSocket {
|
|||
socket_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct SshPortForwardOption {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub local_host: Option<String>,
|
||||
pub local_port: u16,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remote_host: Option<String>,
|
||||
pub remote_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SshConnectionOptions {
|
||||
pub host: String,
|
||||
|
@ -66,6 +78,7 @@ pub struct SshConnectionOptions {
|
|||
pub port: Option<u16>,
|
||||
pub password: Option<String>,
|
||||
pub args: Option<Vec<String>>,
|
||||
pub port_forwards: Option<Vec<SshPortForwardOption>>,
|
||||
|
||||
pub nickname: Option<String>,
|
||||
pub upload_binary_over_ssh: bool,
|
||||
|
@ -83,6 +96,42 @@ macro_rules! shell_script {
|
|||
}};
|
||||
}
|
||||
|
||||
fn parse_port_number(port_str: &str) -> Result<u16> {
|
||||
port_str
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("Invalid port number: {}: {}", port_str, e))
|
||||
}
|
||||
|
||||
fn parse_port_forward_spec(spec: &str) -> Result<SshPortForwardOption> {
|
||||
let parts: Vec<&str> = spec.split(':').collect();
|
||||
|
||||
match parts.len() {
|
||||
4 => {
|
||||
let local_port = parse_port_number(parts[1])?;
|
||||
let remote_port = parse_port_number(parts[3])?;
|
||||
|
||||
Ok(SshPortForwardOption {
|
||||
local_host: Some(parts[0].to_string()),
|
||||
local_port,
|
||||
remote_host: Some(parts[2].to_string()),
|
||||
remote_port,
|
||||
})
|
||||
}
|
||||
3 => {
|
||||
let local_port = parse_port_number(parts[0])?;
|
||||
let remote_port = parse_port_number(parts[2])?;
|
||||
|
||||
Ok(SshPortForwardOption {
|
||||
local_host: None,
|
||||
local_port,
|
||||
remote_host: Some(parts[1].to_string()),
|
||||
remote_port,
|
||||
})
|
||||
}
|
||||
_ => anyhow::bail!("Invalid port forward format"),
|
||||
}
|
||||
}
|
||||
|
||||
impl SshConnectionOptions {
|
||||
pub fn parse_command_line(input: &str) -> Result<Self> {
|
||||
let input = input.trim_start_matches("ssh ");
|
||||
|
@ -90,14 +139,14 @@ impl SshConnectionOptions {
|
|||
let mut username: Option<String> = None;
|
||||
let mut port: Option<u16> = None;
|
||||
let mut args = Vec::new();
|
||||
let mut port_forwards: Vec<SshPortForwardOption> = Vec::new();
|
||||
|
||||
// disallowed: -E, -e, -F, -f, -G, -g, -M, -N, -n, -O, -q, -S, -s, -T, -t, -V, -v, -W
|
||||
const ALLOWED_OPTS: &[&str] = &[
|
||||
"-4", "-6", "-A", "-a", "-C", "-K", "-k", "-X", "-x", "-Y", "-y",
|
||||
];
|
||||
const ALLOWED_ARGS: &[&str] = &[
|
||||
"-B", "-b", "-c", "-D", "-I", "-i", "-J", "-L", "-l", "-m", "-o", "-P", "-p", "-R",
|
||||
"-w",
|
||||
"-B", "-b", "-c", "-D", "-I", "-i", "-J", "-l", "-m", "-o", "-P", "-p", "-R", "-w",
|
||||
];
|
||||
|
||||
let mut tokens = shlex::split(input)
|
||||
|
@ -123,6 +172,20 @@ impl SshConnectionOptions {
|
|||
username = Some(l.to_string());
|
||||
continue;
|
||||
}
|
||||
if arg == "-L" || arg.starts_with("-L") {
|
||||
let forward_spec = if arg == "-L" {
|
||||
tokens.next()
|
||||
} else {
|
||||
Some(arg.strip_prefix("-L").unwrap().to_string())
|
||||
};
|
||||
|
||||
if let Some(spec) = forward_spec {
|
||||
port_forwards.push(parse_port_forward_spec(&spec)?);
|
||||
} else {
|
||||
anyhow::bail!("Missing port forward format");
|
||||
}
|
||||
}
|
||||
|
||||
for a in ALLOWED_ARGS {
|
||||
if arg == *a {
|
||||
args.push(arg);
|
||||
|
@ -154,10 +217,16 @@ impl SshConnectionOptions {
|
|||
anyhow::bail!("missing hostname");
|
||||
};
|
||||
|
||||
let port_forwards = match port_forwards.len() {
|
||||
0 => None,
|
||||
_ => Some(port_forwards),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
host: hostname.to_string(),
|
||||
username: username.clone(),
|
||||
port,
|
||||
port_forwards,
|
||||
args: Some(args),
|
||||
password: None,
|
||||
nickname: None,
|
||||
|
@ -179,8 +248,28 @@ impl SshConnectionOptions {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn additional_args(&self) -> Option<&Vec<String>> {
|
||||
self.args.as_ref()
|
||||
pub fn additional_args(&self) -> Vec<String> {
|
||||
let mut args = self.args.iter().flatten().cloned().collect::<Vec<String>>();
|
||||
|
||||
if let Some(forwards) = &self.port_forwards {
|
||||
args.extend(forwards.iter().map(|pf| {
|
||||
let local_host = match &pf.local_host {
|
||||
Some(host) => host,
|
||||
None => "localhost",
|
||||
};
|
||||
let remote_host = match &pf.remote_host {
|
||||
Some(host) => host,
|
||||
None => "localhost",
|
||||
};
|
||||
|
||||
format!(
|
||||
"-L{}:{}:{}:{}",
|
||||
local_host, pf.local_port, remote_host, pf.remote_port
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
fn scp_url(&self) -> String {
|
||||
|
@ -1454,7 +1543,7 @@ impl SshRemoteConnection {
|
|||
.stderr(Stdio::piped())
|
||||
.env("SSH_ASKPASS_REQUIRE", "force")
|
||||
.env("SSH_ASKPASS", &askpass_script_path)
|
||||
.args(connection_options.additional_args().unwrap_or(&Vec::new()))
|
||||
.args(connection_options.additional_args())
|
||||
.args([
|
||||
"-N",
|
||||
"-o",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue