[pbs-devel] [PATCH proxmox-backup v9 02/46] config: introduce s3 object store client configuration

Christian Ebner c.ebner at proxmox.com
Sat Jul 19 14:49:51 CEST 2025


Adds the client configuration for s3 object store as dedicated
configuration files, with secrets being stored separately from the
regular configuration and excluded from api responses for security
reasons.

Signed-off-by: Christian Ebner <c.ebner at proxmox.com>
---
changes since version 8:
- use pbs_buildcfg::configdir as config base path
- define and expose `s3client` and `s3secrets` as config type constants
- use object_schema_unwrap()
- add missing docstrings

 pbs-config/Cargo.toml |  1 +
 pbs-config/src/lib.rs |  1 +
 pbs-config/src/s3.rs  | 89 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 91 insertions(+)
 create mode 100644 pbs-config/src/s3.rs

diff --git a/pbs-config/Cargo.toml b/pbs-config/Cargo.toml
index 284149658..74afb3c64 100644
--- a/pbs-config/Cargo.toml
+++ b/pbs-config/Cargo.toml
@@ -19,6 +19,7 @@ serde_json.workspace = true
 
 proxmox-notify.workspace = true
 proxmox-router = { workspace = true, default-features = false }
+proxmox-s3-client.workspace = true
 proxmox-schema.workspace = true
 proxmox-section-config.workspace = true
 proxmox-shared-memory.workspace = true
diff --git a/pbs-config/src/lib.rs b/pbs-config/src/lib.rs
index 9c4d77c24..d03c079ab 100644
--- a/pbs-config/src/lib.rs
+++ b/pbs-config/src/lib.rs
@@ -10,6 +10,7 @@ pub mod network;
 pub mod notifications;
 pub mod prune;
 pub mod remote;
+pub mod s3;
 pub mod sync;
 pub mod tape_job;
 pub mod token_shadow;
diff --git a/pbs-config/src/s3.rs b/pbs-config/src/s3.rs
new file mode 100644
index 000000000..7351cd184
--- /dev/null
+++ b/pbs-config/src/s3.rs
@@ -0,0 +1,89 @@
+use std::collections::HashMap;
+use std::sync::LazyLock;
+
+use anyhow::Error;
+
+use proxmox_s3_client::{S3ClientConfig, S3ClientSecretsConfig};
+use proxmox_schema::*;
+use proxmox_section_config::{SectionConfig, SectionConfigData, SectionConfigPlugin};
+
+use pbs_api_types::JOB_ID_SCHEMA;
+
+use pbs_buildcfg::configdir;
+
+use crate::{open_backup_lockfile, replace_backup_config, BackupLockGuard};
+
+pub static CONFIG: LazyLock<SectionConfig> = LazyLock::new(init);
+
+fn init() -> SectionConfig {
+    let obj_schema = S3ClientConfig::API_SCHEMA.unwrap_object_schema();
+    let secrets_obj_schema = S3ClientSecretsConfig::API_SCHEMA.unwrap_object_schema();
+    let plugin =
+        SectionConfigPlugin::new("s3client".to_string(), Some(String::from("id")), obj_schema);
+    let secrets_plugin = SectionConfigPlugin::new(
+        "s3secrets".to_string(),
+        Some(String::from("secrets-id")),
+        secrets_obj_schema,
+    );
+    let mut config = SectionConfig::new(&JOB_ID_SCHEMA);
+    config.register_plugin(plugin);
+    config.register_plugin(secrets_plugin);
+
+    config
+}
+
+/// Configuration file location for S3 client.
+pub const S3_CFG_FILENAME: &str = configdir!("/s3.cfg");
+/// Configuration file location for S3 client secret with limited access, only readable by `root`.
+pub const S3_SECRETS_CFG_FILENAME: &str = configdir!("/s3-secrets.cfg");
+/// Configuration lock file used to prevent concurrent configuration update operations.
+pub const S3_CFG_LOCKFILE: &str = configdir!("/.s3.lck");
+
+/// Config type for s3 client config entries
+pub const S3_CFG_TYPE_ID: &str = "s3client";
+/// Config type for s3 client secrets config entries
+pub const S3_SECRETS_CFG_TYPE_ID: &str = "s3secrets";
+
+/// Get exclusive lock for S3 client configuration update.
+pub fn lock_config() -> Result<BackupLockGuard, Error> {
+    open_backup_lockfile(S3_CFG_LOCKFILE, None, true)
+}
+
+/// Load s3 client configuration from file.
+pub fn config() -> Result<(SectionConfigData, [u8; 32]), Error> {
+    parse_config(S3_CFG_FILENAME)
+}
+
+/// Load s3 client secrets configuration from file.
+pub fn secrets_config() -> Result<(SectionConfigData, [u8; 32]), Error> {
+    parse_config(S3_SECRETS_CFG_FILENAME)
+}
+
+/// Save given s3 client and secrets configuration to file.
+pub fn save_config(config: &SectionConfigData, secrets: &SectionConfigData) -> Result<(), Error> {
+    let raw = CONFIG.write(S3_CFG_FILENAME, config)?;
+    replace_backup_config(S3_CFG_FILENAME, raw.as_bytes())?;
+
+    let secrets_raw = CONFIG.write(S3_SECRETS_CFG_FILENAME, secrets)?;
+    // Secrets are stored with `backup` permissions to allow reading from
+    // not protected api endpoints as well.
+    replace_backup_config(S3_SECRETS_CFG_FILENAME, secrets_raw.as_bytes())?;
+
+    Ok(())
+}
+
+/// Shell completion helper to complete s3 client id's as found in the config.
+pub fn complete_s3_client_id(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
+    match config() {
+        Ok((data, _digest)) => data.sections.keys().map(|id| id.to_string()).collect(),
+        Err(_) => Vec::new(),
+    }
+}
+
+fn parse_config(path: &str) -> Result<(SectionConfigData, [u8; 32]), Error> {
+    let content = proxmox_sys::fs::file_read_optional_string(path)?;
+    let content = content.unwrap_or_default();
+    let digest = openssl::sha::sha256(content.as_bytes());
+    let data = CONFIG.parse(path, &content)?;
+    Ok((data, digest))
+}
-- 
2.47.2





More information about the pbs-devel mailing list