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
next prev 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox