product-config: remove digest implementation (move to proxmox-config-digest crate)

And use the new proxmox-config-digest crate instead.

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
This commit is contained in:
Dietmar Maurer 2024-05-29 18:40:11 +02:00
parent 34b21106dd
commit 4768ad2200
11 changed files with 30 additions and 165 deletions

View File

@ -12,19 +12,15 @@ description = "ACME API implementation"
anyhow.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
base64 = { workspace = true, optional = true }
hex = { workspace = true, optional = true }
tokio = { workspace = true, optional = true, features = ["fs"] }
hyper = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
http = { workspace = true, optional = true }
log = { workspace = true, optional = true }
nix = { workspace = true, optional = true }
openssl = { workspace = true, optional = true }
lazy_static = { workspace = true, optional = true }
proxmox-serde = { workspace = true, optional = true }
proxmox-section-config = { workspace = true, optional = true }
proxmox-rest-server = { workspace = true, optional = true }
@ -32,6 +28,7 @@ proxmox-router = { workspace = true, optional = true }
proxmox-sys = { workspace = true, optional = true }
proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] }
proxmox-acme = { workspace = true, optional = true, features = ["api-types"] }
proxmox-config-digest = { workspace = true, optional = true }
proxmox-product-config = { workspace = true, optional = true }
[features]
@ -39,13 +36,14 @@ default = ["api-types"]
api-types = ["dep:proxmox-acme", "dep:proxmox-serde"]
impl = [
"api-types",
"dep:proxmox-config-digest",
"proxmox-config-digest?/openssl",
"dep:proxmox-product-config",
"proxmox-product-config?/impl",
"dep:proxmox-acme",
"proxmox-acme?/impl",
"proxmox-acme?/async-client",
"dep:proxmox-section-config",
"dep:openssl",
#"dep:openssl",
"dep:lazy_static",
"dep:log",
"dep:nix",
@ -57,5 +55,4 @@ impl = [
"dep:proxmox-rest-server",
"dep:proxmox-router",
"dep:base64",
"dep:hex",
]

View File

@ -1,12 +1,12 @@
//! ACME plugin configuration API implementation
use anyhow::{bail, format_err, Error};
use hex::FromHex;
use serde::Deserialize;
use serde_json::Value;
use proxmox_schema::param_bail;
use proxmox_config_digest::ConfigDigest;
use crate::types::{
DeletablePluginProperty, DnsPlugin, DnsPluginCore, DnsPluginCoreUpdater, PluginConfig,
@ -17,7 +17,7 @@ use proxmox_router::{http_bail, RpcEnvironment};
pub fn list_plugins(rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<PluginConfig>, Error> {
let (plugins, digest) = super::plugin_config::plugin_config()?;
rpcenv["digest"] = hex::encode(digest).into();
rpcenv["digest"] = digest.to_hex().into();
Ok(plugins
.iter()
.map(|(id, (ty, data))| modify_cfg_for_api(id, ty, data))
@ -29,7 +29,7 @@ pub fn get_plugin(
rpcenv: &mut dyn RpcEnvironment,
) -> Result<PluginConfig, Error> {
let (plugins, digest) = super::plugin_config::plugin_config()?;
rpcenv["digest"] = hex::encode(digest).into();
rpcenv["digest"] = digest.to_hex().into();
match plugins.get(&id) {
Some((ty, data)) => Ok(modify_cfg_for_api(&id, ty, data)),
@ -69,7 +69,7 @@ pub fn update_plugin(
update: DnsPluginCoreUpdater,
data: Option<String>,
delete: Option<Vec<DeletablePluginProperty>>,
digest: Option<String>,
digest: Option<ConfigDigest>,
) -> Result<(), Error> {
let data = data
.as_deref()
@ -83,12 +83,7 @@ pub fn update_plugin(
let (mut plugins, expected_digest) = super::plugin_config::plugin_config()?;
if let Some(digest) = digest {
let digest = <[u8; 32]>::from_hex(digest)?;
if digest != expected_digest {
bail!("detected modified configuration - file changed by other user? Try again.");
}
}
expected_digest.detect_modification(digest.as_ref())?;
match plugins.get_mut(&id) {
Some((ty, ref mut entry)) => {

View File

@ -6,6 +6,7 @@ use serde_json::Value;
use proxmox_schema::{ApiType, Schema};
use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin};
use proxmox_config_digest::ConfigDigest;
use proxmox_product_config::{ApiLockGuard, open_api_lockfile, replace_config};
use crate::types::{DnsPlugin, StandalonePlugin, PLUGIN_ID_SCHEMA};
@ -60,13 +61,13 @@ pub(crate) fn lock_plugin_config() -> Result<ApiLockGuard, Error> {
open_api_lockfile(plugin_cfg_lockfile, None, true)
}
pub(crate) fn plugin_config() -> Result<(PluginData, [u8; 32]), Error> {
pub(crate) fn plugin_config() -> Result<(PluginData, ConfigDigest), Error> {
let plugin_cfg_filename = super::config::plugin_cfg_filename();
let content =
proxmox_sys::fs::file_read_optional_string(&plugin_cfg_filename)?.unwrap_or_default();
let digest = openssl::sha::sha256(content.as_bytes());
let digest = ConfigDigest::from_slice(content.as_bytes());
let mut data = CONFIG.parse(&plugin_cfg_filename, &content)?;
if data.sections.get("standalone").is_none() {

View File

@ -12,14 +12,6 @@ exclude.workspace = true
[dependencies]
anyhow.workspace = true
hex.workspace = true
log = { workspace = true, optional = true }
nix = { workspace = true, optional = true }
openssl = { workspace = true, optional = true }
serde.workspace = true
serde_plain.workspace = true
proxmox-sys = { workspace = true, optional = true, features = ["timer"] }
proxmox-schema = { workspace = true, features = ["api-types"] }
[features]
default = []
impl = ["dep:log", "dep:nix", "dep:openssl", "dep:proxmox-sys"]
log.workspace = true
nix.workspace = true
proxmox-sys = { workspace = true, features = ["timer"] }

View File

@ -1,112 +0,0 @@
use anyhow::{bail, Error};
#[cfg(feature = "impl")]
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[..])
}
#[cfg(feature = "impl")]
pub fn from_slice<T: AsRef<[u8]>>(data: T) -> ConfigDigest {
let digest = sha::sha256(data.as_ref());
ConfigDigest(digest)
}
/// Detect modified configuration files
///
/// This function fails with a reasonable error message if checksums do not match.
pub fn detect_modification(&self, user_digest: Option<&Self>) -> Result<(), Error> {
if let Some(user_digest) = user_digest {
if user_digest != self {
bail!("detected modified configuration - file changed by other user? Try again.");
}
}
Ok(())
}
}
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);

View File

@ -1,12 +1,5 @@
mod digest;
pub use digest::{ConfigDigest, PROXMOX_CONFIG_DIGEST_FORMAT, PROXMOX_CONFIG_DIGEST_SCHEMA};
#[cfg(feature = "impl")]
mod filesystem_helpers;
#[cfg(feature = "impl")]
pub use filesystem_helpers::*;
#[cfg(feature = "impl")]
mod product_config;
#[cfg(feature = "impl")]
pub use product_config::*;

View File

@ -23,37 +23,37 @@ log = { workspace = true, optional = true }
proxmox-sys = { workspace = true, optional = true }
proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] }
proxmox-time = { workspace = true, optional = true }
proxmox-config-digest = { workspace = true, optional = true }
proxmox-product-config = { workspace = true, optional = true }
[features]
default = []
dns-api-types = ["dep:proxmox-product-config"]
dns-api-types = ["dep:proxmox-config-digest"]
dns-impl = [
"dns-api-types",
"dep:proxmox-product-config",
"proxmox-product-config?/impl",
"dep:proxmox-config-digest",
"proxmox-config-digest?/openssl",
"dep:proxmox-sys",
"dep:proxmox-time",
]
time-api-types = []
time-impl = [
"time-api-types",
"dep:proxmox-config-digest",
"proxmox-config-digest?/openssl",
"dep:proxmox-product-config",
"proxmox-product-config?/impl",
"dep:proxmox-sys",
"dep:proxmox-time",
]
network-api-types = []
network-impl = [
"network-api-types",
"dep:proxmox-config-digest",
"proxmox-config-digest?/openssl",
"dep:proxmox-product-config",
"proxmox-product-config?/impl",
"dep:nix",
"dep:libc",
"dep:proxmox-sys",
]
syslog-api-types = []
syslog-impl = [
"syslog-api-types",
"dep:log",
]
syslog-impl = ["syslog-api-types", "dep:log"]

View File

@ -5,7 +5,7 @@ use proxmox_schema::api_types::IP_FORMAT;
use proxmox_schema::Schema;
use proxmox_schema::StringSchema;
use proxmox_product_config::ConfigDigest;
use proxmox_config_digest::ConfigDigest;
pub const SEARCH_DOMAIN_SCHEMA: Schema =
StringSchema::new("Search domain for host-name lookup.").schema();

View File

@ -4,7 +4,7 @@ use std::sync::Mutex;
use anyhow::Error;
use const_format::concatcp;
use lazy_static::lazy_static;
use proxmox_product_config::ConfigDigest;
use proxmox_config_digest::ConfigDigest;
use regex::Regex;
use proxmox_sys::fs::file_get_contents;

View File

@ -1,6 +1,6 @@
use anyhow::{bail, Error};
use proxmox_product_config::ConfigDigest;
use proxmox_config_digest::ConfigDigest;
use crate::network::{self, parse_vlan_id_from_name, parse_vlan_raw_device_from_name};
use crate::network::{

View File

@ -20,9 +20,8 @@ use helper::compute_file_diff;
use helper::get_network_interfaces;
use parser::NetworkParser;
use proxmox_product_config::{
open_api_lockfile, replace_system_config, ApiLockGuard, ConfigDigest,
};
use proxmox_config_digest::ConfigDigest;
use proxmox_product_config::{open_api_lockfile, replace_system_config, ApiLockGuard};
lazy_static! {
static ref PHYSICAL_NIC_REGEX: Regex = Regex::new(r"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap();