tape: generate random encryptions keys and store key_config on media
This commit is contained in:
parent
8ca37d6a65
commit
feb1645f37
@ -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,14 +51,14 @@ 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()),
|
||||
});
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -138,18 +131,21 @@ pub fn delete_key(
|
||||
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),
|
||||
};
|
||||
|
||||
@ -456,6 +457,46 @@ fn write_media_label(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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: {
|
||||
@ -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);
|
||||
|
@ -22,10 +22,13 @@ use crate::tools::format::{as_fingerprint, bytes_as_fingerprint};
|
||||
use proxmox::api::api;
|
||||
|
||||
// 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.
|
||||
const FINGERPRINT_INPUT: [u8; 32] = [
|
||||
110, 208, 239, 119, 71, 31, 255, 77,
|
||||
85, 199, 168, 254, 74, 157, 182, 33,
|
||||
97, 64, 127, 19, 76, 114, 93, 223,
|
||||
48, 153, 45, 37, 236, 69, 237, 38, ];
|
||||
48, 153, 45, 37, 236, 69, 237, 38,
|
||||
];
|
||||
#[api(default: "encrypt")]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
|
@ -30,7 +30,7 @@ impl Default for Kdf {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub enum KeyDerivationConfig {
|
||||
Scrypt {
|
||||
n: u64,
|
||||
@ -82,7 +82,7 @@ impl KeyDerivationConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct KeyConfig {
|
||||
pub kdf: Option<KeyDerivationConfig>,
|
||||
#[serde(with = "proxmox::tools::serde::epoch_as_rfc3339")]
|
||||
|
@ -1,4 +1,4 @@
|
||||
use anyhow::Error;
|
||||
use anyhow::{bail, Error};
|
||||
use serde_json::Value;
|
||||
|
||||
use proxmox::{
|
||||
@ -8,11 +8,16 @@ use proxmox::{
|
||||
RpcEnvironment,
|
||||
ApiHandler,
|
||||
},
|
||||
sys::linux::tty,
|
||||
};
|
||||
|
||||
use proxmox_backup::{
|
||||
config,
|
||||
api2::{
|
||||
self,
|
||||
types::{
|
||||
DRIVE_NAME_SCHEMA,
|
||||
},
|
||||
},
|
||||
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(
|
||||
"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(
|
||||
"remove",
|
||||
@ -36,6 +45,79 @@ pub fn encryption_key_commands() -> CommandLineInterface {
|
||||
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(¶m, &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(
|
||||
input: {
|
||||
properties: {
|
||||
|
@ -1,18 +1,22 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
use anyhow::{bail, Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use openssl::sha::sha256;
|
||||
|
||||
use proxmox::tools::fs::{
|
||||
file_read_optional_string,
|
||||
replace_file,
|
||||
open_file_locked,
|
||||
CreateOptions,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backup::{
|
||||
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)]
|
||||
pub struct EncryptionKeyInfo {
|
||||
pub hint: String,
|
||||
pub fingerprint: Fingerprint,
|
||||
#[serde(with = "hex_key")]
|
||||
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 {
|
||||
|
||||
pub fn new(key: &[u8; 32], hint: String) -> Self {
|
||||
Self {
|
||||
hint,
|
||||
key: key.clone(),
|
||||
fingerprint: Fingerprint::new(sha256(key)),
|
||||
pub fn new(key: [u8; 32], fingerprint: Fingerprint) -> Self {
|
||||
Self { fingerprint, key }
|
||||
}
|
||||
}
|
||||
|
||||
impl EncryptionKeyConfig {
|
||||
pub fn new(key_config: KeyConfig, hint: String) -> Self {
|
||||
Self { hint, key_config }
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
/// Load tape encryption keys (private part)
|
||||
pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8;32]), Error> {
|
||||
|
||||
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 list: Vec<EncryptionKeyInfo> = serde_json::from_str(&content)?;
|
||||
let key_list: Vec<EncryptionKeyInfo> = serde_json::from_str(&content)?;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for item in list {
|
||||
let expected_fingerprint = Fingerprint::new(sha256(&item.key));
|
||||
for item in key_list {
|
||||
let expected_fingerprint = compute_tape_key_fingerprint(&item.key)?;
|
||||
if item.fingerprint != expected_fingerprint {
|
||||
bail!(
|
||||
"inconsistent fingerprint ({} != {})",
|
||||
@ -85,7 +113,39 @@ pub fn load_keys() -> Result<(HashMap<Fingerprint, EncryptionKeyInfo>, [u8;32])
|
||||
);
|
||||
}
|
||||
|
||||
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))
|
||||
@ -114,13 +174,73 @@ pub fn save_keys(map: HashMap<Fingerprint, EncryptionKeyInfo>) -> Result<(), Err
|
||||
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
|
||||
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,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
|
||||
data.keys().map(|fp| crate::tools::format::as_fingerprint(fp.bytes())).collect()
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,10 @@ use proxmox::sys::error::SysResult;
|
||||
|
||||
use crate::{
|
||||
config,
|
||||
backup::Fingerprint,
|
||||
backup::{
|
||||
Fingerprint,
|
||||
KeyConfig,
|
||||
},
|
||||
tools::run_command,
|
||||
api2::types::{
|
||||
TapeDensity,
|
||||
@ -448,7 +451,11 @@ impl TapeDriver for LinuxTapeHandle {
|
||||
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()?;
|
||||
if file_number != 1 {
|
||||
@ -466,7 +473,20 @@ impl TapeDriver for LinuxTapeHandle {
|
||||
let mut handle = TapeWriterHandle {
|
||||
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);
|
||||
handle.write_header(&header, raw.as_bytes())?;
|
||||
|
@ -20,12 +20,16 @@ pub use linux_list_drives::*;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use ::serde::{Deserialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use proxmox::tools::io::ReadExt;
|
||||
use proxmox::api::section_config::SectionConfigData;
|
||||
|
||||
use crate::{
|
||||
backup::Fingerprint,
|
||||
backup::{
|
||||
Fingerprint,
|
||||
KeyConfig,
|
||||
},
|
||||
api2::types::{
|
||||
VirtualTapeDrive,
|
||||
LinuxTapeDrive,
|
||||
@ -101,18 +105,26 @@ pub trait TapeDriver {
|
||||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// This tries to read both media labels (label and media_set_label).
|
||||
fn read_label(&mut self) -> Result<Option<MediaId>, Error> {
|
||||
/// This tries to read both media labels (label and
|
||||
/// media_set_label). Also returns the optional encryption key configuration.
|
||||
fn read_label(&mut self) -> Result<(Option<MediaId>, Option<KeyConfig>), Error> {
|
||||
|
||||
self.rewind()?;
|
||||
|
||||
let label = {
|
||||
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,
|
||||
};
|
||||
|
||||
@ -135,7 +147,7 @@ pub trait TapeDriver {
|
||||
|
||||
// try to read MediaSet label
|
||||
let mut reader = match self.read_next_file()? {
|
||||
None => return Ok(Some(media_id)),
|
||||
None => return Ok((Some(media_id), None)),
|
||||
Some(reader) => reader,
|
||||
};
|
||||
|
||||
@ -143,7 +155,17 @@ pub trait TapeDriver {
|
||||
header.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, 1, 64*1024)?;
|
||||
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))?;
|
||||
|
||||
// make sure we read the EOF marker
|
||||
@ -153,7 +175,7 @@ pub trait TapeDriver {
|
||||
|
||||
media_id.media_set_label = Some(media_set_label);
|
||||
|
||||
Ok(Some(media_id))
|
||||
Ok((Some(media_id), key_config))
|
||||
}
|
||||
|
||||
/// Eject media
|
||||
@ -278,7 +300,7 @@ pub fn request_and_load_media(
|
||||
), Error> {
|
||||
|
||||
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!(
|
||||
"found media label {} ({})",
|
||||
media_id.label.label_text,
|
||||
@ -348,7 +370,7 @@ pub fn request_and_load_media(
|
||||
};
|
||||
|
||||
match handle.read_label() {
|
||||
Ok(Some(media_id)) => {
|
||||
Ok((Some(media_id), _)) => {
|
||||
if media_id.label.uuid == label.uuid {
|
||||
worker.log(format!(
|
||||
"found media label {} ({})",
|
||||
@ -367,7 +389,7 @@ pub fn request_and_load_media(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
Ok((None, _)) => {
|
||||
if last_media_uuid.is_some() {
|
||||
worker.log(format!("found empty media without label (please label all tapes first)"));
|
||||
last_media_uuid = None;
|
||||
|
@ -11,6 +11,7 @@ use proxmox::tools::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backup::KeyConfig,
|
||||
tape::{
|
||||
TapeWrite,
|
||||
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)?;
|
||||
|
||||
if key_config.is_some() {
|
||||
bail!("encryption is not implemented - internal error");
|
||||
}
|
||||
|
||||
let mut status = self.load_status()?;
|
||||
match status.current_tape {
|
||||
Some(VirtualTapeStatus { ref name, ref mut pos }) => {
|
||||
|
@ -30,6 +30,7 @@ use crate::{
|
||||
media_changer,
|
||||
file_formats::MediaSetLabel,
|
||||
},
|
||||
config::tape_encryption_keys::load_key_configs,
|
||||
};
|
||||
|
||||
|
||||
@ -452,12 +453,27 @@ fn update_media_set_label(
|
||||
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);
|
||||
|
||||
match old_set {
|
||||
None => {
|
||||
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)?;
|
||||
}
|
||||
Some(media_set_label) => {
|
||||
@ -476,7 +492,7 @@ fn update_media_set_label(
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user