• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

veeso / termscp / 4926442260

pending completion
4926442260

push

github

veeso
rustfmt.toml

5042 of 5376 relevant lines covered (93.79%)

15.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.05
/src/system/sshkey_storage.rs
1
//! ## SshKeyStorage
2✔
2
//!
3
//! `SshKeyStorage` is the module which behaves a storage for ssh keys
4

5
// Locals
6
use std::collections::HashMap;
7
use std::path::{Path, PathBuf};
8

9
// Ext
10
use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
11
use ssh2_config::SshConfig;
12

13
use super::config_client::ConfigClient;
14

15
#[derive(Default)]
4✔
16
pub struct SshKeyStorage {
17
    /// Association between {user}@{host} and RSA key path
18
    hosts: HashMap<String, PathBuf>,
2✔
19
    /// Ssh2 configuration
20
    ssh_config: Option<SshConfig>,
2✔
21
}
22

23
impl SshKeyStorage {
24
    /// Make mapkey from host and username
25
    fn make_mapkey(host: &str, username: &str) -> String {
6✔
26
        format!("{username}@{host}")
6✔
27
    }
6✔
28

29
    #[cfg(test)]
30
    /// Add a key to storage
31
    /// NOTE: available only for tests
32
    pub fn add_key(&mut self, host: &str, username: &str, p: PathBuf) {
1✔
33
        let key: String = Self::make_mapkey(host, username);
1✔
34
        self.hosts.insert(key, p);
1✔
35
    }
1✔
36

37
    /// Parse ssh2 config
38
    fn parse_ssh2_config(path: &str) -> Result<SshConfig, String> {
1✔
39
        use std::fs::File;
40
        use std::io::BufReader;
41

42
        let mut reader = File::open(path)
2✔
43
            .map_err(|e| format!("failed to open {path}: {e}"))
1✔
44
            .map(BufReader::new)?;
×
45
        SshConfig::default()
1✔
46
            .parse(&mut reader)
47
            .map_err(|e| format!("Failed to parse ssh2 config: {e}"))
×
48
    }
1✔
49

50
    /// Resolve host via termscp ssh keys storage
51
    fn resolve_host_in_termscp_storage(&self, host: &str, username: &str) -> Option<&Path> {
4✔
52
        let key: String = Self::make_mapkey(host, username);
4✔
53
        self.hosts.get(&key).map(|x| x.as_path())
6✔
54
    }
4✔
55

56
    /// Resolve host via ssh2 configuration
57
    fn resolve_host_in_ssh2_configuration(&self, host: &str) -> Option<PathBuf> {
2✔
58
        self.ssh_config.as_ref().and_then(|x| {
3✔
59
            let key = x
1✔
60
                .query(host)
1✔
61
                .identity_file
62
                .as_ref()
63
                .and_then(|x| x.get(0).cloned());
2✔
64

65
            key
1✔
66
        })
1✔
67
    }
2✔
68
}
69

70
impl SshKeyStorageTrait for SshKeyStorage {
71
    fn resolve(&self, host: &str, username: &str) -> Option<PathBuf> {
4✔
72
        // search in termscp keys
73
        if let Some(path) = self.resolve_host_in_termscp_storage(host, username) {
4✔
74
            return Some(path.to_path_buf());
2✔
75
        }
76
        debug!(
2✔
77
            "couldn't find any ssh key associated to {} at {}. Trying with ssh2 config",
78
            username, host
79
        );
80
        // otherwise search in configuration
81
        let key = self.resolve_host_in_ssh2_configuration(host)?;
6✔
82
        debug!("Found key in SSH config for {host}: {}", key.display());
1✔
83
        Some(key)
1✔
84
    }
4✔
85
}
86

