From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 7098C78607 for ; Fri, 30 Apr 2021 08:27:05 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5F726246A9 for ; Fri, 30 Apr 2021 08:26:35 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 8F6792469B for ; Fri, 30 Apr 2021 08:26:34 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 620F3427DF for ; Fri, 30 Apr 2021 08:26:34 +0200 (CEST) To: Proxmox Backup Server development discussion , Wolfgang Bumiller References: <20210429131322.24319-1-w.bumiller@proxmox.com> <20210429131322.24319-4-w.bumiller@proxmox.com> From: Dietmar Maurer Message-ID: Date: Fri, 30 Apr 2021 08:26:33 +0200 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.10.0 MIME-Version: 1.0 In-Reply-To: <20210429131322.24319-4-w.bumiller@proxmox.com> Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit Content-Language: en-US X-SPAM-LEVEL: Spam detection results: 0 AWL 0.277 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment NICE_REPLY_A -0.001 Looks like a legit reply (A) SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [config.rs, node.rs] Subject: Re: [pbs-devel] [REBASED backup 03/14] add node config X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 30 Apr 2021 06:27:05 -0000 Again, please can you include the suggested changes? On 4/29/21 3:13 PM, Wolfgang Bumiller wrote: > Signed-off-by: Wolfgang Bumiller > --- > src/config.rs | 1 + > src/config/node.rs | 190 +++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 191 insertions(+) > create mode 100644 src/config/node.rs > > diff --git a/src/config.rs b/src/config.rs > index 83ea0461..94b7fb6c 100644 > --- a/src/config.rs > +++ b/src/config.rs > @@ -20,6 +20,7 @@ pub mod acme; > pub mod cached_user_info; > pub mod datastore; > pub mod network; > +pub mod node; > pub mod remote; > pub mod sync; > pub mod tfa; > diff --git a/src/config/node.rs b/src/config/node.rs > new file mode 100644 > index 00000000..b6abeef3 > --- /dev/null > +++ b/src/config/node.rs > @@ -0,0 +1,190 @@ > +use std::fs::File; > +use std::time::Duration; > + > +use anyhow::{format_err, Error}; > +use nix::sys::stat::Mode; > +use serde::{Deserialize, Serialize}; > + > +use proxmox::api::api; > +use proxmox::api::schema::{self, Updater}; > +use proxmox::tools::fs::{replace_file, CreateOptions}; > + > +use crate::acme::AcmeClient; > +use crate::config::acme::{AccountName, AcmeDomain}; > + > +const CONF_FILE: &str = configdir!("/node.cfg"); > +const LOCK_FILE: &str = configdir!("/.node.cfg.lock"); > +const LOCK_TIMEOUT: Duration = Duration::from_secs(5); > + > +pub fn read_lock() -> Result { > + proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, false) > +} > + > +pub fn write_lock() -> Result { > + proxmox::tools::fs::open_file_locked(LOCK_FILE, LOCK_TIMEOUT, true) > +} > + > +/// Read the Node Config. > +pub fn config() -> Result<(NodeConfig, [u8; 32]), Error> { > + let content = > + proxmox::tools::fs::file_read_optional_string(CONF_FILE)?.unwrap_or_else(|| "".to_string()); > + > + let digest = openssl::sha::sha256(content.as_bytes()); > + let data: NodeConfig = crate::tools::config::from_str(&content, &NodeConfig::API_SCHEMA)?; > + > + Ok((data, digest)) > +} > + > +/// Write the Node Config, requires the write lock to be held. > +pub fn save_config(config: &NodeConfig) -> Result<(), Error> { > + let raw = crate::tools::config::to_bytes(config, &NodeConfig::API_SCHEMA)?; > + > + let backup_user = crate::backup::backup_user()?; > + let options = CreateOptions::new() > + .perm(Mode::from_bits_truncate(0o0640)) > + .owner(nix::unistd::ROOT) > + .group(backup_user.gid); > + > + replace_file(CONF_FILE, &raw, options) > +} > + > +#[api( > + properties: { > + account: { type: AccountName }, > + } > +)] > +#[derive(Deserialize, Serialize)] > +/// The ACME configuration. > +/// > +/// Currently only contains the name of the account use. > +pub struct AcmeConfig { > + /// Account to use to acquire ACME certificates. > + account: AccountName, > +} > + > +#[api( > + properties: { > + acme: { > + optional: true, > + type: String, > + format: &schema::ApiStringFormat::PropertyString(&AcmeConfig::API_SCHEMA), > + }, > + acmedomain0: { > + type: String, > + optional: true, > + format: &schema::ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA), > + }, > + acmedomain1: { > + type: String, > + optional: true, > + format: &schema::ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA), > + }, > + acmedomain2: { > + type: String, > + optional: true, > + format: &schema::ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA), > + }, > + acmedomain3: { > + type: String, > + optional: true, > + format: &schema::ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA), > + }, > + acmedomain4: { > + type: String, > + optional: true, > + format: &schema::ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA), > + }, > + }, > +)] > +#[derive(Deserialize, Serialize, Updater)] > +/// Node specific configuration. > +pub struct NodeConfig { > + /// The acme account to use on this node. > + #[serde(skip_serializing_if = "Updater::is_empty")] > + acme: Option, > + > + /// ACME domain to get a certificate for for this node. > + #[serde(skip_serializing_if = "Updater::is_empty")] > + acmedomain0: Option, > + > + /// ACME domain to get a certificate for for this node. > + #[serde(skip_serializing_if = "Updater::is_empty")] > + acmedomain1: Option, > + > + /// ACME domain to get a certificate for for this node. > + #[serde(skip_serializing_if = "Updater::is_empty")] > + acmedomain2: Option, > + > + /// ACME domain to get a certificate for for this node. > + #[serde(skip_serializing_if = "Updater::is_empty")] > + acmedomain3: Option, > + > + /// ACME domain to get a certificate for for this node. > + #[serde(skip_serializing_if = "Updater::is_empty")] > + acmedomain4: Option, > +} > + > +impl NodeConfig { > + pub fn acme_config(&self) -> Option> { > + self.acme.as_deref().map(|config| -> Result<_, Error> { > + Ok(crate::tools::config::from_property_string( > + config, > + &AcmeConfig::API_SCHEMA, > + )?) > + }) > + } > + > + pub async fn acme_client(&self) -> Result { > + AcmeClient::load( > + &self > + .acme_config() > + .ok_or_else(|| format_err!("no acme client configured"))?? > + .account, > + ) > + .await > + } > + > + pub fn acme_domains(&self) -> AcmeDomainIter { > + AcmeDomainIter::new(self) > + } > +} > + > +pub struct AcmeDomainIter<'a> { > + config: &'a NodeConfig, > + index: usize, > +} > + > +impl<'a> AcmeDomainIter<'a> { > + fn new(config: &'a NodeConfig) -> Self { > + Self { config, index: 0 } > + } > +} > + > +impl<'a> Iterator for AcmeDomainIter<'a> { > + type Item = Result; > + > + fn next(&mut self) -> Option { > + let domain = loop { > + let index = self.index; > + self.index += 1; > + > + let domain = match index { > + 0 => self.config.acmedomain0.as_deref(), > + 1 => self.config.acmedomain1.as_deref(), > + 2 => self.config.acmedomain2.as_deref(), > + 3 => self.config.acmedomain3.as_deref(), > + 4 => self.config.acmedomain4.as_deref(), > + _ => return None, > + }; > + > + if let Some(domain) = domain { > + break domain; > + } > + }; > + > + Some(crate::tools::config::from_property_string( > + domain, > + &AcmeDomain::API_SCHEMA, > + )) > + } > +}