public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
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> {





  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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal