]>
Commit | Line | Data |
---|---|---|
feb1645f | 1 | use anyhow::{bail, Error}; |
d5a48b5c DM |
2 | use serde_json::Value; |
3 | ||
6ef1b649 WB |
4 | use proxmox_router::{cli::*, ApiHandler, RpcEnvironment}; |
5 | use proxmox_schema::api; | |
25877d05 | 6 | use proxmox_sys::linux::tty; |
d5a48b5c | 7 | |
5839c469 DM |
8 | use pbs_api_types::{ |
9 | Fingerprint, Kdf, DRIVE_NAME_SCHEMA, TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
10 | PASSWORD_HINT_SCHEMA, | |
11 | }; | |
12 | ||
eb5e0ae6 | 13 | use pbs_datastore::paperkey::{PaperkeyFormat, generate_paper_key}; |
5839c469 | 14 | use pbs_config::tape_encryption_keys::{load_key_configs,complete_key_fingerprint}; |
86fb3877 | 15 | |
5839c469 | 16 | use proxmox_backup::api2; |
d5a48b5c DM |
17 | |
18 | pub fn encryption_key_commands() -> CommandLineInterface { | |
19 | ||
20 | let cmd_def = CliCommandMap::new() | |
21 | .insert("list", CliCommand::new(&API_METHOD_LIST_KEYS)) | |
22 | .insert( | |
23 | "create", | |
feb1645f DM |
24 | CliCommand::new(&API_METHOD_CREATE_KEY) |
25 | ) | |
301b8aa0 DM |
26 | .insert( |
27 | "change-passphrase", | |
28 | CliCommand::new(&API_METHOD_CHANGE_PASSPHRASE) | |
29 | .arg_param(&["fingerprint"]) | |
30 | .completion_cb("fingerprint", complete_key_fingerprint) | |
31 | ) | |
69b8bc3b DM |
32 | .insert( |
33 | "show", | |
34 | CliCommand::new(&API_METHOD_SHOW_KEY) | |
35 | .arg_param(&["fingerprint"]) | |
36 | .completion_cb("fingerprint", complete_key_fingerprint) | |
37 | ) | |
64b83c3d DM |
38 | .insert( |
39 | "paperkey", | |
40 | CliCommand::new(&API_METHOD_PAPER_KEY) | |
41 | .arg_param(&["fingerprint"]) | |
42 | .completion_cb("fingerprint", complete_key_fingerprint) | |
43 | ) | |
feb1645f DM |
44 | .insert( |
45 | "restore", | |
46 | CliCommand::new(&API_METHOD_RESTORE_KEY) | |
d5a48b5c DM |
47 | ) |
48 | .insert( | |
49 | "remove", | |
50 | CliCommand::new(&api2::config::tape_encryption_keys::API_METHOD_DELETE_KEY) | |
51 | .arg_param(&["fingerprint"]) | |
52 | .completion_cb("fingerprint", complete_key_fingerprint) | |
53 | ) | |
54 | ; | |
55 | ||
56 | cmd_def.into() | |
57 | } | |
58 | ||
64b83c3d DM |
59 | #[api( |
60 | input: { | |
61 | properties: { | |
62 | fingerprint: { | |
63 | schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
64 | }, | |
65 | subject: { | |
d1d74c43 | 66 | description: "Include the specified subject as title text.", |
64b83c3d DM |
67 | optional: true, |
68 | }, | |
69 | "output-format": { | |
70 | type: PaperkeyFormat, | |
71 | optional: true, | |
72 | }, | |
73 | }, | |
74 | }, | |
75 | )] | |
76 | /// Generate a printable, human readable text file containing the encryption key. | |
77 | /// | |
78 | /// This also includes a scanable QR code for fast key restore. | |
79 | fn paper_key( | |
80 | fingerprint: Fingerprint, | |
81 | subject: Option<String>, | |
82 | output_format: Option<PaperkeyFormat>, | |
83 | ) -> Result<(), Error> { | |
84 | ||
85 | let (config_map, _digest) = load_key_configs()?; | |
86 | ||
87 | let key_config = match config_map.get(&fingerprint) { | |
88 | Some(key_config) => key_config, | |
89 | None => bail!("tape encryption key '{}' does not exist.", fingerprint), | |
90 | }; | |
91 | ||
92 | let data: String = serde_json::to_string_pretty(&key_config)?; | |
93 | ||
94 | generate_paper_key(std::io::stdout(), &data, subject, output_format) | |
95 | } | |
96 | ||
69b8bc3b DM |
97 | #[api( |
98 | input: { | |
99 | properties: { | |
100 | fingerprint: { | |
101 | schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
102 | }, | |
103 | "output-format": { | |
104 | schema: OUTPUT_FORMAT, | |
105 | optional: true, | |
106 | }, | |
107 | }, | |
108 | }, | |
109 | )] | |
d1d74c43 | 110 | /// Print the encryption key's metadata. |
69b8bc3b DM |
111 | fn show_key( |
112 | param: Value, | |
113 | rpcenv: &mut dyn RpcEnvironment, | |
114 | ) -> Result<(), Error> { | |
115 | ||
116 | let output_format = get_output_format(¶m); | |
117 | ||
118 | let info = &api2::config::tape_encryption_keys::API_METHOD_READ_KEY; | |
119 | let mut data = match info.handler { | |
120 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | |
121 | _ => unreachable!(), | |
122 | }; | |
123 | ||
6ef1b649 | 124 | let options = proxmox_router::cli::default_table_format_options() |
69b8bc3b | 125 | .column(ColumnConfig::new("kdf")) |
770a36e5 WB |
126 | .column(ColumnConfig::new("created").renderer(pbs_tools::format::render_epoch)) |
127 | .column(ColumnConfig::new("modified").renderer(pbs_tools::format::render_epoch)) | |
69b8bc3b DM |
128 | .column(ColumnConfig::new("fingerprint")) |
129 | .column(ColumnConfig::new("hint")); | |
130 | ||
131 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); | |
132 | ||
133 | Ok(()) | |
134 | } | |
135 | ||
301b8aa0 DM |
136 | #[api( |
137 | input: { | |
138 | properties: { | |
139 | kdf: { | |
140 | type: Kdf, | |
141 | optional: true, | |
142 | }, | |
143 | fingerprint: { | |
144 | schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
145 | }, | |
146 | hint: { | |
147 | schema: PASSWORD_HINT_SCHEMA, | |
148 | optional: true, | |
149 | }, | |
150 | }, | |
151 | }, | |
152 | )] | |
153 | /// Change the encryption key's password. | |
154 | fn change_passphrase( | |
155 | mut param: Value, | |
156 | rpcenv: &mut dyn RpcEnvironment, | |
157 | ) -> Result<(), Error> { | |
158 | ||
159 | if !tty::stdin_isatty() { | |
160 | bail!("unable to change passphrase - no tty"); | |
161 | } | |
162 | ||
163 | let password = tty::read_password("Current Tape Encryption Key Password: ")?; | |
164 | ||
165 | let new_password = tty::read_and_verify_password("New Tape Encryption Key Password: ")?; | |
166 | ||
167 | param["password"] = String::from_utf8(password)?.into(); | |
168 | param["new-password"] = String::from_utf8(new_password)?.into(); | |
169 | ||
170 | let info = &api2::config::tape_encryption_keys::API_METHOD_CHANGE_PASSPHRASE; | |
171 | match info.handler { | |
172 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | |
173 | _ => unreachable!(), | |
174 | }; | |
175 | ||
176 | Ok(()) | |
177 | } | |
178 | ||
feb1645f DM |
179 | #[api( |
180 | input: { | |
181 | properties: { | |
182 | drive: { | |
183 | schema: DRIVE_NAME_SCHEMA, | |
184 | optional: true, | |
185 | }, | |
186 | }, | |
187 | }, | |
188 | )] | |
189 | /// Restore encryption key from tape (read password from stdin) | |
190 | async fn restore_key( | |
191 | mut param: Value, | |
192 | rpcenv: &mut dyn RpcEnvironment, | |
193 | ) -> Result<(), Error> { | |
194 | ||
1ce8e905 | 195 | let (config, _digest) = pbs_config::drive::config()?; |
85cdc4f3 | 196 | param["drive"] = crate::extract_drive_name(&mut param, &config)?.into(); |
feb1645f DM |
197 | |
198 | if !tty::stdin_isatty() { | |
199 | bail!("no password input mechanism available"); | |
200 | } | |
201 | ||
202 | let password = tty::read_password("Tepe Encryption Key Password: ")?; | |
203 | param["password"] = String::from_utf8(password)?.into(); | |
204 | ||
205 | let info = &api2::tape::drive::API_METHOD_RESTORE_KEY; | |
206 | match info.handler { | |
207 | ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?, | |
208 | _ => unreachable!(), | |
209 | }; | |
210 | ||
211 | Ok(()) | |
212 | } | |
213 | ||
214 | #[api( | |
215 | input: { | |
216 | properties: { | |
e5b6c933 DM |
217 | kdf: { |
218 | type: Kdf, | |
219 | optional: true, | |
220 | }, | |
feb1645f DM |
221 | hint: { |
222 | description: "Password restore hint.", | |
223 | type: String, | |
224 | min_length: 1, | |
225 | max_length: 32, | |
226 | }, | |
227 | }, | |
228 | }, | |
229 | )] | |
230 | /// Create key (read password from stdin) | |
231 | fn create_key( | |
232 | mut param: Value, | |
233 | rpcenv: &mut dyn RpcEnvironment, | |
234 | ) -> Result<(), Error> { | |
235 | ||
236 | if !tty::stdin_isatty() { | |
237 | bail!("no password input mechanism available"); | |
238 | } | |
239 | ||
240 | let password = tty::read_and_verify_password("Tape Encryption Key Password: ")?; | |
241 | ||
242 | param["password"] = String::from_utf8(password)?.into(); | |
243 | ||
244 | let info = &api2::config::tape_encryption_keys::API_METHOD_CREATE_KEY; | |
245 | let fingerprint = match info.handler { | |
246 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | |
247 | _ => unreachable!(), | |
248 | }; | |
249 | ||
250 | println!("{}", fingerprint); | |
251 | ||
252 | Ok(()) | |
253 | } | |
254 | ||
255 | ||
d5a48b5c DM |
256 | #[api( |
257 | input: { | |
258 | properties: { | |
259 | "output-format": { | |
260 | schema: OUTPUT_FORMAT, | |
261 | optional: true, | |
262 | }, | |
263 | }, | |
264 | }, | |
265 | )] | |
266 | /// List keys | |
267 | fn list_keys( | |
268 | param: Value, | |
269 | rpcenv: &mut dyn RpcEnvironment, | |
270 | ) -> Result<(), Error> { | |
271 | ||
272 | let output_format = get_output_format(¶m); | |
273 | let info = &api2::config::tape_encryption_keys::API_METHOD_LIST_KEYS; | |
274 | let mut data = match info.handler { | |
275 | ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | |
276 | _ => unreachable!(), | |
277 | }; | |
278 | ||
279 | let options = default_table_format_options() | |
280 | .column(ColumnConfig::new("fingerprint")) | |
281 | .column(ColumnConfig::new("hint")) | |
282 | ; | |
283 | ||
284 | format_and_print_result_full(&mut data, &info.returns, &output_format, &options); | |
285 | ||
286 | Ok(()) | |
287 | } |