tape: generate random encryptions keys and store key_config on media

This commit is contained in:
Dietmar Maurer 2021-01-19 06:19:18 +01:00
parent 8ca37d6a65
commit feb1645f37
10 changed files with 402 additions and 87 deletions

View File

@ -15,9 +15,12 @@ use crate::{
config::{ config::{
tape_encryption_keys::{ tape_encryption_keys::{
TAPE_KEYS_LOCKFILE, TAPE_KEYS_LOCKFILE,
EncryptionKeyInfo, generate_tape_encryption_key,
load_keys, load_keys,
load_key_configs,
save_keys, save_keys,
save_key_configs,
insert_key,
}, },
}, },
api2::types::{ api2::types::{
@ -25,12 +28,13 @@ use crate::{
PROXMOX_CONFIG_DIGEST_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA,
TapeKeyMetadata, TapeKeyMetadata,
}, },
backup::Fingerprint, backup::{
Fingerprint,
},
tools::format::as_fingerprint, tools::format::as_fingerprint,
}; };
#[api( #[api(
protected: true,
input: { input: {
properties: {}, properties: {},
}, },
@ -47,17 +51,17 @@ pub fn list_keys(
mut rpcenv: &mut dyn RpcEnvironment, mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<TapeKeyMetadata>, Error> { ) -> Result<Vec<TapeKeyMetadata>, Error> {
let (key_map, digest) = load_keys()?; let (key_map, digest) = load_key_configs()?;
let mut list = Vec::new(); let mut list = Vec::new();
for (_fingerprint, item) in key_map { for (fingerprint, item) in key_map {
list.push(TapeKeyMetadata { list.push(TapeKeyMetadata {
hint: item.hint, hint: item.hint,
fingerprint: as_fingerprint(item.fingerprint.bytes()), fingerprint: as_fingerprint(fingerprint.bytes()),
}); });
} }
rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
Ok(list) Ok(list)
@ -71,11 +75,15 @@ pub fn list_keys(
min_length: 5, min_length: 5,
}, },
hint: { hint: {
description: "Password restore hint", description: "Password restore hint.",
min_length: 1, min_length: 1,
max_length: 32,
}, },
}, },
}, },
returns: {
schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
},
)] )]
/// Create a new encryption key /// Create a new encryption key
pub fn create_key( pub fn create_key(
@ -84,26 +92,11 @@ pub fn create_key(
_rpcenv: &mut dyn RpcEnvironment _rpcenv: &mut dyn RpcEnvironment
) -> Result<Fingerprint, Error> { ) -> 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( insert_key(key, key_config, hint)?;
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)?;
Ok(fingerprint) Ok(fingerprint)
} }
@ -131,25 +124,28 @@ pub fn delete_key(
digest: Option<String>, digest: Option<String>,
_rpcenv: &mut dyn RpcEnvironment, _rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> { ) -> Result<(), Error> {
let _lock = open_file_locked( let _lock = open_file_locked(
TAPE_KEYS_LOCKFILE, TAPE_KEYS_LOCKFILE,
std::time::Duration::new(10, 0), std::time::Duration::new(10, 0),
true, 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 { if let Some(ref digest) = digest {
let digest = proxmox::tools::hex_to_digest(digest)?; let digest = proxmox::tools::hex_to_digest(digest)?;
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
} }
match key_map.get(&fingerprint) { match config_map.get(&fingerprint) {
Some(_) => { key_map.remove(&fingerprint); }, Some(_) => { config_map.remove(&fingerprint); },
None => bail!("tape encryption key '{}' does not exist.", fingerprint), None => bail!("tape encryption key '{}' does not exist.", fingerprint),
} }
save_key_configs(config_map)?;
key_map.remove(&fingerprint);
save_keys(key_map)?; save_keys(key_map)?;
Ok(()) Ok(())

View File

@ -24,6 +24,7 @@ use crate::{
self, self,
drive::check_drive_exists, drive::check_drive_exists,
}, },
backup::decrypt_key_config,
api2::{ api2::{
types::{ types::{
UPID_SCHEMA, UPID_SCHEMA,
@ -408,7 +409,7 @@ fn write_media_label(
worker.log(format!("Label media '{}' for pool '{}'", label.label_text, pool)); worker.log(format!("Label media '{}' for pool '{}'", label.label_text, pool));
let set = MediaSetLabel::with_data(&pool, [0u8; 16].into(), 0, label.ctime, None); 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); media_set_label = Some(set);
} else { } else {
worker.log(format!("Label media '{}' (no pool assignment)", label.label_text)); worker.log(format!("Label media '{}' (no pool assignment)", label.label_text));
@ -427,7 +428,7 @@ fn write_media_label(
drive.rewind()?; drive.rewind()?;
match drive.read_label() { match drive.read_label() {
Ok(Some(info)) => { Ok((Some(info), _)) => {
if info.label.uuid != media_id.label.uuid { if info.label.uuid != media_id.label.uuid {
bail!("verify label failed - got wrong 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), Err(err) => bail!("verify label failed - {}", err),
}; };
@ -457,6 +458,46 @@ fn write_media_label(
} }
#[api( #[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: { input: {
properties: { properties: {
drive: { drive: {
@ -472,7 +513,7 @@ fn write_media_label(
type: MediaIdFlat, type: MediaIdFlat,
}, },
)] )]
/// Read media label /// Read media label (optionally inventorize media)
pub async fn read_label( pub async fn read_label(
drive: String, drive: String,
inventorize: Option<bool>, inventorize: Option<bool>,
@ -483,7 +524,7 @@ pub async fn read_label(
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let mut drive = open_drive(&config, &drive)?; 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 { let media_id = match media_id {
Some(media_id) => { Some(media_id) => {
@ -723,10 +764,10 @@ pub fn update_inventory(
Err(err) => { Err(err) => {
worker.warn(format!("unable to read label form media '{}' - {}", label_text, 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)); 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 { if label_text != media_id.label.label_text {
worker.warn(format!("label text missmatch ({} != {})", label_text, media_id.label.label_text)); worker.warn(format!("label text missmatch ({} != {})", label_text, media_id.label.label_text));
continue; continue;
@ -970,14 +1011,20 @@ pub fn catalog_media(
drive.rewind()?; drive.rewind()?;
let media_id = match drive.read_label()? { let media_id = match drive.read_label()? {
Some(media_id) => { (Some(media_id), key_config) => {
worker.log(format!( worker.log(format!(
"found media label: {}", "found media label: {}",
serde_json::to_string_pretty(&serde_json::to_value(&media_id)?)? 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 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); let status_path = Path::new(TAPE_STATUS_DIR);

View File

@ -22,10 +22,13 @@ use crate::tools::format::{as_fingerprint, bytes_as_fingerprint};
use proxmox::api::api; use proxmox::api::api;
// openssl::sha::sha256(b"Proxmox Backup Encryption Key Fingerprint") // openssl::sha::sha256(b"Proxmox Backup Encryption Key Fingerprint")
const FINGERPRINT_INPUT: [u8; 32] = [ 110, 208, 239, 119, 71, 31, 255, 77, /// This constant is used to compute fingerprints.
85, 199, 168, 254, 74, 157, 182, 33, const FINGERPRINT_INPUT: [u8; 32] = [
97, 64, 127, 19, 76, 114, 93, 223, 110, 208, 239, 119, 71, 31, 255, 77,
48, 153, 45, 37, 236, 69, 237, 38, ]; 85, 199, 168, 254, 74, 157, 182, 33,
97, 64, 127, 19, 76, 114, 93, 223,
48, 153, 45, 37, 236, 69, 237, 38,
];
#[api(default: "encrypt")] #[api(default: "encrypt")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]

View File

@ -30,7 +30,7 @@ impl Default for Kdf {
} }
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
pub enum KeyDerivationConfig { pub enum KeyDerivationConfig {
Scrypt { Scrypt {
n: u64, n: u64,
@ -82,7 +82,7 @@ impl KeyDerivationConfig {
} }
} }
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
pub struct KeyConfig { pub struct KeyConfig {
pub kdf: Option<KeyDerivationConfig>, pub kdf: Option<KeyDerivationConfig>,
#[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")] #[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]

View File

@ -1,4 +1,4 @@
use anyhow::Error; use anyhow::{bail, Error};
use serde_json::Value; use serde_json::Value;
use proxmox::{ use proxmox::{
@ -8,11 +8,16 @@ use proxmox::{
RpcEnvironment, RpcEnvironment,
ApiHandler, ApiHandler,
}, },
sys::linux::tty,
}; };
use proxmox_backup::{ use proxmox_backup::{
config,
api2::{ api2::{
self, self,
types::{
DRIVE_NAME_SCHEMA,
},
}, },
config::tape_encryption_keys::complete_key_fingerprint, config::tape_encryption_keys::complete_key_fingerprint,
}; };
@ -23,7 +28,11 @@ pub fn encryption_key_commands() -> CommandLineInterface {
.insert("list", CliCommand::new(&API_METHOD_LIST_KEYS)) .insert("list", CliCommand::new(&API_METHOD_LIST_KEYS))
.insert( .insert(
"create", "create",
CliCommand::new(&api2::config::tape_encryption_keys::API_METHOD_CREATE_KEY) CliCommand::new(&API_METHOD_CREATE_KEY)
)
.insert(
"restore",
CliCommand::new(&API_METHOD_RESTORE_KEY)
) )
.insert( .insert(
"remove", "remove",
@ -36,6 +45,79 @@ pub fn encryption_key_commands() -> CommandLineInterface {
cmd_def.into() cmd_def.into()
} }
#[api(
input: {
properties: {
drive: {
schema: DRIVE_NAME_SCHEMA,
optional: true,
},
},
},
)]
/// Restore encryption key from tape (read password from stdin)
async fn restore_key(
mut param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let (config, _digest) = config::drive::config()?;
param["drive"] = crate::lookup_drive_name(&param, &config)?.into();
if !tty::stdin_isatty() {
bail!("no password input mechanism available");
}
let password = tty::read_password("Tepe Encryption Key Password: ")?;
param["password"] = String::from_utf8(password)?.into();
let info = &api2::tape::drive::API_METHOD_RESTORE_KEY;
match info.handler {
ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
_ => unreachable!(),
};
Ok(())
}
#[api(
input: {
properties: {
hint: {
description: "Password restore hint.",
type: String,
min_length: 1,
max_length: 32,
},
},
},
)]
/// Create key (read password from stdin)
fn create_key(
mut param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
if !tty::stdin_isatty() {
bail!("no password input mechanism available");
}
let password = tty::read_and_verify_password("Tape Encryption Key Password: ")?;
param["password"] = String::from_utf8(password)?.into();
let info = &api2::config::tape_encryption_keys::API_METHOD_CREATE_KEY;
let fingerprint = match info.handler {
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
_ => unreachable!(),
};
println!("{}", fingerprint);
Ok(())
}
#[api( #[api(
input: { input: {
properties: { properties: {

View File

@ -1,18 +1,22 @@
use std::collections::HashMap; use std::collections::{HashSet, HashMap};
use anyhow::{bail, Error}; use anyhow::{bail, Error};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use openssl::sha::sha256;
use proxmox::tools::fs::{ use proxmox::tools::fs::{
file_read_optional_string, file_read_optional_string,
replace_file, replace_file,
open_file_locked,
CreateOptions, CreateOptions,
}; };
use crate::{ use crate::{
backup::{ backup::{
Fingerprint, Fingerprint,
Kdf,
KeyConfig,
CryptConfig,
encrypt_key_with_passphrase,
}, },
}; };
@ -41,29 +45,53 @@ mod hex_key {
} }
} }
/// Store Hardware Encryption keys /// Store Hardware Encryption keys (private part)
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct EncryptionKeyInfo { pub struct EncryptionKeyInfo {
pub hint: String, pub fingerprint: Fingerprint,
#[serde(with = "hex_key")] #[serde(with = "hex_key")]
pub key: [u8; 32], pub key: [u8; 32],
pub fingerprint: Fingerprint, }
/// Store Hardware Encryption keys (public part)
#[derive(Deserialize, Serialize)]
pub struct EncryptionKeyConfig {
pub hint: String,
pub key_config: KeyConfig,
}
pub fn compute_tape_key_fingerprint(key: &[u8; 32]) -> Result<Fingerprint, Error> {
let crypt_config = CryptConfig::new(key.clone())?;
Ok(crypt_config.fingerprint())
}
pub fn generate_tape_encryption_key(password: &[u8]) -> Result<([u8; 32], KeyConfig), Error> {
let mut key = [0u8; 32];
proxmox::sys::linux::fill_with_random_data(&mut key)?;
let mut key_config = encrypt_key_with_passphrase(&key, password, Kdf::Scrypt)?;
key_config.fingerprint = Some(compute_tape_key_fingerprint(&key)?);
Ok((key, key_config))
} }
impl EncryptionKeyInfo { impl EncryptionKeyInfo {
pub fn new(key: [u8; 32], fingerprint: Fingerprint) -> Self {
Self { fingerprint, key }
}
}
pub fn new(key: &[u8; 32], hint: String) -> Self { impl EncryptionKeyConfig {
Self { pub fn new(key_config: KeyConfig, hint: String) -> Self {
hint, Self { hint, key_config }
key: key.clone(),
fingerprint: Fingerprint::new(sha256(key)),
}
} }
} }
pub const TAPE_KEYS_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-keys.json"; pub const TAPE_KEYS_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-keys.json";
pub const TAPE_KEY_CONFIG_FILENAME: &str = "/etc/proxmox-backup/tape-encryption-key-config.json";
pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck"; pub const TAPE_KEYS_LOCKFILE: &str = "/etc/proxmox-backup/.tape-encryption-keys.lck";
/// Load tape encryption keys (private part)
pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8;32]), Error> { pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8;32]), Error> {
let content = file_read_optional_string(TAPE_KEYS_FILENAME)?; let content = file_read_optional_string(TAPE_KEYS_FILENAME)?;
@ -71,12 +99,12 @@ pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8;32])
let digest = openssl::sha::sha256(content.as_bytes()); let digest = openssl::sha::sha256(content.as_bytes());
let list: Vec<EncryptionKeyInfo> = serde_json::from_str(&content)?; let key_list: Vec<EncryptionKeyInfo> = serde_json::from_str(&content)?;
let mut map = HashMap::new(); let mut map = HashMap::new();
for item in list { for item in key_list {
let expected_fingerprint = Fingerprint::new(sha256(&item.key)); let expected_fingerprint = compute_tape_key_fingerprint(&item.key)?;
if item.fingerprint != expected_fingerprint { if item.fingerprint != expected_fingerprint {
bail!( bail!(
"inconsistent fingerprint ({} != {})", "inconsistent fingerprint ({} != {})",
@ -84,10 +112,42 @@ pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8;32])
expected_fingerprint, expected_fingerprint,
); );
} }
map.insert(item.fingerprint.clone(), item); if map.insert(item.fingerprint.clone(), item).is_some() {
bail!("found duplicate fingerprint");
}
} }
Ok((map, digest))
}
/// Load tape encryption key configurations (public part)
pub fn load_key_configs() -> Result<(HashMap<Fingerprint, EncryptionKeyConfig>, [u8;32]), Error> {
let content = file_read_optional_string(TAPE_KEY_CONFIG_FILENAME)?;
let content = content.unwrap_or_else(|| String::from("[]"));
let digest = openssl::sha::sha256(content.as_bytes());
let key_list: Vec<EncryptionKeyConfig> = serde_json::from_str(&content)?;
let mut map = HashMap::new();
let mut hint_set = HashSet::new();
for item in key_list {
match item.key_config.fingerprint {
Some(ref fingerprint) => {
if !hint_set.insert(item.hint.clone()) {
bail!("found duplicate password hint '{}'", item.hint);
}
if map.insert(fingerprint.clone(), item).is_some() {
bail!("found duplicate fingerprint");
}
}
None => bail!("missing fingerprint"),
}
}
Ok((map, digest)) Ok((map, digest))
} }
@ -100,7 +160,7 @@ pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Err
} }
let raw = serde_json::to_string_pretty(&list)?; let raw = serde_json::to_string_pretty(&list)?;
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600); let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
// set the correct owner/group/permissions while saving file // set the correct owner/group/permissions while saving file
// owner(rw) = root, group(r)= root // owner(rw) = root, group(r)= root
@ -110,17 +170,77 @@ pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Err
.group(nix::unistd::Gid::from_raw(0)); .group(nix::unistd::Gid::from_raw(0));
replace_file(TAPE_KEYS_FILENAME, raw.as_bytes(), options)?; replace_file(TAPE_KEYS_FILENAME, raw.as_bytes(), options)?;
Ok(()) Ok(())
} }
pub fn save_key_configs(map: HashMap<Fingerprint, EncryptionKeyConfig>) -> Result<(), Error> {
let mut list = Vec::new();
let mut hint_set = HashSet::new();
for (_fp, item) in map {
if !hint_set.insert(item.hint.clone()) {
bail!("found duplicate password hint '{}'", item.hint);
}
list.push(item);
}
let raw = serde_json::to_string_pretty(&list)?;
let backup_user = crate::backup::backup_user()?;
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0600);
// set the correct owner/group/permissions while saving file
// owner(rw) = root, group(r)= backup
let options = CreateOptions::new()
.perm(mode)
.owner(nix::unistd::ROOT)
.group(backup_user.gid);
replace_file(TAPE_KEY_CONFIG_FILENAME, raw.as_bytes(), options)?;
Ok(())
}
pub fn insert_key(key: [u8;32], key_config: KeyConfig, hint: String) -> Result<(), Error> {
let _lock = open_file_locked(
TAPE_KEYS_LOCKFILE,
std::time::Duration::new(10, 0),
true,
)?;
let (mut key_map, _) = load_keys()?;
let (mut config_map, _) = load_key_configs()?;
let fingerprint = match key_config.fingerprint.clone() {
Some(fingerprint) => fingerprint,
None => bail!("missing encryption key fingerprint - internal error"),
};
if let Some(_) = config_map.get(&fingerprint) {
bail!("encryption key '{}' already exists.", fingerprint);
}
let item = EncryptionKeyInfo::new(key, fingerprint.clone());
key_map.insert(fingerprint.clone(), item);
save_keys(key_map)?;
let item = EncryptionKeyConfig::new(key_config, hint);
config_map.insert(fingerprint.clone(), item);
save_key_configs(config_map)?;
Ok(())
}
// shell completion helper // shell completion helper
pub fn complete_key_fingerprint(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> { pub fn complete_key_fingerprint(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
let data = match load_keys() { let data = match load_key_configs() {
Ok((data, _digest)) => data, Ok((data, _digest)) => data,
Err(_) => return Vec::new(), Err(_) => return Vec::new(),
}; };
data.keys().map(|fp| crate::tools::format::as_fingerprint(fp.bytes())).collect() data.keys().map(|fp| crate::tools::format::as_fingerprint(fp.bytes())).collect()
} }

View File

@ -10,7 +10,10 @@ use proxmox::sys::error::SysResult;
use crate::{ use crate::{
config, config,
backup::Fingerprint, backup::{
Fingerprint,
KeyConfig,
},
tools::run_command, tools::run_command,
api2::types::{ api2::types::{
TapeDensity, TapeDensity,
@ -448,7 +451,11 @@ impl TapeDriver for LinuxTapeHandle {
Ok(Box::new(handle)) Ok(Box::new(handle))
} }
fn write_media_set_label(&mut self, media_set_label: &MediaSetLabel) -> Result<(), Error> { fn write_media_set_label(
&mut self,
media_set_label: &MediaSetLabel,
key_config: Option<&KeyConfig>,
) -> Result<(), Error> {
let file_number = self.current_file_number()?; let file_number = self.current_file_number()?;
if file_number != 1 { if file_number != 1 {
@ -466,7 +473,20 @@ impl TapeDriver for LinuxTapeHandle {
let mut handle = TapeWriterHandle { let mut handle = TapeWriterHandle {
writer: BlockedWriter::new(&mut self.file), writer: BlockedWriter::new(&mut self.file),
}; };
let raw = serde_json::to_string_pretty(&serde_json::to_value(media_set_label)?)?;
let mut value = serde_json::to_value(media_set_label)?;
if media_set_label.encryption_key_fingerprint.is_some() {
match key_config {
Some(key_config) => {
value["key-config"] = serde_json::to_value(key_config)?;
}
None => {
bail!("missing encryption key config");
}
}
}
let raw = serde_json::to_string_pretty(&value)?;
let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32); let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32);
handle.write_header(&header, raw.as_bytes())?; handle.write_header(&header, raw.as_bytes())?;

View File

@ -20,12 +20,16 @@ pub use linux_list_drives::*;
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use ::serde::{Deserialize}; use ::serde::{Deserialize};
use serde_json::Value;
use proxmox::tools::io::ReadExt; use proxmox::tools::io::ReadExt;
use proxmox::api::section_config::SectionConfigData; use proxmox::api::section_config::SectionConfigData;
use crate::{ use crate::{
backup::Fingerprint, backup::{
Fingerprint,
KeyConfig,
},
api2::types::{ api2::types::{
VirtualTapeDrive, VirtualTapeDrive,
LinuxTapeDrive, LinuxTapeDrive,
@ -101,18 +105,26 @@ pub trait TapeDriver {
} }
/// Write the media set label to tape /// Write the media set label to tape
fn write_media_set_label(&mut self, media_set_label: &MediaSetLabel) -> Result<(), Error>; ///
/// If the media-set is encrypted, we also store the encryption
/// key_config, so that it is possible to restore the key.
fn write_media_set_label(
&mut self,
media_set_label: &MediaSetLabel,
key_config: Option<&KeyConfig>,
) -> Result<(), Error>;
/// Read the media label /// Read the media label
/// ///
/// This tries to read both media labels (label and media_set_label). /// This tries to read both media labels (label and
fn read_label(&mut self) -> Result<Option<MediaId>, Error> { /// media_set_label). Also returns the optional encryption key configuration.
fn read_label(&mut self) -> Result<(Option<MediaId>, Option<KeyConfig>), Error> {
self.rewind()?; self.rewind()?;
let label = { let label = {
let mut reader = match self.read_next_file()? { let mut reader = match self.read_next_file()? {
None => return Ok(None), // tape is empty None => return Ok((None, None)), // tape is empty
Some(reader) => reader, Some(reader) => reader,
}; };
@ -135,7 +147,7 @@ pub trait TapeDriver {
// try to read MediaSet label // try to read MediaSet label
let mut reader = match self.read_next_file()? { let mut reader = match self.read_next_file()? {
None => return Ok(Some(media_id)), None => return Ok((Some(media_id), None)),
Some(reader) => reader, Some(reader) => reader,
}; };
@ -143,7 +155,17 @@ pub trait TapeDriver {
header.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, 1, 64*1024)?; header.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, 1, 64*1024)?;
let data = reader.read_exact_allocated(header.size as usize)?; let data = reader.read_exact_allocated(header.size as usize)?;
let media_set_label: MediaSetLabel = serde_json::from_slice(&data) let mut data: Value = serde_json::from_slice(&data)
.map_err(|err| format_err!("unable to parse media set label - {}", err))?;
let key_config_value = data["key-config"].take();
let key_config: Option<KeyConfig> = if !key_config_value.is_null() {
Some(serde_json::from_value(key_config_value)?)
} else {
None
};
let media_set_label: MediaSetLabel = serde_json::from_value(data)
.map_err(|err| format_err!("unable to parse media set label - {}", err))?; .map_err(|err| format_err!("unable to parse media set label - {}", err))?;
// make sure we read the EOF marker // make sure we read the EOF marker
@ -153,7 +175,7 @@ pub trait TapeDriver {
media_id.media_set_label = Some(media_set_label); media_id.media_set_label = Some(media_set_label);
Ok(Some(media_id)) Ok((Some(media_id), key_config))
} }
/// Eject media /// Eject media
@ -278,7 +300,7 @@ pub fn request_and_load_media(
), Error> { ), Error> {
let check_label = |handle: &mut dyn TapeDriver, uuid: &proxmox::tools::Uuid| { let check_label = |handle: &mut dyn TapeDriver, uuid: &proxmox::tools::Uuid| {
if let Ok(Some(media_id)) = handle.read_label() { if let Ok((Some(media_id), _)) = handle.read_label() {
worker.log(format!( worker.log(format!(
"found media label {} ({})", "found media label {} ({})",
media_id.label.label_text, media_id.label.label_text,
@ -348,7 +370,7 @@ pub fn request_and_load_media(
}; };
match handle.read_label() { match handle.read_label() {
Ok(Some(media_id)) => { Ok((Some(media_id), _)) => {
if media_id.label.uuid == label.uuid { if media_id.label.uuid == label.uuid {
worker.log(format!( worker.log(format!(
"found media label {} ({})", "found media label {} ({})",
@ -367,7 +389,7 @@ pub fn request_and_load_media(
} }
} }
} }
Ok(None) => { Ok((None, _)) => {
if last_media_uuid.is_some() { if last_media_uuid.is_some() {
worker.log(format!("found empty media without label (please label all tapes first)")); worker.log(format!("found empty media without label (please label all tapes first)"));
last_media_uuid = None; last_media_uuid = None;

View File

@ -11,6 +11,7 @@ use proxmox::tools::{
}; };
use crate::{ use crate::{
backup::KeyConfig,
tape::{ tape::{
TapeWrite, TapeWrite,
TapeRead, TapeRead,
@ -325,10 +326,18 @@ impl TapeDriver for VirtualTapeHandle {
} }
} }
fn write_media_set_label(&mut self, media_set_label: &MediaSetLabel) -> Result<(), Error> { fn write_media_set_label(
&mut self,
media_set_label: &MediaSetLabel,
key_config: Option<&KeyConfig>,
) -> Result<(), Error> {
self.set_encryption(None)?; self.set_encryption(None)?;
if key_config.is_some() {
bail!("encryption is not implemented - internal error");
}
let mut status = self.load_status()?; let mut status = self.load_status()?;
match status.current_tape { match status.current_tape {
Some(VirtualTapeStatus { ref name, ref mut pos }) => { Some(VirtualTapeStatus { ref name, ref mut pos }) => {

View File

@ -30,6 +30,7 @@ use crate::{
media_changer, media_changer,
file_formats::MediaSetLabel, file_formats::MediaSetLabel,
}, },
config::tape_encryption_keys::load_key_configs,
}; };
@ -452,12 +453,27 @@ fn update_media_set_label(
Some(ref set) => set, Some(ref set) => set,
}; };
let key_config = if let Some(ref fingerprint) = new_set.encryption_key_fingerprint {
let (config_map, _digest) = load_key_configs()?;
match config_map.get(fingerprint) {
Some(item) => {
// fixme: store item.hint??? should be in key-config instead
Some(item.key_config.clone())
}
None => {
bail!("unable to find tape encryption key config '{}'", fingerprint);
}
}
} else {
None
};
let status_path = Path::new(TAPE_STATUS_DIR); let status_path = Path::new(TAPE_STATUS_DIR);
match old_set { match old_set {
None => { None => {
worker.log(format!("wrinting new media set label")); worker.log(format!("wrinting new media set label"));
drive.write_media_set_label(new_set)?; drive.write_media_set_label(new_set, key_config.as_ref())?;
media_catalog = MediaCatalog::overwrite(status_path, media_id, false)?; media_catalog = MediaCatalog::overwrite(status_path, media_id, false)?;
} }
Some(media_set_label) => { Some(media_set_label) => {
@ -476,7 +492,7 @@ fn update_media_set_label(
media_set_label.uuid.to_string(), media_set_label.seq_nr) media_set_label.uuid.to_string(), media_set_label.seq_nr)
); );
drive.write_media_set_label(new_set)?; drive.write_media_set_label(new_set, key_config.as_ref())?;
media_catalog = MediaCatalog::overwrite(status_path, media_id, false)?; media_catalog = MediaCatalog::overwrite(status_path, media_id, false)?;
} }
} }