ssh_vault/cli/actions/
create.rs1use crate::cli::actions::{Action, process_input};
2use crate::vault::{SshVault, crypto, dio, find, online, remote};
3use anyhow::{Result, anyhow};
4use secrecy::SecretSlice;
5use serde::{Deserialize, Serialize};
6use ssh_key::PublicKey;
7use std::io::{Read, Write};
8
9#[derive(Serialize, Deserialize)]
10pub struct JsonVault {
11 vault: String,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 private_key: Option<String>,
14}
15
16pub fn handle(action: Action) -> Result<()> {
23 match action {
24 Action::Create {
25 fingerprint,
26 key,
27 user,
28 vault,
29 json,
30 input,
31 } => {
32 let mut helper: Option<String> = None;
34
35 let ssh_key: PublicKey = if let Some(user) = user {
36 if user == "new" && (key.is_some() || fingerprint.is_some()) {
38 return Err(anyhow!("Options -k and -f not required when using -u new"));
39 }
40
41 let int_key: Option<u32> = key.as_ref().and_then(|s| s.parse::<u32>().ok());
42
43 let keys = remote::get_keys(&user)?;
45
46 let ssh_key = remote::get_user_key(&keys, int_key, &fingerprint)?;
48
49 if let Ok(key) = online::get_private_key_id(&ssh_key, &user)
51 && !key.is_empty()
52 {
53 helper = Some(key);
54 }
55
56 ssh_key
57 } else {
58 find::public_key(key)?
59 };
60
61 let key_type = find::key_type(&ssh_key.algorithm())?;
62
63 let v = SshVault::new(&key_type, Some(ssh_key), None)?;
64
65 let mut buffer = Vec::new();
66
67 let skip_editor = input.as_ref().is_some_and(|stdin| stdin == "-");
69
70 let (mut input, output) = dio::setup_io(input, vault)?;
72
73 if !output.is_empty()? {
74 return Err(anyhow!("Vault file already exists"));
75 }
76
77 if input.is_terminal() {
78 if skip_editor {
79 input.read_to_end(&mut buffer)?;
80 } else {
81 process_input(&mut buffer, None)?;
83 }
84 } else {
85 input.read_to_end(&mut buffer)?;
87 }
88
89 let password: SecretSlice<u8> = crypto::gen_password()?;
91
92 let vault = v.create(password, &mut buffer)?;
94
95 format(output, vault, json, helper)?;
97 }
98 _ => unreachable!(),
99 }
100 Ok(())
101}
102
103fn format<W: Write>(
104 mut output: W,
105 vault: String,
106 json: bool,
107 helper: Option<String>,
108) -> Result<()> {
109 if json {
111 let json_vault = JsonVault {
112 vault,
113 private_key: helper,
114 };
115
116 let json = serde_json::to_string(&json_vault)?;
117
118 output.write_all(json.as_bytes())?;
119 } else if let Some(helper) = helper {
120 let format = format!("echo \"{vault}\" | ssh-vault view -k {helper}");
121 output.write_all(format.as_bytes())?;
122 } else {
123 output.write_all(vault.as_bytes())?;
124 }
125
126 Ok(())
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_format() -> Result<(), Box<dyn std::error::Error>> {
135 let mut output = Vec::new();
136 let vault = "vault".to_string();
137 let json = false;
138 let helper = None;
139
140 format(&mut output, vault, json, helper)?;
141
142 assert_eq!(output, b"vault");
143 Ok(())
144 }
145
146 #[test]
147 fn test_format_helper() -> Result<(), Box<dyn std::error::Error>> {
148 let mut output = Vec::new();
149 let vault = "vault".to_string();
150 let json = false;
151 let helper = Some("helper".to_string());
152
153 format(&mut output, vault, json, helper)?;
154
155 assert_eq!(output, b"echo \"vault\" | ssh-vault view -k helper");
156 Ok(())
157 }
158
159 #[test]
160 fn test_format_json() -> Result<(), Box<dyn std::error::Error>> {
161 let mut output = Vec::new();
162 let vault = "vault".to_string();
163 let json = true;
164 let helper = None;
165
166 format(&mut output, vault, json, helper)?;
167
168 assert_eq!(output, b"{\"vault\":\"vault\"}");
169 Ok(())
170 }
171
172 #[test]
173 fn test_format_helper_json() -> Result<(), Box<dyn std::error::Error>> {
174 let mut output = Vec::new();
175 let vault = "vault".to_string();
176 let json = true;
177 let helper = Some("helper".to_string());
178
179 format(&mut output, vault, json, helper)?;
180
181 assert_eq!(output, b"{\"vault\":\"vault\",\"private_key\":\"helper\"}");
182 Ok(())
183 }
184}