]> git.proxmox.com Git - proxmox.git/commitdiff
product-config: add rust API type for configuration digest
authorDietmar Maurer <dietmar@proxmox.com>
Thu, 2 May 2024 10:15:59 +0000 (12:15 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Thu, 2 May 2024 10:28:11 +0000 (12:28 +0200)
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
proxmox-product-config/Cargo.toml
proxmox-product-config/src/digest.rs [new file with mode: 0644]
proxmox-product-config/src/lib.rs

index 2b706a00d682537babfbdf37b4a216dc41866ce4..adeebb5c10bae36caf5f0e20ff7caa71eb0ad65e 100644 (file)
@@ -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 (file)
index 0000000..94abb2f
--- /dev/null
@@ -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<T: AsRef<[u8]>>(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<ConfigDigest> 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<Self, hex::FromHexError> {
+        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(())
+}
index be2347e801ff3ac1a85426b7504b08d1408172c4..56291791d31cfa870acc2bf317f8112b22154a76 100644 (file)
@@ -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<ProxmoxProductConfig> = 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.