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