]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/tape_encryption_keys.rs
api: tape: don't allow overwriting of ids in changer/drive config
[proxmox-backup.git] / src / api2 / config / tape_encryption_keys.rs
CommitLineData
4de1c42c 1use anyhow::{bail, format_err, Error};
25877d05 2use hex::FromHex;
4de1c42c 3use serde_json::Value;
d5a48b5c 4
4de1c42c 5use proxmox_router::{http_bail, ApiMethod, Permission, Router, RpcEnvironment};
8d6425aa 6use proxmox_schema::{api, param_bail};
d5a48b5c 7
5839c469 8use 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
13use pbs_config::CachedUserInfo;
14
21211748 15use pbs_config::open_backup_lockfile;
8ebb984f
WB
16use pbs_key_config::KeyConfig;
17
18use 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
36pub 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
95pub 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
206pub 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)
264pub 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.
306pub 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
339const 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
344pub const ROUTER: Router = Router::new()
345 .get(&API_METHOD_LIST_KEYS)
346 .post(&API_METHOD_CREATE_KEY)
347 .match_all("fingerprint", &ITEM_ROUTER);