From e64575b6a7f06fde43e42166061208c1bdbef60a Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Thu, 2 May 2024 12:15:59 +0200 Subject: [PATCH] product-config: add rust API type for configuration digest Signed-off-by: Dietmar Maurer --- proxmox-product-config/Cargo.toml | 5 ++ proxmox-product-config/src/digest.rs | 117 +++++++++++++++++++++++++++ proxmox-product-config/src/lib.rs | 9 ++- 3 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 proxmox-product-config/src/digest.rs diff --git a/proxmox-product-config/Cargo.toml b/proxmox-product-config/Cargo.toml index 2b706a00..adeebb5c 100644 --- a/proxmox-product-config/Cargo.toml +++ b/proxmox-product-config/Cargo.toml @@ -11,6 +11,11 @@ exclude.workspace = true [dependencies] anyhow.workspace = true +hex.workspace = true log.workspace = true nix.workspace = true +openssl.workspace = true +serde.workspace = true +serde_plain.workspace = true proxmox-sys = { workspace = true, features = [ "timer" ] } +proxmox-schema = { workspace = true, features = [ "api-types" ] } \ No newline at end of file diff --git a/proxmox-product-config/src/digest.rs b/proxmox-product-config/src/digest.rs new file mode 100644 index 00000000..94abb2f7 --- /dev/null +++ b/proxmox-product-config/src/digest.rs @@ -0,0 +1,117 @@ +use anyhow::{bail, Error}; +use openssl::sha; + +use proxmox_schema::api_types::SHA256_HEX_REGEX; +use proxmox_schema::ApiStringFormat; +use proxmox_schema::ApiType; +use proxmox_schema::Schema; +use proxmox_schema::StringSchema; + +pub const PROXMOX_CONFIG_DIGEST_FORMAT: ApiStringFormat = + ApiStringFormat::Pattern(&SHA256_HEX_REGEX); + +pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new( + "Prevent changes if current configuration file has different \ + SHA256 digest. This can be used to prevent concurrent \ + modifications.", +) +.format(&PROXMOX_CONFIG_DIGEST_FORMAT) +.schema(); + +#[derive(Clone, Debug, Eq, PartialEq)] +/// A configuration digest - a SHA256 hash. +pub struct ConfigDigest([u8; 32]); + +impl ConfigDigest { + pub fn to_hex(&self) -> String { + hex::encode(&self.0[..]) + } + + pub fn from_slice>(data: T) -> ConfigDigest { + let digest = sha::sha256(data.as_ref()); + ConfigDigest(digest) + } +} + +impl ApiType for ConfigDigest { + const API_SCHEMA: Schema = PROXMOX_CONFIG_DIGEST_SCHEMA; +} + +impl From<[u8; 32]> for ConfigDigest { + #[inline] + fn from(digest: [u8; 32]) -> Self { + Self(digest) + } +} + +impl From for [u8; 32] { + #[inline] + fn from(digest: ConfigDigest) -> Self { + digest.0 + } +} + +impl AsRef<[u8]> for ConfigDigest { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsRef<[u8; 32]> for ConfigDigest { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl std::ops::Deref for ConfigDigest { + type Target = [u8; 32]; + + fn deref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl std::ops::DerefMut for ConfigDigest { + fn deref_mut(&mut self) -> &mut [u8; 32] { + &mut self.0 + } +} + +impl std::fmt::Display for ConfigDigest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +impl std::str::FromStr for ConfigDigest { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + let mut digest = [0u8; 32]; + hex::decode_to_slice(s, &mut digest)?; + Ok(ConfigDigest(digest)) + } +} + +serde_plain::derive_deserialize_from_fromstr!(ConfigDigest, "valid configuration digest"); +serde_plain::derive_serialize_from_display!(ConfigDigest); + +/// Detect modified configuration files +/// +/// This function fails with a reasonable error message if checksums do not match. +pub fn detect_modified_configuration_file( + user_digest: Option<&[u8; 32]>, + config_digest: &[u8; 32], +) -> Result<(), Error> { + use hex::FromHex; + + let user_digest = match user_digest { + Some(digest) => <[u8; 32]>::from_hex(digest)?, + None => return Ok(()), + }; + + if user_digest != *config_digest { + bail!("detected modified configuration - file changed by other user? Try again."); + } + Ok(()) +} diff --git a/proxmox-product-config/src/lib.rs b/proxmox-product-config/src/lib.rs index be2347e8..56291791 100644 --- a/proxmox-product-config/src/lib.rs +++ b/proxmox-product-config/src/lib.rs @@ -7,6 +7,12 @@ use nix::fcntl::OFlag; use nix::sys::stat::Mode; use nix::unistd::{Gid, Uid}; +mod digest; +pub use digest::{ + detect_modified_configuration_file, ConfigDigest, PROXMOX_CONFIG_DIGEST_FORMAT, + PROXMOX_CONFIG_DIGEST_SCHEMA, +}; + static mut PRODUCT_CONFIG: Option = None; /// Initialize the global product configuration. @@ -43,8 +49,6 @@ impl ProxmoxProductConfig { path.push(rel_path); path } - - } // Check file/directory permissions @@ -104,7 +108,6 @@ pub fn mkdir_permissions(dir: &str, uid: Uid, gid: Gid, mode: u32) -> Result<(), Ok(()) } - /// Atomically write data to file owned by `root:api-user` with permission `0640` /// /// Only the superuser can write those files, but group 'api-user' can read them. -- 2.39.5