From: "Shannon Sterz" <s.sterz@proxmox.com>
To: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>,
pve-devel@lists.proxmox.com
Subject: Re: [RFC proxmox 2/6] proxmox-subscription: add new machine-id based serverid
Date: Mon, 13 Apr 2026 10:45:32 +0200 [thread overview]
Message-ID: <DHRW8V7B8Q03.2RLR02WWTWYU@proxmox.com> (raw)
In-Reply-To: <20260410100326.3199377-3-f.gruenbichler@proxmox.com>
On Fri Apr 10, 2026 at 12:02 PM CEST, Fabian Grünbichler wrote:
> and adapt the code to allow querying all possible serverids, and accepting the
> existing one if it matches one of the candidates.
>
> Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
> ---
>
> Notes:
> requires bumped librust-proxmox-systemd-dev
>
> proxmox-subscription/Cargo.toml | 3 +-
> proxmox-subscription/debian/control | 2 +
> proxmox-subscription/src/lib.rs | 2 +-
> proxmox-subscription/src/subscription_info.rs | 105 ++++++++++++++++--
> 4 files changed, 98 insertions(+), 14 deletions(-)
>
> diff --git a/proxmox-subscription/Cargo.toml b/proxmox-subscription/Cargo.toml
> index dda31a69..4db7ca71 100644
> --- a/proxmox-subscription/Cargo.toml
> +++ b/proxmox-subscription/Cargo.toml
> @@ -23,11 +23,12 @@ proxmox-base64 = { workspace = true, optional = true }
> proxmox-http = { workspace = true, optional = true, features = ["client-trait", "http-helpers"] }
> proxmox-serde.workspace = true
> proxmox-sys = { workspace = true, optional = true }
> +proxmox-systemd = { workspace = true, optional = true }
> proxmox-time = { workspace = true, optional = true }
>
> proxmox-schema = { workspace = true, features = ["api-macro"], optional = true }
>
> [features]
> default = ["impl"]
> -impl = [ "dep:proxmox-base64", "dep:hex", "dep:openssl", "dep:proxmox-http", "dep:proxmox-sys", "dep:proxmox-time"]
> +impl = [ "dep:proxmox-base64", "dep:hex", "dep:openssl", "dep:proxmox-http", "dep:proxmox-sys", "dep:proxmox-systemd", "dep:proxmox-time"]
> api-types = ["dep:proxmox-schema"]
> diff --git a/proxmox-subscription/debian/control b/proxmox-subscription/debian/control
> index 5fbfcccb..5584d0c1 100644
> --- a/proxmox-subscription/debian/control
> +++ b/proxmox-subscription/debian/control
> @@ -16,6 +16,7 @@ Build-Depends-Arch: cargo:native <!nocheck>,
> librust-proxmox-serde-1+default-dev <!nocheck>,
> librust-proxmox-serde-1+serde-json-dev <!nocheck>,
> librust-proxmox-sys-1+default-dev <!nocheck>,
> + librust-proxmox-systemd-1+default-dev <!nocheck>,
> librust-proxmox-time-2+default-dev (>= 2.1.0-~~) <!nocheck>,
> librust-regex-1+default-dev (>= 1.5-~~) <!nocheck>,
> librust-serde-1+default-dev <!nocheck>,
> @@ -78,6 +79,7 @@ Depends:
> librust-proxmox-http-1+default-dev (>= 1.0.5-~~),
> librust-proxmox-http-1+http-helpers-dev (>= 1.0.5-~~),
> librust-proxmox-sys-1+default-dev,
> + librust-proxmox-systemd-1+default-dev,
> librust-proxmox-time-2+default-dev (>= 2.1.0-~~)
> Provides:
> librust-proxmox-subscription+default-dev (= ${binary:Version}),
> diff --git a/proxmox-subscription/src/lib.rs b/proxmox-subscription/src/lib.rs
> index 2ed96903..eb1573e6 100644
> --- a/proxmox-subscription/src/lib.rs
> +++ b/proxmox-subscription/src/lib.rs
> @@ -3,7 +3,7 @@
> mod subscription_info;
> #[cfg(feature = "impl")]
> pub use subscription_info::{
> - get_hardware_address, ProductType, SubscriptionInfo, SubscriptionStatus,
> + get_hardware_address_candidates, ProductType, ServerId, SubscriptionInfo, SubscriptionStatus,
> };
>
> #[cfg(not(feature = "impl"))]
> diff --git a/proxmox-subscription/src/subscription_info.rs b/proxmox-subscription/src/subscription_info.rs
> index f53b3ce3..f0daa51f 100644
> --- a/proxmox-subscription/src/subscription_info.rs
> +++ b/proxmox-subscription/src/subscription_info.rs
> @@ -47,6 +47,43 @@ impl std::fmt::Display for SubscriptionStatus {
> }
> }
>
> +/// Variant discriminator for `ServerId`
> +#[derive(Clone, Copy, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
> +pub enum ServerIdType {
> + /// Legacy variant tied to SSH host key, for backwards compatibility
> + SshMd5,
> + /// Tied to /etc/machine-id
> + MachineId,
> +}
> +
> +impl Display for ServerIdType {
> + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
> + let txt = match self {
> + ServerIdType::SshMd5 => "SSH MD5",
> + ServerIdType::MachineId => "machine-id",
> + };
> + f.write_str(txt)
> + }
> +}
> +
> +/// Serverid used to bind subscription key to system
> +pub struct ServerId {
> + ty: ServerIdType,
> + id: String,
> +}
> +
> +impl ServerId {
> + pub fn kind(&self) -> ServerIdType {
> + self.ty
> + }
> +}
> +
> +impl Display for ServerId {
> + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
> + f.write_str(&self.id)
> + }
> +}
> +
> #[cfg_attr(feature = "api-types", api())]
> #[cfg_attr(feature = "api-types", derive(Updater))]
> #[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
> @@ -133,7 +170,7 @@ pub struct SubscriptionInfo {
> }
>
> #[cfg(feature = "impl")]
> -pub use _impl::get_hardware_address;
> +pub use _impl::get_hardware_address_candidates;
>
> #[cfg(feature = "impl")]
> pub(crate) use _impl::{md5sum, SHARED_KEY_DATA};
> @@ -151,6 +188,10 @@ mod _impl {
>
> use crate::sign::Verifier;
>
> + // Generated using `systemd-sd128 new`
> + pub(crate) const PMX_APPLICATION_ID: [u8; 16] =
> + 0x0e6b456a63fe4892997e9f42ebfaf980_u128.to_le_bytes();
> +
> pub(crate) const SHARED_KEY_DATA: &str = "kjfdlskfhiuewhfk947368";
>
> /// How long the local key is valid for in between remote checks
> @@ -245,7 +286,7 @@ mod _impl {
> /// `status` is set to [SubscriptionStatus::Invalid] and `message` to a human-readable
> /// message in case it does not.
> pub fn check_server_id(&mut self) {
> - match (self.serverid.as_ref(), get_hardware_address()) {
> + match (self.serverid.as_ref(), get_hardware_address_candidates()) {
> (_, Err(err)) => {
> self.status = SubscriptionStatus::Invalid;
> self.message = Some(format!("Failed to obtain server ID - {err}."));
> @@ -256,7 +297,9 @@ mod _impl {
> self.message = Some("Missing server ID.".to_string());
> self.signature = None;
> }
> - (Some(contained), Ok(expected)) if &expected != contained => {
> + (Some(contained), Ok(expected))
> + if !expected.iter().any(|serverid| serverid.id == *contained) =>
> + {
> self.status = SubscriptionStatus::Invalid;
> self.message = Some("Server ID mismatch.".to_string());
> self.signature = None;
> @@ -316,16 +359,54 @@ mod _impl {
> hash(MessageDigest::md5(), data).map_err(Error::from)
> }
>
> + fn get_hardware_address(ty: super::ServerIdType) -> Result<super::ServerId, Error> {
> + fn get_ssh_key() -> Result<Vec<u8>, Error> {
> + static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
> +
> + proxmox_sys::fs::file_get_contents(FILENAME)
> + .map_err(|e| format_err!("Error getting host key - {}", e))
> + }
> +
> + let id = match ty {
> + crate::subscription_info::ServerIdType::SshMd5 => {
> + let digest = md5sum(&get_ssh_key()?)
> + .map_err(|e| format_err!("Error digesting host key - {}", e))?;
nit: could inline these errors here.
> +
> + hex::encode(digest).to_uppercase()
> + }
> + crate::subscription_info::ServerIdType::MachineId => {
> + let machine_id =
> + proxmox_systemd::sd_id128::get_app_specific_id(PMX_APPLICATION_ID)?;
> + hex::encode(machine_id).to_uppercase()
> + }
> + };
> + Ok(super::ServerId { ty, id })
> + }
> +
> /// Generate the current system's "server ID".
> - pub fn get_hardware_address() -> Result<String, Error> {
> - static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
> -
> - let contents = proxmox_sys::fs::file_get_contents(FILENAME)
> - .map_err(|e| format_err!("Error getting host key - {}", e))?;
> - let digest =
> - md5sum(&contents).map_err(|e| format_err!("Error digesting host key - {}", e))?;
> -
> - Ok(hex::encode(digest).to_uppercase())
> + pub fn get_hardware_address_candidates() -> Result<Vec<super::ServerId>, Error> {
> + let mut res = Vec::new();
> + let mut errors = Vec::new();
> + let variants = [super::ServerIdType::MachineId, super::ServerIdType::SshMd5];
> + for ty in variants {
> + match get_hardware_address(ty) {
> + Ok(id) => res.push(id),
> + Err(err) => errors.push((ty, err)),
> + }
> + }
> + if res.is_empty() {
> + let error_strings: Vec<String> = errors
> + .into_iter()
> + .map(|(ty, err)| format!("{ty}: {err}"))
> + .collect();
> + let msg = if error_strings.is_empty() {
> + "unknown error".to_string()
> + } else {
> + error_strings.join(", ")
> + };
> + bail!("Failed to get any hardware address candidate: {msg}",);
> + }
> + Ok(res)
> }
>
> fn parse_next_due(value: &str) -> Result<i64, Error> {
next prev parent reply other threads:[~2026-04-13 8:45 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-10 10:02 [RFC manager/proxmox{,-backup,-perl-rs} 0/6] adapt subscription handling to alternative server IDs Fabian Grünbichler
2026-04-10 10:02 ` [PATCH proxmox 1/6] systemd: add support for machine-id generation Fabian Grünbichler
2026-04-13 8:45 ` Shannon Sterz
2026-04-10 10:02 ` [RFC proxmox 2/6] proxmox-subscription: add new machine-id based serverid Fabian Grünbichler
2026-04-13 8:45 ` Shannon Sterz [this message]
2026-04-10 10:02 ` [RFC proxmox-backup 3/6] subscription: adapt to multiple server ID variants Fabian Grünbichler
2026-04-10 10:02 ` [RFC proxmox-perl-rs 4/6] common: subscription: expose server ID candidates Fabian Grünbichler
2026-04-10 10:02 ` [RFC manager 5/6] subscription: adapt to multiple server ID variants Fabian Grünbichler
2026-04-10 10:02 ` [RFC manager 6/6] api2tools: remove unused get_hwaddress Fabian Grünbichler
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=DHRW8V7B8Q03.2RLR02WWTWYU@proxmox.com \
--to=s.sterz@proxmox.com \
--cc=f.gruenbichler@proxmox.com \
--cc=pve-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