87
impl From<&ConfigClient> for SshKeyStorage {
88
    fn from(cfg_client: &ConfigClient) -> Self {
4✔
89
        // read ssh2 config
90
        let ssh_config = cfg_client.get_ssh_config().and_then(|x| {
5✔
91
            debug!("reading ssh config at {}", x);
1✔
92
            Self::parse_ssh2_config(x).ok()
1✔
93
        });
1✔
94
        let mut hosts: HashMap<String, PathBuf> =
95
            HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
4✔
96
        debug!("Setting up SSH key storage");
4✔
97
        // Iterate over keys in storage
98
        for key in cfg_client.iter_ssh_keys() {
5✔
99
            match cfg_client.get_ssh_key(key) {
1✔
100
                Ok(host) => match host {
1✔
101
                    Some((addr, username, rsa_key_path)) => {
1✔
102
                        let key_name: String = Self::make_mapkey(&addr, &username);
1✔
103
                        hosts.insert(key_name, rsa_key_path);
1✔
104
                    }
1✔
105
                    None => continue,
106
                },
107
                Err(err) => {
×
108
                    error!("Failed to get SSH key for {}: {}", key, err);
×
109
                    continue;
110
                }
×
111
            }
112
            info!("Got SSH key for {}", key);
1✔
113
        }
114
        // Return storage
115
        SshKeyStorage { hosts, ssh_config }
4✔
116
    }
4✔
117
}
118

119
#[cfg(test)]
120
mod tests {
121

122
    use std::path::Path;
123

124
    use pretty_assertions::assert_eq;
125

126
    use super::*;
127
    use crate::system::config_client::ConfigClient;
128
    use crate::utils::test_helpers;
129

130
    #[test]
131
    fn test_system_sshkey_storage_new() {
2✔
132
        let tmp_dir: tempfile::TempDir = tempfile::TempDir::new().ok().unwrap();
1✔
133
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
134
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
135
            .ok()
136
            .unwrap();
137
        // Add ssh key
138
        assert!(client
1✔
139
            .add_ssh_key("192.168.1.31", "pi", "piroporopero")
140
            .is_ok());
141
        // Create ssh key storage
142
        let storage: SshKeyStorage = SshKeyStorage::from(&client);
1✔
143
        // Verify key exists
144
        let mut exp_key_path: PathBuf = key_path;
1✔
145
        exp_key_path.push("pi@192.168.1.31.key");
1✔
146
        assert_eq!(
1✔
147
            *storage.resolve("192.168.1.31", "pi").unwrap(),
1✔
148
            exp_key_path
149
        );
150
        // Verify unexisting key
151
        assert!(storage.resolve("deskichup", "veeso").is_none());
1✔
152
    }
2✔
153

154
    #[test]
155
    fn sould_resolve_key_from_ssh2_config() {
2✔
156
        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");
1✔
157
        let ssh_config_file = test_helpers::create_sample_file_with_content(format!(
1✔
158
            r#"
159
Host test
160
        HostName 127.0.0.1
161
        Port 2222
162
        User test
163
        IdentityFile {}
164
        StrictHostKeyChecking no
165
        UserKnownHostsFile /dev/null
166
"#,
167
            rsa_key.path().display()
1✔
168
        ));
169
        // make storage
170
        let tmp_dir: tempfile::TempDir = tempfile::TempDir::new().ok().unwrap();
1✔
171
        let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
1✔
172
        let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
1✔
173
            .ok()
174
            .unwrap();
175
        client.set_ssh_config(Some(ssh_config_file.path().to_string_lossy().to_string()));
1✔
176
        let storage: SshKeyStorage = SshKeyStorage::from(&client);
1✔
177
        assert_eq!(
1✔
178
            storage.resolve("test", "pi").unwrap().as_path(),
1✔
179
            rsa_key.path()
1✔
180
        );
181
    }
2✔
182

183
    #[test]
184
    fn test_system_sshkey_storage_empty() {
2✔
185
        let storage: SshKeyStorage = SshKeyStorage::default();
1✔
186
        assert_eq!(storage.hosts.len(), 0);
1✔
187
    }
2✔
188

189
    #[test]
190
    fn test_system_sshkey_storage_add() {
2✔
191
        let mut storage: SshKeyStorage = SshKeyStorage::default();
1✔
192
        storage.add_key("deskichup", "veeso", PathBuf::from("/tmp/omar"));
1✔
193
        assert_eq!(
1✔
194
            *storage.resolve("deskichup", "veeso").unwrap(),
1✔
195
            PathBuf::from("/tmp/omar")
1✔
196
        );
197
    }
2✔
198

199
    /// Get paths for configuration and keys directory
200
    fn get_paths(dir: &Path) -> (PathBuf, PathBuf) {
2✔
201
        let mut k: PathBuf = PathBuf::from(dir);
2✔
202
        let mut c: PathBuf = k.clone();
2✔
203
        k.push("ssh-keys/");
2✔
204
        c.push("config.toml");
2✔
205
        (c, k)
2✔
206
    }
2✔
207
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc