Harden forced ssh flags: accept-new host keys, block multiplexing, tighten identity auth
BatchMode=yes already blocked TTY prompts, but a brand-new host with no known_hosts entry failed outright on first connect since ssh had no way to ask for acceptance. Add StrictHostKeyChecking=accept-new (TOFU, still hard-fails on a changed known host) plus LogLevel=ERROR to keep the resulting "permanently added" notice out of profile logs. Also force ControlMaster=no/ControlPath=none and ClearAllForwardings=yes so a user's own ~/.ssh/config can't make porthole's spawned ssh share a multiplexed connection or apply extra forwards - the supervisor's process-based tracking assumes one spawned ssh exclusively owns one tunnel. ServerAliveCountMax=3 makes dead-connection detection time deterministic against the profile's keepalive, and -T is explicit no-pty insurance alongside the existing -N. IdentitiesOnly=yes is added whenever a profile sets an identity file, avoiding auth-failure lockouts from also offering agent/default keys. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
40
src/ssh.rs
40
src/ssh.rs
@@ -15,12 +15,25 @@ pub fn build(profile: &Profile) -> Command {
|
|||||||
"-o",
|
"-o",
|
||||||
"BatchMode=yes",
|
"BatchMode=yes",
|
||||||
"-o",
|
"-o",
|
||||||
|
"StrictHostKeyChecking=accept-new",
|
||||||
|
"-o",
|
||||||
|
"LogLevel=ERROR",
|
||||||
|
"-o",
|
||||||
"ExitOnForwardFailure=yes",
|
"ExitOnForwardFailure=yes",
|
||||||
"-o",
|
"-o",
|
||||||
"ConnectTimeout=10",
|
"ConnectTimeout=10",
|
||||||
"-o",
|
"-o",
|
||||||
|
"ServerAliveCountMax=3",
|
||||||
|
"-o",
|
||||||
|
"ControlMaster=no",
|
||||||
|
"-o",
|
||||||
|
"ControlPath=none",
|
||||||
|
"-o",
|
||||||
|
"ClearAllForwardings=yes",
|
||||||
|
"-o",
|
||||||
&format!("ServerAliveInterval={}", profile.keepalive),
|
&format!("ServerAliveInterval={}", profile.keepalive),
|
||||||
"-N",
|
"-N",
|
||||||
|
"-T",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let (jumps, target) = profile.ssh_target();
|
let (jumps, target) = profile.ssh_target();
|
||||||
@@ -31,6 +44,7 @@ pub fn build(profile: &Profile) -> Command {
|
|||||||
cmd.arg("-p").arg(profile.ssh_port.to_string());
|
cmd.arg("-p").arg(profile.ssh_port.to_string());
|
||||||
if let Some(identity) = &profile.identity {
|
if let Some(identity) = &profile.identity {
|
||||||
cmd.arg("-i").arg(identity);
|
cmd.arg("-i").arg(identity);
|
||||||
|
cmd.args(["-o", "IdentitiesOnly=yes"]);
|
||||||
}
|
}
|
||||||
if let Some(user) = &profile.user {
|
if let Some(user) = &profile.user {
|
||||||
cmd.arg("-l").arg(user);
|
cmd.arg("-l").arg(user);
|
||||||
@@ -86,4 +100,30 @@ mod tests {
|
|||||||
let l_idx = args.iter().position(|a| a == "-L").expect("-L present");
|
let l_idx = args.iter().position(|a| a == "-L").expect("-L present");
|
||||||
assert_eq!(args[l_idx + 1], "5432:db.internal:5432");
|
assert_eq!(args[l_idx + 1], "5432:db.internal:5432");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn identities_only_set_when_identity_given() {
|
||||||
|
let p = Profile::new(
|
||||||
|
"t".into(),
|
||||||
|
&ProfileEdits {
|
||||||
|
local: Some("5432:db.internal:5432".into()),
|
||||||
|
via: Some(vec!["jumpbox".into()]),
|
||||||
|
identity: Some("/home/me/.ssh/id_ed25519".into()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let args: Vec<String> = build(&p).get_args().map(|a| a.to_string_lossy().into_owned()).collect();
|
||||||
|
let i_idx = args.iter().position(|a| a == "-i").expect("-i present");
|
||||||
|
assert_eq!(args[i_idx + 1], "/home/me/.ssh/id_ed25519");
|
||||||
|
assert!(args.windows(2).any(|w| w == ["-o", "IdentitiesOnly=yes"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn identities_only_absent_without_identity() {
|
||||||
|
let p = profile_with(vec!["jumpbox"]);
|
||||||
|
assert!(p.identity.is_none());
|
||||||
|
let args: Vec<String> = build(&p).get_args().map(|a| a.to_string_lossy().into_owned()).collect();
|
||||||
|
assert!(!args.windows(2).any(|w| w == ["-o", "IdentitiesOnly=yes"]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user