From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 9ADD91FF139 for ; Tue, 10 Feb 2026 11:40:29 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 288261C103; Tue, 10 Feb 2026 11:41:14 +0100 (CET) Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Tue, 10 Feb 2026 11:41:08 +0100 Message-Id: To: "Lukas Wagner" , Subject: Re: [PATCH datacenter-manager v2 3/4] fix #7179: cli: admin: expose acme commands From: "Shan Shaji" X-Mailer: aerc 0.20.0 References: <20260203175101.457724-1-s.shaji@proxmox.com> <20260203175101.457724-4-s.shaji@proxmox.com> In-Reply-To: X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1770719983664 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.109 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: JQRIQ62EHESYXSGAMJ4FOSJOHT45UTLA X-Message-ID-Hash: JQRIQ62EHESYXSGAMJ4FOSJOHT45UTLA X-MailFrom: s.shaji@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Hi Lukas, thank you so much for the nice improvement feedbacks.=20 Some comments inline. On Thu Feb 5, 2026 at 3:26 PM CET, Lukas Wagner wrote: > Hi Shan, > > thanks for this updated version of your patch series. Looking good from > what I can, some comments inline. > > On Tue Feb 3, 2026 at 6:51 PM CET, Shan Shaji wrote: >> Previously, ACME commands were not exposed through the admin CLI. >> Added the necessary functionality to manage ACME settings directly >> via the command line. >> >> Signed-off-by: Shan Shaji >> --- >> [...] >> +///Register an ACME account. >> +async fn register_account( >> + name: AcmeAccountName, >> + contact: String, >> + directory: Option, >> + rpcenv: &mut dyn RpcEnvironment, >> +) -> Result<(), Error> { >> + let (directory_url, custom_directory) =3D match directory { >> + Some(directory) =3D> (directory, true), >> + None =3D> { >> + println!("Directory endpoints:"); >> + for (i, dir) in KNOWN_ACME_DIRECTORIES.iter().enumerate() { >> + println!("{}) {}", i, dir.url); >> + } >> + >> + println!("{}) Custom", KNOWN_ACME_DIRECTORIES.len()); >> + let mut attempt =3D 0; >> + loop { >> + let mut input =3D read_input("Enter selection")?; >> + match input.trim().parse::() { >> + Ok(n) if n < KNOWN_ACME_DIRECTORIES.len() =3D> { >> + break (KNOWN_ACME_DIRECTORIES[n].url.to_string(= ), false); >> + } >> + Ok(n) if n =3D=3D KNOWN_ACME_DIRECTORIES.len() =3D>= { >> + input.clear(); >> + input =3D read_input("Enter custom directory UR= I")?; >> + break (input.trim().to_owned(), true); >> + } >> + _ =3D> eprintln!("Invalid selection."), >> + } >> + >> + attempt +=3D 1; >> + if attempt >=3D 3 { >> + bail!("Aborting."); >> + } >> + } >> + } >> + }; >> + >> + println!("Attempting to fetch Terms of Service from {directory_url:= ?}"); >> + let mut client =3D AcmeClient::new(directory_url.clone()); >> + let directory =3D client.directory().await?; >> + let tos_agreed =3D if let Some(tos_url) =3D directory.terms_of_serv= ice_url() { >> + println!("Terms of Service: {tos_url}"); >> + let input =3D read_input("Do you agree to the above terms? [y|N= ]")?; >> + input.trim().eq_ignore_ascii_case("y") >> + } else { >> + println!("No Terms of Service found, proceeding."); >> + true >> + }; >> + >> + let mut eab_enabled =3D directory.external_account_binding_required= (); >> + if !eab_enabled && custom_directory { >> + let input =3D read_input("Do you want to use external account b= inding? [y|N]")?; >> + eab_enabled =3D input.trim().eq_ignore_ascii_case("y"); >> + } else if eab_enabled { >> + println!("The CA requires external account binding."); >> + } >> + >> + let eab_creds =3D if eab_enabled { >> + println!("You should have received a key id and a key from your= CA."); >> + let eab_kid =3D read_input("Enter EAB key id")?; >> + let eab_hmac_key =3D read_input("Enter EAB key")?; >> + Some((eab_kid.trim().to_owned(), eab_hmac_key.trim().to_owned()= )) >> + } else { >> + None >> + }; >> + >> + let tos_url =3D tos_agreed >> + .then(|| directory.terms_of_service_url().map(str::to_owned)) >> + .flatten(); >> + >> + let (eab_kid, eab_hmac_key) =3D eab_creds.unzip(); >> + let parameters =3D AcmeRegistrationParams { >> + name: Some(name), >> + contact: contact, >> + tos_url: tos_url, >> + directory: Some(directory_url), >> + eab_kid: eab_kid, >> + eab_hmac_key: eab_hmac_key, >> + }; >> + let param =3D serde_json::to_value(parameters)?; >> + >> + let info =3D &dc_api::config::acme::API_METHOD_REGISTER_ACCOUNT; >> + let result =3D match info.handler { >> + ApiHandler::Sync(handler) =3D> (handler)(param, info, rpcenv)?, >> + _ =3D> unreachable!(), >> + }; >> + >> + wait_for_local_worker(result.as_str().unwrap()).await?; >> + >> + Ok(()) >> +} > > Two things came to mind when actually trying out this CLI: > > - The directory selection is a bit odd to use, since it uses 0-based > indexing, something like > 0.) Let's Encrypt > 1.) Let's Encrypt Staging > .... > > 1-based indexing probably is a bit nicer for users here. I will add this in v3. Thanks! > - Also, I wonder if there should be non-interactive version of this > command as well, one that can be scripted, one where all parameters > that are asked as parameters can be provided as flags. > People can always use the API for this, but maybe it would still be > nice to offer this in the CLI tool as well. > Just thinking out loud, I'm aware that you just copied the approach > from PBS, if we add something like this, this should be done in a > separate series. This is a nice improvement. We already have an option to provide custom directory endpoint using --directory option. I will add additional options to accept the TOS selection and EAB credentials. =20 Do we need to provide a flag to choose the Lets encrypt endpoint =20 as we already have a --directory option? > - Might be worth moving this 'wizard' implementation to some helper > module in proxmox-acme-api and then use the same implementation from > both PDM and PBS. To avoid scope creep this can always be done in a > follow-up series. > > I guess in this case the appropriate place for the new > AcmeRegistrationParams is then also proxmox-acme-api, since the this > would be the type that is returned from such a shared helper. > > (sorry, some of these I could have noticed in my earlier, less > thorough review) > > All of these can be done in followup-patches, it's just some ideas for > improvement over the status quo that we already have in PBS. Thanks! I will move the wizard implementation to the proxmox-acme-api crate and use the same on both PBS and PDM. Will send another series once this series is applied.=20 >> + >> +#[api( >> + input: { >> + properties: { >> + name: { type: AcmeAccountName }, >> + contact: { >> + description: "List of email addresses.", >> + type: String, >> + optional: true, >> + } >> + } >> + } >> +)] >> +/// Update an ACME Account. >> +async fn update_account(param: Value, rpcenv: &mut dyn RpcEnvironment) = -> Result<(), Error> { >> + let info =3D &dc_api::config::acme::API_METHOD_UPDATE_ACCOUNT; >> + let result =3D match info.handler { >> + ApiHandler::Sync(handler) =3D> (handler)(param, info, rpcenv)?, >> + _ =3D> unreachable!(), >> + }; >> + >> + wait_for_local_worker(result.as_str().unwrap()).await?; >> + >> + Ok(()) >> +} >> + > > [...] > >> + >> +pub fn cert_cli() -> CommandLineInterface { >> + let cmd_def =3D CliCommandMap::new() >> + .insert("order", CliCommand::new(&API_METHOD_ORDER_ACME_CERT)) >> + .insert("revoke", CliCommand::new(&API_METHOD_REVOKE_ACME_CERT)= ); >> + >> + cmd_def.into() >> +} >> diff --git a/cli/admin/src/main.rs b/cli/admin/src/main.rs >> index 02148e3..3fd3c2f 100644 >> --- a/cli/admin/src/main.rs >> +++ b/cli/admin/src/main.rs >> @@ -9,6 +9,7 @@ use proxmox_router::RpcEnvironment; >> use proxmox_schema::api; >> use proxmox_sys::fs::CreateOptions; >> =20 >> +mod acme; >> mod remotes; >> mod support_status; >> =20 >> @@ -22,7 +23,11 @@ async fn run() -> Result<(), Error> { >> pdm_buildcfg::configdir!("/access"), >> ) >> .context("failed to setup access control config")?; >> + proxmox_acme_api::init(pdm_buildcfg::configdir!("/acme"), false) >> + .context("failed to initialize acme config")?; >> + >> proxmox_log::Logger::from_env("PDM_LOG", proxmox_log::LevelFilter::= INFO) >> + .tasklog() > > I'd use the 'old' tasklog_pbs function here for now, since then we can > apply these patches without bumping proxmox-log first. Changing this to > `tasklog` is trivial and can be done in a followup patch. > > We should be getting a deprecation warning when building PDM once > proxmox-log has been bumped, so there should be little chance to forget > it. > Will update it to `tasklog_pbs`. Thank You! >> .stderr() >> .init() >> .context("failed to set-up logger")?; >> @@ -30,6 +35,7 @@ async fn run() -> Result<(), Error> { >> server::context::init().context("could not set-up server context")?= ; >> =20 >> let cmd_def =3D CliCommandMap::new() >> + .insert("acme", acme::acme_mgmt_cli()) >> .insert("remote", remotes::cli()) >> .insert( >> "report",