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