]>
Commit | Line | Data |
---|---|---|
4de1c42c | 1 | use anyhow::{bail, format_err, Error}; |
25877d05 | 2 | use hex::FromHex; |
4de1c42c | 3 | use serde_json::Value; |
d5a48b5c | 4 | |
4de1c42c | 5 | use proxmox_router::{http_bail, ApiMethod, Permission, Router, RpcEnvironment}; |
8d6425aa | 6 | use proxmox_schema::{api, param_bail}; |
d5a48b5c | 7 | |
5839c469 | 8 | use pbs_api_types::{ |
4de1c42c TL |
9 | Authid, Fingerprint, Kdf, KeyInfo, PASSWORD_HINT_SCHEMA, PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY, |
10 | PROXMOX_CONFIG_DIGEST_SCHEMA, TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
5839c469 DM |
11 | }; |
12 | ||
77d6d7a2 SS |
13 | use pbs_config::CachedUserInfo; |
14 | ||
21211748 | 15 | use pbs_config::open_backup_lockfile; |
8ebb984f WB |
16 | use pbs_key_config::KeyConfig; |
17 | ||
18 | use crate::tape::encryption_keys::{ | |
4de1c42c | 19 | insert_key, load_key_configs, load_keys, save_key_configs, save_keys, TAPE_KEYS_LOCKFILE, |
5839c469 | 20 | }; |
86fb3877 | 21 | |
d5a48b5c | 22 | #[api( |
d5a48b5c DM |
23 | input: { |
24 | properties: {}, | |
25 | }, | |
26 | returns: { | |
27 | description: "The list of tape encryption keys (with config digest).", | |
28 | type: Array, | |
69b8bc3b | 29 | items: { type: KeyInfo }, |
d5a48b5c | 30 | }, |
ccdf327a DM |
31 | access: { |
32 | permission: &Permission::Privilege(&["tape", "pool"], PRIV_TAPE_AUDIT, false), | |
33 | }, | |
d5a48b5c DM |
34 | )] |
35 | /// List existing keys | |
36 | pub fn list_keys( | |
37 | _param: Value, | |
38 | _info: &ApiMethod, | |
41c1a179 | 39 | rpcenv: &mut dyn RpcEnvironment, |
69b8bc3b | 40 | ) -> Result<Vec<KeyInfo>, Error> { |
feb1645f | 41 | let (key_map, digest) = load_key_configs()?; |
d5a48b5c DM |
42 | |
43 | let mut list = Vec::new(); | |
feb1645f | 44 | |
69b8bc3b DM |
45 | for (_fingerprint, item) in key_map.iter() { |
46 | list.push(item.into()); | |
d5a48b5c | 47 | } |
feb1645f | 48 | |
16f6766a | 49 | rpcenv["digest"] = hex::encode(digest).into(); |
d5a48b5c DM |
50 | |
51 | Ok(list) | |
52 | } | |
301b8aa0 DM |
53 | |
54 | #[api( | |
4dafc513 | 55 | protected: true, |
301b8aa0 DM |
56 | input: { |
57 | properties: { | |
58 | kdf: { | |
59 | type: Kdf, | |
60 | optional: true, | |
61 | }, | |
62 | fingerprint: { | |
63 | schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
64 | }, | |
65 | password: { | |
66 | description: "The current password.", | |
67 | min_length: 5, | |
77d6d7a2 | 68 | optional: true, |
301b8aa0 DM |
69 | }, |
70 | "new-password": { | |
71 | description: "The new password.", | |
72 | min_length: 5, | |
73 | }, | |
74 | hint: { | |
75 | schema: PASSWORD_HINT_SCHEMA, | |
76 | }, | |
77d6d7a2 SS |
77 | force: { |
78 | optional: true, | |
79 | type: bool, | |
80 | description: "Reset the passphrase for a tape key, using the root-only accessible copy.", | |
81 | default: false, | |
82 | }, | |
301b8aa0 DM |
83 | digest: { |
84 | optional: true, | |
85 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
86 | }, | |
87 | }, | |
88 | }, | |
ccdf327a DM |
89 | access: { |
90 | permission: &Permission::Privilege(&["tape", "pool"], PRIV_TAPE_MODIFY, false), | |
91 | }, | |
301b8aa0 DM |
92 | )] |
93 | /// Change the encryption key's password (and password hint). | |
e1db0670 | 94 | #[allow(clippy::too_many_arguments)] |
301b8aa0 DM |
95 | pub fn change_passphrase( |
96 | kdf: Option<Kdf>, | |
77d6d7a2 | 97 | password: Option<String>, |
301b8aa0 DM |
98 | new_password: String, |
99 | hint: String, | |
77d6d7a2 | 100 | force: bool, |
301b8aa0 DM |
101 | fingerprint: Fingerprint, |
102 | digest: Option<String>, | |
4de1c42c | 103 | rpcenv: &mut dyn RpcEnvironment, |
301b8aa0 | 104 | ) -> Result<(), Error> { |
301b8aa0 DM |
105 | let kdf = kdf.unwrap_or_default(); |
106 | ||
107 | if let Kdf::None = kdf { | |
4de1c42c TL |
108 | param_bail!( |
109 | "kdf", | |
110 | format_err!("Please specify a key derivation function (none is not allowed here).") | |
111 | ); | |
301b8aa0 DM |
112 | } |
113 | ||
7526d864 | 114 | let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; |
301b8aa0 DM |
115 | |
116 | let (mut config_map, expected_digest) = load_key_configs()?; | |
117 | ||
118 | if let Some(ref digest) = digest { | |
25877d05 | 119 | let digest = <[u8; 32]>::from_hex(digest)?; |
301b8aa0 DM |
120 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
121 | } | |
122 | ||
123 | let key_config = match config_map.get(&fingerprint) { | |
124 | Some(key_config) => key_config, | |
4de1c42c TL |
125 | None => http_bail!( |
126 | NOT_FOUND, | |
127 | "tape encryption key configuration '{}' does not exist.", | |
128 | fingerprint | |
129 | ), | |
77d6d7a2 SS |
130 | }; |
131 | ||
132 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
133 | let user_info = CachedUserInfo::new()?; | |
134 | ||
135 | if force && !user_info.is_superuser(&auth_id) { | |
136 | bail!("resetting the key's passphrase requires root privileges") | |
137 | } | |
138 | ||
139 | let (key, created, fingerprint) = match (force, &password) { | |
4de1c42c TL |
140 | (true, Some(_)) => param_bail!( |
141 | "password", | |
142 | format_err!("password is not allowed when using force") | |
143 | ), | |
8d6425aa | 144 | (false, None) => param_bail!("password", format_err!("missing parameter: password")), |
77d6d7a2 SS |
145 | (false, Some(pass)) => key_config.decrypt(&|| Ok(pass.as_bytes().to_vec()))?, |
146 | (true, None) => { | |
4de1c42c TL |
147 | let key = load_keys()? |
148 | .0 | |
149 | .get(&fingerprint) | |
150 | .ok_or_else(|| { | |
151 | format_err!( | |
152 | "failed to reset passphrase, could not find key '{}'", | |
153 | fingerprint | |
154 | ) | |
155 | })? | |
156 | .key; | |
157 | ||
158 | (key, key_config.created, fingerprint) | |
77d6d7a2 | 159 | } |
301b8aa0 DM |
160 | }; |
161 | ||
301b8aa0 DM |
162 | let mut new_key_config = KeyConfig::with_key(&key, new_password.as_bytes(), kdf)?; |
163 | new_key_config.created = created; // keep original value | |
301b8aa0 DM |
164 | new_key_config.hint = Some(hint); |
165 | ||
166 | config_map.insert(fingerprint, new_key_config); | |
167 | ||
168 | save_key_configs(config_map)?; | |
169 | ||
170 | Ok(()) | |
171 | } | |
172 | ||
d5a48b5c DM |
173 | #[api( |
174 | protected: true, | |
175 | input: { | |
176 | properties: { | |
e5b6c933 DM |
177 | kdf: { |
178 | type: Kdf, | |
179 | optional: true, | |
180 | }, | |
d5a48b5c DM |
181 | password: { |
182 | description: "A secret password.", | |
183 | min_length: 5, | |
184 | }, | |
185 | hint: { | |
82a103c8 | 186 | schema: PASSWORD_HINT_SCHEMA, |
ae60eed3 MF |
187 | optional: true, |
188 | }, | |
189 | key: { | |
ede9dc0d | 190 | description: "Restore/Re-create a key from this JSON string.", |
ae60eed3 MF |
191 | type: String, |
192 | min_length: 300, | |
193 | max_length: 600, | |
194 | optional: true, | |
d5a48b5c DM |
195 | }, |
196 | }, | |
197 | }, | |
feb1645f DM |
198 | returns: { |
199 | schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
200 | }, | |
ccdf327a DM |
201 | access: { |
202 | permission: &Permission::Privilege(&["tape", "pool"], PRIV_TAPE_MODIFY, false), | |
203 | }, | |
d5a48b5c DM |
204 | )] |
205 | /// Create a new encryption key | |
206 | pub fn create_key( | |
e5b6c933 | 207 | kdf: Option<Kdf>, |
d5a48b5c | 208 | password: String, |
ae60eed3 MF |
209 | hint: Option<String>, |
210 | key: Option<String>, | |
ede9dc0d | 211 | _rpcenv: &mut dyn RpcEnvironment, |
d5a48b5c | 212 | ) -> Result<Fingerprint, Error> { |
e5b6c933 DM |
213 | let kdf = kdf.unwrap_or_default(); |
214 | ||
ede9dc0d TL |
215 | if key.is_none() { |
216 | if let Kdf::None = kdf { | |
217 | param_bail!( | |
218 | "kdf", | |
219 | format_err!("Please specify a key derivation function (none is not allowed here).") | |
220 | ); | |
221 | } | |
222 | if hint.is_none() { | |
223 | param_bail!("hint", format_err!("Please specify either a hint or a key")); | |
224 | } | |
ae60eed3 | 225 | } |
e5b6c933 | 226 | |
ede9dc0d | 227 | let (key_decrypt, mut key_config) = match key { |
ae60eed3 MF |
228 | Some(key) => { |
229 | let key_config: KeyConfig = | |
230 | serde_json::from_str(&key).map_err(|err| format_err!("<errmsg>: {}", err))?; | |
ede9dc0d TL |
231 | let (key_decrypt, _created, _fp) = |
232 | key_config.decrypt(&|| Ok(password.as_bytes().to_vec()))?; | |
233 | (key_decrypt, key_config) | |
ae60eed3 | 234 | } |
ede9dc0d | 235 | None => KeyConfig::new(password.as_bytes(), kdf)?, |
ae60eed3 | 236 | }; |
d5a48b5c | 237 | |
ae60eed3 MF |
238 | if hint.is_some() { |
239 | key_config.hint = hint; | |
240 | } | |
d5a48b5c | 241 | |
ede9dc0d | 242 | let fingerprint = key_config.fingerprint.clone().unwrap(); |
ae60eed3 | 243 | insert_key(key_decrypt, key_config, false)?; |
d5a48b5c DM |
244 | |
245 | Ok(fingerprint) | |
246 | } | |
247 | ||
69b8bc3b DM |
248 | #[api( |
249 | input: { | |
250 | properties: { | |
251 | fingerprint: { | |
252 | schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
253 | }, | |
254 | }, | |
255 | }, | |
256 | returns: { | |
257 | type: KeyInfo, | |
258 | }, | |
ccdf327a DM |
259 | access: { |
260 | permission: &Permission::Privilege(&["tape", "pool"], PRIV_TAPE_AUDIT, false), | |
261 | }, | |
69b8bc3b DM |
262 | )] |
263 | /// Get key config (public key part) | |
264 | pub fn read_key( | |
265 | fingerprint: Fingerprint, | |
266 | _rpcenv: &mut dyn RpcEnvironment, | |
267 | ) -> Result<KeyInfo, Error> { | |
69b8bc3b DM |
268 | let (config_map, _digest) = load_key_configs()?; |
269 | ||
270 | let key_config = match config_map.get(&fingerprint) { | |
271 | Some(key_config) => key_config, | |
4de1c42c TL |
272 | None => http_bail!( |
273 | NOT_FOUND, | |
274 | "tape encryption key '{}' does not exist.", | |
275 | fingerprint | |
276 | ), | |
69b8bc3b DM |
277 | }; |
278 | ||
279 | if key_config.kdf.is_none() { | |
280 | bail!("found unencrypted key - internal error"); | |
281 | } | |
282 | ||
283 | Ok(key_config.into()) | |
284 | } | |
285 | ||
d5a48b5c DM |
286 | #[api( |
287 | protected: true, | |
288 | input: { | |
289 | properties: { | |
290 | fingerprint: { | |
291 | schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, | |
292 | }, | |
293 | digest: { | |
294 | optional: true, | |
295 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
296 | }, | |
297 | }, | |
298 | }, | |
ccdf327a DM |
299 | access: { |
300 | permission: &Permission::Privilege(&["tape", "pool"], PRIV_TAPE_MODIFY, false), | |
301 | }, | |
d5a48b5c DM |
302 | )] |
303 | /// Remove a encryption key from the database | |
304 | /// | |
305 | /// Please note that you can no longer access tapes using this key. | |
306 | pub fn delete_key( | |
307 | fingerprint: Fingerprint, | |
308 | digest: Option<String>, | |
309 | _rpcenv: &mut dyn RpcEnvironment, | |
310 | ) -> Result<(), Error> { | |
7526d864 | 311 | let _lock = open_backup_lockfile(TAPE_KEYS_LOCKFILE, None, true)?; |
d5a48b5c | 312 | |
feb1645f DM |
313 | let (mut config_map, expected_digest) = load_key_configs()?; |
314 | let (mut key_map, _) = load_keys()?; | |
d5a48b5c DM |
315 | |
316 | if let Some(ref digest) = digest { | |
25877d05 | 317 | let digest = <[u8; 32]>::from_hex(digest)?; |
d5a48b5c DM |
318 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
319 | } | |
320 | ||
feb1645f | 321 | match config_map.get(&fingerprint) { |
4de1c42c TL |
322 | Some(_) => { |
323 | config_map.remove(&fingerprint); | |
324 | } | |
325 | None => http_bail!( | |
326 | NOT_FOUND, | |
327 | "tape encryption key '{}' does not exist.", | |
328 | fingerprint | |
329 | ), | |
d5a48b5c | 330 | } |
feb1645f | 331 | save_key_configs(config_map)?; |
d5a48b5c | 332 | |
feb1645f | 333 | key_map.remove(&fingerprint); |
d5a48b5c DM |
334 | save_keys(key_map)?; |
335 | ||
336 | Ok(()) | |
337 | } | |
338 | ||
339 | const ITEM_ROUTER: Router = Router::new() | |
69b8bc3b | 340 | .get(&API_METHOD_READ_KEY) |
301b8aa0 | 341 | .put(&API_METHOD_CHANGE_PASSPHRASE) |
d5a48b5c DM |
342 | .delete(&API_METHOD_DELETE_KEY); |
343 | ||
344 | pub const ROUTER: Router = Router::new() | |
345 | .get(&API_METHOD_LIST_KEYS) | |
346 | .post(&API_METHOD_CREATE_KEY) | |
347 | .match_all("fingerprint", &ITEM_ROUTER); |