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

ssh-vault / ssh-vault / 14317917208

07 Apr 2025 07:35PM UTC coverage: 90.38% (+15.2%) from 75.176%
14317917208

push

github

nbari
fix coverage

2236 of 2474 relevant lines covered (90.38%)

54.55 hits per line

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

98.82
/src/cli/actions/mod.rs
1
pub mod create;
2
pub mod edit;
3
pub mod fingerprint;
4
pub mod view;
5

6
use crate::tools;
7
use anyhow::{Result, anyhow};
8
use secrecy::{ExposeSecret, SecretString};
9
use std::{
10
    env,
11
    io::{Read, Seek, SeekFrom, Write},
12
    process::Command,
13
};
14
use tempfile::Builder;
15

16
#[derive(Debug)]
17
pub enum Action {
18
    Fingerprint {
19
        key: Option<String>,
20
        user: Option<String>,
21
    },
22
    Create {
23
        fingerprint: Option<String>,
24
        input: Option<String>,
25
        json: bool,
26
        key: Option<String>,
27
        user: Option<String>,
28
        vault: Option<String>,
29
    },
30
    View {
31
        key: Option<String>,
32
        output: Option<String>,
33
        passphrase: Option<SecretString>,
34
        vault: Option<String>,
35
    },
36
    Edit {
37
        key: Option<String>,
38
        passphrase: Option<SecretString>,
39
        vault: String,
40
    },
41
    Help,
42
}
43

44
pub fn process_input(buf: &mut Vec<u8>, data: Option<SecretString>) -> Result<usize> {
3✔
45
    let mut tmpfile = Builder::new()
3✔
46
        .prefix(".vault-")
3✔
47
        .suffix(".ssh")
3✔
48
        .tempfile_in(tools::get_home()?)?;
3✔
49

50
    if let Some(data) = data {
3✔
51
        write!(tmpfile, "{}", data.expose_secret())?;
3✔
52
    }
×
53

54
    let editor = env::var("EDITOR").unwrap_or_else(|_| String::from("vi"));
3✔
55

56
    let editor_parts = shell_words::split(&editor)?;
3✔
57

58
    let status = Command::new(&editor_parts[0])
3✔
59
        .args(&editor_parts[1..])
3✔
60
        .arg(tmpfile.path())
3✔
61
        .status()?;
3✔
62

63
    if !status.success() {
3✔
64
        return Err(anyhow!("Editor exited with non-zero status code"));
×
65
    }
3✔
66

3✔
67
    // Seek to start
3✔
68
    tmpfile.seek(SeekFrom::Start(0))?;
3✔
69

70
    // read the file
71
    tmpfile.read_to_end(buf)?;
3✔
72

73
    // Fill the file with zeros
74
    let zeros = vec![0u8; buf.len()];
3✔
75
    tmpfile.write_all(&zeros)?;
3✔
76

77
    Ok(buf.len())
3✔
78
}
3✔
79

80
#[cfg(test)]
81
mod tests {
82
    use crate::cli::actions::{Action, create, edit, fingerprint, view};
83
    use serde_json::Value;
84
    use std::io::Write;
85
    use tempfile::NamedTempFile;
86

87
    struct Test {
88
        input: &'static str,
89
        public_key: &'static str,
90
        private_key: &'static str,
91
        header: &'static str,
92
    }
93

94
    #[test]
95
    fn test_create_view_edit_with_input() {
1✔
96
        let tests = [
1✔
97
            Test {
1✔
98
                input: "Machs na",
1✔
99
                public_key: "test_data/ed25519.pub",
1✔
100
                private_key: "test_data/ed25519",
1✔
101
                header: "SSH-VAULT;CHACHA20-POLY1305",
1✔
102
            },
1✔
103
            Test {
1✔
104
                input: "Machs na",
1✔
105
                public_key: "test_data/id_rsa.pub",
1✔
106
                private_key: "test_data/id_rsa",
1✔
107
                header: "SSH-VAULT;AES256",
1✔
108
            },
1✔
109
            Test {
1✔
110
                input: "Arrachera is a Mexican dish made from marinated and grilled skirt steak. The steak is seasoned with a mixture of spices and marinades, giving it a rich and savory flavor. Commonly served in tacos or fajitas, arrachera is known for its tenderness and versatility in Mexican cuisine",
1✔
111
                public_key: "test_data/ed25519.pub",
1✔
112
                private_key: "test_data/ed25519",
1✔
113
                header: "SSH-VAULT;CHACHA20-POLY1305",
1✔
114
            },
1✔
115
        ];
1✔
116

117
        for test in tests.iter() {
3✔
118
            let input = test.input;
3✔
119
            let mut temp_file = NamedTempFile::new().unwrap();
3✔
120
            temp_file.write_all(input.as_bytes()).unwrap();
3✔
121
            let vault_file = NamedTempFile::new().unwrap();
3✔
122

3✔
123
            let create = Action::Create {
3✔
124
                fingerprint: None,
3✔
125
                key: Some(test.public_key.to_string()),
3✔
126
                user: None,
3✔
127
                vault: Some(vault_file.path().to_str().unwrap().to_string()),
3✔
128
                json: false,
3✔
129
                input: Some(temp_file.path().to_str().unwrap().to_string()),
3✔
130
            };
3✔
131
            let vault = create::handle(create);
3✔
132
            assert!(vault.is_ok());
3✔
133

134
            let vault_contents = std::fs::read_to_string(&vault_file).unwrap();
3✔
135
            assert!(vault_contents.starts_with(test.header));
3✔
136

137
            let output = NamedTempFile::new().unwrap();
3✔
138
            let view = Action::View {
3✔
139
                key: Some(test.private_key.to_string()),
3✔
140
                output: Some(output.path().to_str().unwrap().to_string()),
3✔
141
                passphrase: None,
3✔
142
                vault: Some(vault_file.path().to_str().unwrap().to_string()),
3✔
143
            };
3✔
144
            let vault_view = view::handle(view);
3✔
145
            assert!(vault_view.is_ok());
3✔
146

147
            let output = std::fs::read_to_string(output).unwrap();
3✔
148
            assert_eq!(input, output);
3✔
149

150
            let edit = Action::Edit {
3✔
151
                key: Some(test.private_key.to_string()),
3✔
152
                passphrase: None,
3✔
153
                vault: vault_file.path().to_str().unwrap().to_string(),
3✔
154
            };
3✔
155

3✔
156
            // set EDITOR to cat instead of vi
3✔
157
            temp_env::with_vars([("EDITOR", Some("cat"))], || {
3✔
158
                let vault_edit = edit::handle(edit);
3✔
159
                assert!(vault_edit.is_ok());
3✔
160
            });
3✔
161

3✔
162
            let vault_contents_after_edit = std::fs::read_to_string(&vault_file).unwrap();
3✔
163
            assert_ne!(vault_contents, vault_contents_after_edit);
3✔
164

165
            // check if we can still view the vault
166
            let output = NamedTempFile::new().unwrap();
3✔
167
            let view = Action::View {
3✔
168
                key: Some(test.private_key.to_string()),
3✔
169
                output: Some(output.path().to_str().unwrap().to_string()),
3✔
170
                passphrase: None,
3✔
171
                vault: Some(vault_file.path().to_str().unwrap().to_string()),
3✔
172
            };
3✔
173
            let vault_view = view::handle(view);
3✔
174
            assert!(vault_view.is_ok());
3✔
175

176
            let output = std::fs::read_to_string(output).unwrap();
3✔
177
            assert_eq!(input, output);
3✔
178

179
            // try to create again with the same vault (should fail)
180
            let create = Action::Create {
3✔
181
                fingerprint: None,
3✔
182
                key: Some(test.public_key.to_string()),
3✔
183
                user: None,
3✔
184
                vault: Some(vault_file.path().to_str().unwrap().to_string()),
3✔
185
                json: false,
3✔
186
                input: Some(temp_file.path().to_str().unwrap().to_string()),
3✔
187
            };
3✔
188
            let vault = create::handle(create);
3✔
189
            assert!(vault.is_err());
3✔
190
        }
191
    }
1✔
192

193
    #[test]
194
    fn test_create_with_json() {
1✔
195
        let tests = [
1✔
196
            Test {
1✔
197
                input: "Three may keep a secret, if two of them are dead",
1✔
198
                public_key: "test_data/ed25519.pub",
1✔
199
                private_key: "test_data/ed25519",
1✔
200
                header: "SSH-VAULT;CHACHA20-POLY1305",
1✔
201
            },
1✔
202
            Test {
1✔
203
                input: "Hello World!",
1✔
204
                public_key: "test_data/ed25519.pub",
1✔
205
                private_key: "test_data/ed25519",
1✔
206
                header: "SSH-VAULT;CHACHA20-POLY1305",
1✔
207
            },
1✔
208
        ];
1✔
209

210
        for test in tests.iter() {
2✔
211
            let input = test.input;
2✔
212
            let mut temp_file = NamedTempFile::new().unwrap();
2✔
213
            temp_file.write_all(input.as_bytes()).unwrap();
2✔
214
            let vault_json = NamedTempFile::new().unwrap();
2✔
215

2✔
216
            let create = Action::Create {
2✔
217
                fingerprint: None,
2✔
218
                key: Some(test.public_key.to_string()),
2✔
219
                user: None,
2✔
220
                vault: Some(vault_json.path().to_str().unwrap().to_string()),
2✔
221
                json: true,
2✔
222
                input: Some(temp_file.path().to_str().unwrap().to_string()),
2✔
223
            };
2✔
224
            let vault = create::handle(create);
2✔
225
            assert!(vault.is_ok());
2✔
226

227
            let vault_contents = std::fs::read_to_string(&vault_json).unwrap();
2✔
228
            let json: Value = serde_json::from_str(&vault_contents).unwrap();
2✔
229
            let vault = json["vault"].as_str().unwrap();
2✔
230

2✔
231
            let mut vault_file = NamedTempFile::new().unwrap();
2✔
232
            vault_file.write_all(vault.as_bytes()).unwrap();
2✔
233
            let output = NamedTempFile::new().unwrap();
2✔
234

2✔
235
            let view = Action::View {
2✔
236
                key: Some(test.private_key.to_string()),
2✔
237
                output: Some(output.path().to_str().unwrap().to_string()),
2✔
238
                passphrase: None,
2✔
239
                vault: Some(vault_file.path().to_str().unwrap().to_string()),
2✔
240
            };
2✔
241
            let vault_view = view::handle(view);
2✔
242
            assert!(vault_view.is_ok());
2✔
243

244
            let output = std::fs::read_to_string(output).unwrap();
2✔
245
            assert_eq!(input, output);
2✔
246
        }
247
    }
1✔
248

249
    #[test]
250
    fn test_fingerprint() {
1✔
251
        let fingerprint = Action::Fingerprint {
1✔
252
            key: Some("test_data/ed25519.pub".to_string()),
1✔
253
            user: None,
1✔
254
        };
1✔
255

1✔
256
        let fingerprint = fingerprint::handle(fingerprint);
1✔
257
        assert!(fingerprint.is_ok());
1✔
258
    }
1✔
259
}
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