all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [pdm-devel] [PATCH datacenter-manager 2/3] remote config: get token secret from shadow file if shadowed
Date: Mon,  1 Dec 2025 10:29:14 +0100	[thread overview]
Message-ID: <20251201092941.291325-3-f.gruenbichler@proxmox.com> (raw)
In-Reply-To: <20251201092941.291325-1-f.gruenbichler@proxmox.com>

by making this an explicit call (as opposed to automatically reading and
merging the shadow config), accidental leaks are less likely.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
if we want to go down the other route, that's of course also possible..

 lib/pdm-api-types/src/remotes.rs | 50 ++++++++++++++++++++++++++++++++
 lib/pdm-config/src/remotes.rs    | 30 +++++++++++++++++--
 server/src/api/pve/lxc.rs        |  2 +-
 server/src/api/pve/qemu.rs       |  2 +-
 server/src/connection.rs         |  8 +++--
 5 files changed, 86 insertions(+), 6 deletions(-)

diff --git a/lib/pdm-api-types/src/remotes.rs b/lib/pdm-api-types/src/remotes.rs
index bd90ef1..f69609c 100644
--- a/lib/pdm-api-types/src/remotes.rs
+++ b/lib/pdm-api-types/src/remotes.rs
@@ -149,6 +149,56 @@ impl ApiSectionDataEntry for Remote {
     }
 }
 
+#[api(
+    properties: {
+        "id": { schema: REMOTE_ID_SCHEMA },
+        "type": { type: RemoteType },
+    },
+)]
+/// The information required to connect to a remote instance.
+#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+pub struct RemoteShadow {
+    #[serde(rename = "type")]
+    pub ty: RemoteType,
+
+    /// An id for this entry.
+    pub id: String,
+
+    /// The access token's secret.
+    pub token: String,
+}
+
+impl ApiSectionDataEntry for RemoteShadow {
+    const INTERNALLY_TAGGED: Option<&'static str> = Some("type");
+    const SECION_CONFIG_USES_TYPE_KEY: bool = true;
+
+    /// Get the `SectionConfig` configuration for this enum.
+    fn section_config() -> &'static SectionConfig {
+        static CONFIG: OnceLock<SectionConfig> = OnceLock::new();
+
+        CONFIG.get_or_init(|| {
+            let mut this = SectionConfig::new(&REMOTE_ID_SCHEMA).with_type_key("type");
+            for ty in ["pve", "pbs"] {
+                this.register_plugin(SectionConfigPlugin::new(
+                    ty.to_string(),
+                    Some("id".to_string()),
+                    RemoteShadow::API_SCHEMA.unwrap_object_schema(),
+                ));
+            }
+            this
+        })
+    }
+
+    /// Maps an enum value to its type name.
+    fn section_type(&self) -> &'static str {
+        match self.ty {
+            RemoteType::Pve => "pve",
+            RemoteType::Pbs => "pbs",
+        }
+    }
+}
+
 /// Since `Uri` does not directly support `serde`, we turn this into using FromStr/Display.
 ///
 /// If we want to turn this into a property string, we can use a default_key, but may need to work
diff --git a/lib/pdm-config/src/remotes.rs b/lib/pdm-config/src/remotes.rs
index fc78707..93f80eb 100644
--- a/lib/pdm-config/src/remotes.rs
+++ b/lib/pdm-config/src/remotes.rs
@@ -5,17 +5,18 @@
 
 use std::sync::OnceLock;
 
-use anyhow::Error;
+use anyhow::{bail, Error};
 
 use proxmox_config_digest::ConfigDigest;
 use proxmox_product_config::{open_api_lockfile, replace_config, ApiLockGuard};
 use proxmox_section_config::typed::{ApiSectionDataEntry, SectionConfigData};
 
-use pdm_api_types::remotes::Remote;
+use pdm_api_types::remotes::{Remote, RemoteShadow};
 
 use pdm_buildcfg::configdir;
 
 pub const REMOTES_CFG_FILENAME: &str = configdir!("/remotes.cfg");
+const REMOTES_SHADOW_FILENAME: &str = configdir!("/remotes.shadow");
 pub const REMOTES_CFG_LOCKFILE: &str = configdir!("/.remotes.lock");
 
 static INSTANCE: OnceLock<Box<dyn RemoteConfig + Send + Sync>> = OnceLock::new();
@@ -44,6 +45,10 @@ pub fn config() -> Result<(SectionConfigData<Remote>, ConfigDigest), Error> {
     instance().config()
 }
 
+pub fn get_secret_token(remote: &Remote) -> Result<String, Error> {
+    instance().get_secret_token(remote)
+}
+
 /// Replace the currently persisted remotes config
 ///
 /// Will panic if the the remote config instance has not been set before.
