Allow passing args to ssh (#19336)

This is useful for passing a custom identity file, jump hosts, etc.

Unlike with the v1 feature, we won't support `gh`/`gcloud` ssh wrappers
(yet?). I think the right way of supporting those would be to let
extensions provide remote projects.

Closes #19118

Release Notes:

- SSH remoting: restored ability to set arguments for SSH
This commit is contained in:
Conrad Irwin 2024-10-16 21:09:31 -06:00 committed by GitHub
parent f1d01d59ac
commit 378a2cf9d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 216 additions and 89 deletions

View file

@ -61,9 +61,89 @@ pub struct SshConnectionOptions {
pub username: Option<String>,
pub port: Option<u16>,
pub password: Option<String>,
pub args: Option<Vec<String>>,
}
impl SshConnectionOptions {
pub fn parse_command_line(input: &str) -> Result<Self> {
let input = input.trim_start_matches("ssh ");
let mut hostname: Option<String> = None;
let mut username: Option<String> = None;
let mut port: Option<u16> = None;
let mut args = 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",
];
let mut tokens = shlex::split(input)
.ok_or_else(|| anyhow!("invalid input"))?
.into_iter();
'outer: while let Some(arg) = tokens.next() {
if ALLOWED_OPTS.contains(&(&arg as &str)) {
args.push(arg.to_string());
continue;
}
if arg == "-p" {
port = tokens.next().and_then(|arg| arg.parse().ok());
continue;
} else if let Some(p) = arg.strip_prefix("-p") {
port = p.parse().ok();
continue;
}
if arg == "-l" {
username = tokens.next();
continue;
} else if let Some(l) = arg.strip_prefix("-l") {
username = Some(l.to_string());
continue;
}
for a in ALLOWED_ARGS {
if arg == *a {
args.push(arg);
if let Some(next) = tokens.next() {
args.push(next);
}
continue 'outer;
} else if arg.starts_with(a) {
args.push(arg);
continue 'outer;
}
}
if arg.starts_with("-") || hostname.is_some() {
anyhow::bail!("unsupported argument: {:?}", arg);
}
let mut input = &arg as &str;
if let Some((u, rest)) = input.split_once('@') {
input = rest;
username = Some(u.to_string());
}
if let Some((rest, p)) = input.split_once(':') {
input = rest;
port = p.parse().ok()
}
hostname = Some(input.to_string())
}
let Some(hostname) = hostname else {
anyhow::bail!("missing hostname");
};
Ok(Self {
host: hostname.to_string(),
username: username.clone(),
port,
password: None,
args: Some(args),
})
}
pub fn ssh_url(&self) -> String {
let mut result = String::from("ssh://");
if let Some(username) = &self.username {
@ -78,6 +158,10 @@ impl SshConnectionOptions {
result
}
pub fn additional_args(&self) -> Option<&Vec<String>> {
self.args.as_ref()
}
fn scp_url(&self) -> String {
if let Some(username) = &self.username {
format!("{}@{}", username, self.host)
@ -1179,6 +1263,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([
"-N",
"-o",