Use ssh2 config IdentityFile as fallback for key based authentication

This commit is contained in:
veeso
2022-08-30 17:47:55 +02:00
parent 833cd7d3ba
commit 64e3848c97
5 changed files with 112 additions and 19 deletions

View File

@@ -35,6 +35,7 @@ Released on ??
- **Changed keybindings for BACKTAB**: backtab will now change the explorer tab
- To active the LOG PANEL, use `P`
- **Yes/No dialogs** are now answerable by pressing `Y` or `N` on your keyboard ([Issue 121](https://github.com/veeso/termscp/issues/121))
- **Use ssh2 config IdentityFile** as fallback for key based authentication
- **Bugfix**
- Fixed [Issue 122](https://github.com/veeso/termscp/issues/122)
- Fixed version comparison when going above 0.9
@@ -44,6 +45,8 @@ Released on ??
- Bump `keyring` to `1.2.0`
- Bump `open` to `3.0.2`
- Changed `regex` to `lazy-regex 2.3.0`
- Bump `remotefs-ssh` to `0.1.2`
- Bump `ssh2-config` to `0.1.3`
- Bump `tuirealm` to `1.8.0`
- Bump `tui-realm-stdlib` to `1.1.7`
- Added `version-compare 0.1.0`

17
Cargo.lock generated
View File

@@ -1719,6 +1719,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696"
[[package]]
name = "path-slash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
[[package]]
name = "pathdiff"
version = "0.2.1"
@@ -1938,7 +1944,7 @@ checksum = "9939849e0b3895b07f258458d0fa5cd4632c78ffc517e66e1f758ddf23990542"
dependencies = [
"chrono",
"log",
"path-slash",
"path-slash 0.1.5",
"remotefs",
"rust-s3",
"thiserror",
@@ -1952,7 +1958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "359fd989a6ad50fa6defae771e453695412cbfdb5476f49e42d1bb1d51b5c096"
dependencies = [
"log",
"path-slash",
"path-slash 0.1.5",
"remotefs",
"suppaftp",
"users",
@@ -1960,14 +1966,14 @@ dependencies = [
[[package]]
name = "remotefs-ssh"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2ac5a7f475c1b63ffd2aaab57ca4e37e6a6ed23461046d4fb1554835e6088a"
checksum = "190106b53b839cf094172d2f1bc14e8d2846a69de06b239f11c2fc353080e9e0"
dependencies = [
"chrono",
"lazy_static",
"log",
"path-slash",
"path-slash 0.2.1",
"regex",
"remotefs",
"ssh2",
@@ -2511,6 +2517,7 @@ dependencies = [
"serde",
"serial_test",
"simplelog",
"ssh2-config",
"tempfile",
"thiserror",
"toml",

View File

@@ -51,11 +51,12 @@ rand = "0.8.5"
remotefs = "^0.2.0"
remotefs-aws-s3 = "^0.2.0"
remotefs-ftp = { version = "^0.1.0", features = [ "secure" ] }
remotefs-ssh = { version = "^0.1.1", features = [ "ssh2-vendored" ] }
remotefs-ssh = { version = "^0.1.2", features = [ "ssh2-vendored" ] }
rpassword = "6.0.1"
self_update = { version = "0.30.0", features = [ "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
serde = { version = "^1.0.0", features = [ "derive" ] }
simplelog = "0.12.0"
ssh2-config = "^0.1.3"
tempfile = "3.2.0"
thiserror = "^1.0.0"
toml = "0.5.0"

View File

@@ -5,12 +5,16 @@
// Locals
use super::config_client::ConfigClient;
// Ext
use remotefs_ssh::SshKeyStorage as SshKeyStorageT;
use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
use ssh2_config::SshConfig;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub struct SshKeyStorage {
hosts: HashMap<String, PathBuf>, // Association between {user}@{host} and RSA key path
/// Association between {user}@{host} and RSA key path
hosts: HashMap<String, PathBuf>,
/// Ssh2 configuration
ssh_config: Option<SshConfig>,
}
impl SshKeyStorage {
@@ -19,6 +23,7 @@ impl SshKeyStorage {
pub fn empty() -> Self {
SshKeyStorage {
hosts: HashMap::new(),
ssh_config: None,
}
}
@@ -34,21 +39,67 @@ impl SshKeyStorage {
let key: String = Self::make_mapkey(host, username);
self.hosts.insert(key, p);
}
}
impl SshKeyStorageT for SshKeyStorage {
fn resolve(&self, host: &str, username: &str) -> Option<&Path> {
/// Parse ssh2 config
fn parse_ssh2_config(path: &str) -> Result<SshConfig, String> {
use std::fs::File;
use std::io::BufReader;
let mut reader = File::open(path)
.map_err(|e| format!("failed to open {}: {}", path, e))
.map(BufReader::new)?;
SshConfig::default()
.parse(&mut reader)
.map_err(|e| format!("Failed to parse ssh2 config: {}", e))
}
/// Resolve host via termscp ssh keys storage
fn resolve_host_in_termscp_storage(&self, host: &str, username: &str) -> Option<&Path> {
let key: String = Self::make_mapkey(host, username);
self.hosts.get(&key).map(|x| x.as_path())
}
/// Resolve host via ssh2 configuration
fn resolve_host_in_ssh2_configuration(&self, host: &str) -> Option<PathBuf> {
if let Some(config) = self.ssh_config.as_ref() {
let params = config.query(host);
params
.identity_file
.as_ref()
.and_then(|x| x.get(0).cloned())
} else {
debug!("ssh2 config is not available; no key has been found");
None
}
}
}
impl SshKeyStorageTrait for SshKeyStorage {
fn resolve(&self, host: &str, username: &str) -> Option<PathBuf> {
// search in termscp keys
if let Some(path) = self.resolve_host_in_termscp_storage(host, username) {
return Some(path.to_path_buf());
}
debug!(
"couldn't find any ssh key associated to {} at {}. Trying with ssh2 config",
username, host
);
// otherwise search in configuration
self.resolve_host_in_ssh2_configuration(host)
}
}
impl From<&ConfigClient> for SshKeyStorage {
fn from(cfg_client: &ConfigClient) -> Self {
// read ssh2 config
let ssh_config = cfg_client.get_ssh_config().and_then(|x| {
debug!("reading ssh config at {}", x);
Self::parse_ssh2_config(x).ok()
});
let mut hosts: HashMap<String, PathBuf> =
HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
debug!("Setting up SSH key storage");
// Iterate over keys
// Iterate over keys in storage
for key in cfg_client.iter_ssh_keys() {
match cfg_client.get_ssh_key(key) {
Ok(host) => match host {
@@ -66,7 +117,7 @@ impl From<&ConfigClient> for SshKeyStorage {
info!("Got SSH key for {}", key);
}
// Return storage
SshKeyStorage { hosts }
SshKeyStorage { hosts, ssh_config }
}
}
@@ -75,6 +126,7 @@ mod tests {
use super::*;
use crate::system::config_client::ConfigClient;
use crate::utils::test_helpers;
use pretty_assertions::assert_eq;
use std::path::Path;
@@ -103,6 +155,35 @@ mod tests {
assert!(storage.resolve("deskichup", "veeso").is_none());
}
#[test]
fn sould_resolve_key_from_ssh2_config() {
let rsa_key = test_helpers::create_sample_file_with_content("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a");
let ssh_config_file = test_helpers::create_sample_file_with_content(format!(
r#"
Host test
HostName 127.0.0.1
Port 2222
User test
IdentityFile {}
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
"#,
rsa_key.path().display()
));
// make storage
let tmp_dir: tempfile::TempDir = tempfile::TempDir::new().ok().unwrap();
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
.ok()
.unwrap();
client.set_ssh_config(Some(ssh_config_file.path().to_string_lossy().to_string()));
let storage: SshKeyStorage = SshKeyStorage::from(&client);
assert_eq!(
storage.resolve("test", "pi").unwrap().as_path(),
rsa_key.path()
);
}
#[test]
fn test_system_sshkey_storage_empty() {
let storage: SshKeyStorage = SshKeyStorage::empty();

View File

@@ -21,14 +21,15 @@ pub fn create_sample_file_entry() -> (File, NamedTempFile) {
)
}
/// Create sample file with default lorem ipsum content
pub fn create_sample_file() -> NamedTempFile {
// Write
create_sample_file_with_content("Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus.")
}
/// Create sample file with provided content
pub fn create_sample_file_with_content(content: impl std::fmt::Display) -> NamedTempFile {
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
writeln!(
tmpfile,
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus."
)
.unwrap();
writeln!(tmpfile, "{}", content).unwrap();
tmpfile
}