@@ -54,6 +59,8 @@ pub fn save_config(config: SectionConfigData<Remote>) -> Result<(), Error> {
 pub trait RemoteConfig {
     /// Return contents of the remotes config
     fn config(&self) -> Result<(SectionConfigData<Remote>, ConfigDigest), Error>;
+    /// Return contents of the remotes shadow config
+    fn get_secret_token(&self, remote: &Remote) -> Result<String, Error>;
     /// Lock the remotes config
     fn lock_config(&self) -> Result<ApiLockGuard, Error>;
     /// Replace the currently persisted remotes config
@@ -83,6 +90,25 @@ impl RemoteConfig for DefaultRemoteConfig {
         let raw = Remote::write_section_config(REMOTES_CFG_FILENAME, &config)?;
         replace_config(REMOTES_CFG_FILENAME, raw.as_bytes())
     }
+
+    fn get_secret_token(&self, remote: &Remote) -> Result<String, Error> {
+        // not yet rewritten into shadow config
+        if remote.token != "-" {
+            return Ok(remote.token.clone());
+        }
+
+        let shadow_content = proxmox_sys::fs::file_read_optional_string(REMOTES_SHADOW_FILENAME)?
+            .unwrap_or_default();
+
+        let shadow_config =
+            RemoteShadow::parse_section_config(REMOTES_SHADOW_FILENAME, &shadow_content)?;
+
+        if let Some(shadow_entry) = shadow_config.get(&remote.id) {
+            Ok(shadow_entry.token.clone())
+        } else {
+            bail!("No shadow entry found for remote {id}", id = remote.id);
+        }
+    }
 }
 
 /// Initialize the [`RemoteConfig`] instance.
diff --git a/server/src/api/pve/lxc.rs b/server/src/api/pve/lxc.rs
index dc95783..198137b 100644
--- a/server/src/api/pve/lxc.rs
+++ b/server/src/api/pve/lxc.rs
@@ -545,7 +545,7 @@ pub async fn lxc_remote_migrate(
         "host={host},port={port},apitoken=PVEAPIToken={authid}={secret}",
         host = target_host_port.host(),
         authid = target.authid,
-        secret = target.token,
+        secret = pdm_config::remotes::get_secret_token(target)?,
         port = target_host_port.port_u16().unwrap_or(8006),
     );
     if let Some(fp) = target_node.fingerprint.as_deref() {
diff --git a/server/src/api/pve/qemu.rs b/server/src/api/pve/qemu.rs
index efab228..4a25ecb 100644
--- a/server/src/api/pve/qemu.rs
+++ b/server/src/api/pve/qemu.rs
@@ -593,7 +593,7 @@ pub async fn qemu_remote_migrate(
         "host={host},port={port},apitoken=PVEAPIToken={authid}={secret}",
         host = target_host_port.host(),
         authid = target.authid,
-        secret = target.token,
+        secret = pdm_config::remotes::get_secret_token(target)?,
         port = target_host_port.port_u16().unwrap_or(8006),
     );
     if let Some(fp) = target_node.fingerprint.as_deref() {
diff --git a/server/src/connection.rs b/server/src/connection.rs
index 1c0069e..7e36671 100644
--- a/server/src/connection.rs
+++ b/server/src/connection.rs
@@ -110,9 +110,11 @@ fn prepare_connect_client(
 /// authentication information and settings for the [`RemoteType`]
 fn connect(remote: &Remote, target_endpoint: Option<&str>) -> Result<Client, anyhow::Error> {
     let (client, info) = prepare_connect_client(remote, target_endpoint)?;
+    let token = pdm_config::remotes::get_secret_token(remote)?;
+
     client.set_authentication(proxmox_client::Token {
         userid: remote.authid.to_string(),
-        value: remote.token.to_string(),
+        value: token,
         prefix: info.prefix,
         perl_compat: info.perl_compat,
     });
@@ -148,10 +150,12 @@ fn prepare_connect_multi_client(remote: &Remote) -> Result<(MultiClient, Connect
 fn multi_connect(remote: &Remote) -> Result<MultiClient, anyhow::Error> {
     let (client, info) = prepare_connect_multi_client(remote)?;
 
+    let token = pdm_config::remotes::get_secret_token(remote)?;
+
     client.for_each_client(|client| {
         client.set_authentication(proxmox_client::Token {
             userid: remote.authid.to_string(),
-            value: remote.token.to_string(),
+            value: token.clone(),
             prefix: info.prefix.clone(),
             perl_compat: info.perl_compat,
         });
-- 
2.47.3



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel

  parent reply	other threads:[~2025-12-01  9:29 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-01  9:29 [pdm-devel] [PATCH datacenter-manager 0/3] token secret shadow config Fabian Grünbichler
2025-12-01  9:29 ` [pdm-devel] [PATCH datacenter-manager 1/3] remote config: let save_config take ownership Fabian Grünbichler
2025-12-01  9:29 ` Fabian Grünbichler [this message]
2025-12-01  9:29 ` [pdm-devel] [PATCH datacenter-manager 3/3] remote config: shadow token secrets when saving Fabian Grünbichler
2025-12-01 14:39   ` Lukas Wagner
2025-12-01 14:46 ` [pdm-devel] [PATCH datacenter-manager 0/3] token secret shadow config Lukas Wagner
2025-12-01 16:44 ` [pdm-devel] applied: " Thomas Lamprecht

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251201092941.291325-3-f.gruenbichler@proxmox.com \
    --to=f.gruenbichler@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal