tape: generate random encryptions keys and store key_config on media
This commit is contained in:
@ -15,9 +15,12 @@ use crate::{
|
||||
config::{
|
||||
tape_encryption_keys::{
|
||||
TAPE_KEYS_LOCKFILE,
|
||||
EncryptionKeyInfo,
|
||||
generate_tape_encryption_key,
|
||||
load_keys,
|
||||
load_key_configs,
|
||||
save_keys,
|
||||
save_key_configs,
|
||||
insert_key,
|
||||
},
|
||||
},
|
||||
api2::types::{
|
||||
@ -25,12 +28,13 @@ use crate::{
|
||||
PROXMOX_CONFIG_DIGEST_SCHEMA,
|
||||
TapeKeyMetadata,
|
||||
},
|
||||
backup::Fingerprint,
|
||||
backup::{
|
||||
Fingerprint,
|
||||
},
|
||||
tools::format::as_fingerprint,
|
||||
};
|
||||
|
||||
#[api(
|
||||
protected: true,
|
||||
input: {
|
||||
properties: {},
|
||||
},
|
||||
@ -47,17 +51,17 @@ pub fn list_keys(
|
||||
mut rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<Vec<TapeKeyMetadata>, Error> {
|
||||
|
||||
let (key_map, digest) = load_keys()?;
|
||||
let (key_map, digest) = load_key_configs()?;
|
||||
|
||||
let mut list = Vec::new();
|
||||
|
||||
for (_fingerprint, item) in key_map {
|
||||
|
||||
for (fingerprint, item) in key_map {
|
||||
list.push(TapeKeyMetadata {
|
||||
hint: item.hint,
|
||||
fingerprint: as_fingerprint(item.fingerprint.bytes()),
|
||||
fingerprint: as_fingerprint(fingerprint.bytes()),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
|
||||
|
||||
Ok(list)
|
||||
@ -71,11 +75,15 @@ pub fn list_keys(
|
||||
min_length: 5,
|
||||
},
|
||||
hint: {
|
||||
description: "Password restore hint",
|
||||
description: "Password restore hint.",
|
||||
min_length: 1,
|
||||
max_length: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
|
||||
},
|
||||
)]
|
||||
/// Create a new encryption key
|
||||
pub fn create_key(
|
||||
@ -84,26 +92,11 @@ pub fn create_key(
|
||||
_rpcenv: &mut dyn RpcEnvironment
|
||||
) -> Result<Fingerprint, Error> {
|
||||
|
||||
let key = openssl::sha::sha256(password.as_bytes()); // fixme: better KDF ??
|
||||
let (key, key_config) = generate_tape_encryption_key(password.as_bytes())?;
|
||||
|
||||
let item = EncryptionKeyInfo::new(&key, hint);
|
||||
let fingerprint = key_config.fingerprint.clone().unwrap();
|
||||
|
||||
let _lock = open_file_locked(
|
||||
TAPE_KEYS_LOCKFILE,
|
||||
std::time::Duration::new(10, 0),
|
||||
true,
|
||||
)?;
|
||||
|
||||
let (mut key_map, _) = load_keys()?;
|
||||
|
||||
let fingerprint = item.fingerprint.clone();
|
||||
|
||||
if let Some(_) = key_map.get(&fingerprint) {
|
||||
bail!("encryption key '{}' already exists.", fingerprint);
|
||||
}
|
||||
|
||||
key_map.insert(fingerprint.clone(), item);
|
||||
save_keys(key_map)?;
|
||||
insert_key(key, key_config, hint)?;
|
||||
|
||||
Ok(fingerprint)
|
||||
}
|
||||
@ -131,25 +124,28 @@ pub fn delete_key(
|
||||
digest: Option<String>,
|
||||
_rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
|
||||
let _lock = open_file_locked(
|
||||
TAPE_KEYS_LOCKFILE,
|
||||
std::time::Duration::new(10, 0),
|
||||
true,
|
||||
)?;
|
||||
|
||||
let (mut key_map, expected_digest) = load_keys()?;
|
||||
let (mut config_map, expected_digest) = load_key_configs()?;
|
||||
let (mut key_map, _) = load_keys()?;
|
||||
|
||||
if let Some(ref digest) = digest {
|
||||
let digest = proxmox::tools::hex_to_digest(digest)?;
|
||||
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
|
||||
}
|
||||
|
||||
match key_map.get(&fingerprint) {
|
||||
Some(_) => { key_map.remove(&fingerprint); },
|
||||
match config_map.get(&fingerprint) {
|
||||
Some(_) => { config_map.remove(&fingerprint); },
|
||||
None => bail!("tape encryption key '{}' does not exist.", fingerprint),
|
||||
}
|
||||
save_key_configs(config_map)?;
|
||||
|
||||
key_map.remove(&fingerprint);
|
||||
save_keys(key_map)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -24,6 +24,7 @@ use crate::{
|
||||
self,
|
||||
drive::check_drive_exists,
|
||||
},
|
||||
backup::decrypt_key_config,
|
||||
api2::{
|
||||
types::{
|
||||
UPID_SCHEMA,
|
||||
@ -408,7 +409,7 @@ fn write_media_label(
|
||||
worker.log(format!("Label media '{}' for pool '{}'", label.label_text, pool));
|
||||
let set = MediaSetLabel::with_data(&pool, [0u8; 16].into(), 0, label.ctime, None);
|
||||
|
||||
drive.write_media_set_label(&set)?;
|
||||
drive.write_media_set_label(&set, None)?;
|
||||
media_set_label = Some(set);
|
||||
} else {
|
||||
worker.log(format!("Label media '{}' (no pool assignment)", label.label_text));
|
||||
@ -427,7 +428,7 @@ fn write_media_label(
|
||||
drive.rewind()?;
|
||||
|
||||
match drive.read_label() {
|
||||
Ok(Some(info)) => {
|
||||
Ok((Some(info), _)) => {
|
||||
if info.label.uuid != media_id.label.uuid {
|
||||
bail!("verify label failed - got wrong label uuid");
|
||||
}
|
||||
@ -447,7 +448,7 @@ fn write_media_label(
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(None) => bail!("verify label failed (got empty media)"),
|
||||
Ok((None, _)) => bail!("verify label failed (got empty media)"),
|
||||
Err(err) => bail!("verify label failed - {}", err),
|
||||
};
|
||||
|
||||
@ -457,6 +458,46 @@ fn write_media_label(
|
||||
}
|
||||
|
||||
#[api(
|
||||
protected: true,
|
||||
input: {
|
||||
properties: {
|
||||
drive: {
|
||||
schema: DRIVE_NAME_SCHEMA,
|
||||
},
|
||||
password: {
|
||||
description: "Encryption key password.",
|
||||
},
|
||||
},
|
||||
},
|
||||
)]
|
||||
/// Try to restore a tape encryption key
|
||||
pub async fn restore_key(
|
||||
drive: String,
|
||||
password: String,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let (config, _digest) = config::drive::config()?;
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut drive = open_drive(&config, &drive)?;
|
||||
|
||||
let (_media_id, key_config) = drive.read_label()?;
|
||||
|
||||
if let Some(key_config) = key_config {
|
||||
let hint = String::from("fixme: add hint");
|
||||
// fixme: howto show restore hint
|
||||
let password_fn = || { Ok(password.as_bytes().to_vec()) };
|
||||
let (key, ..) = decrypt_key_config(&key_config, &password_fn)?;
|
||||
config::tape_encryption_keys::insert_key(key, key_config, hint)?;
|
||||
} else {
|
||||
bail!("media does not contain any encryption key configuration");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}).await?
|
||||
}
|
||||
|
||||
#[api(
|
||||
input: {
|
||||
properties: {
|
||||
drive: {
|
||||
@ -472,7 +513,7 @@ fn write_media_label(
|
||||
type: MediaIdFlat,
|
||||
},
|
||||
)]
|
||||
/// Read media label
|
||||
/// Read media label (optionally inventorize media)
|
||||
pub async fn read_label(
|
||||
drive: String,
|
||||
inventorize: Option<bool>,
|
||||
@ -483,7 +524,7 @@ pub async fn read_label(
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut drive = open_drive(&config, &drive)?;
|
||||
|
||||
let media_id = drive.read_label()?;
|
||||
let (media_id, _key_config) = drive.read_label()?;
|
||||
|
||||
let media_id = match media_id {
|
||||
Some(media_id) => {
|
||||
@ -723,10 +764,10 @@ pub fn update_inventory(
|
||||
Err(err) => {
|
||||
worker.warn(format!("unable to read label form media '{}' - {}", label_text, err));
|
||||
}
|
||||
Ok(None) => {
|
||||
Ok((None, _)) => {
|
||||
worker.log(format!("media '{}' is empty", label_text));
|
||||
}
|
||||
Ok(Some(media_id)) => {
|
||||
Ok((Some(media_id), _key_config)) => {
|
||||
if label_text != media_id.label.label_text {
|
||||
worker.warn(format!("label text missmatch ({} != {})", label_text, media_id.label.label_text));
|
||||
continue;
|
||||
@ -970,14 +1011,20 @@ pub fn catalog_media(
|
||||
drive.rewind()?;
|
||||
|
||||
let media_id = match drive.read_label()? {
|
||||
Some(media_id) => {
|
||||
(Some(media_id), key_config) => {
|
||||
worker.log(format!(
|
||||
"found media label: {}",
|
||||
serde_json::to_string_pretty(&serde_json::to_value(&media_id)?)?
|
||||
));
|
||||
if key_config.is_some() {
|
||||
worker.log(format!(
|
||||
"encryption key config: {}",
|
||||
serde_json::to_string_pretty(&serde_json::to_value(&key_config)?)?
|
||||
));
|
||||
}
|
||||
media_id
|
||||
},
|
||||
None => bail!("media is empty (no media label found)"),
|
||||
(None, _) => bail!("media is empty (no media label found)"),
|
||||
};
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
|
Reference in New Issue
Block a user