product-config: add rust API type for configuration digest

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
This commit is contained in:
Dietmar Maurer 2024-05-02 12:15:59 +02:00
parent 9fc48d96d2
commit e64575b6a7
3 changed files with 128 additions and 3 deletions

View 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" ] }

View File

@ -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(())
}

View 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.