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