all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Samuel Rufinatscha <s.rufinatscha@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-backup v6 2/2] acme: remove unused src/acme and plugin code
Date: Fri, 16 Jan 2026 12:28:59 +0100	[thread overview]
Message-ID: <20260116112859.194016-6-s.rufinatscha@proxmox.com> (raw)
In-Reply-To: <20260116112859.194016-1-s.rufinatscha@proxmox.com>

Removes the unused src/acme module and plugin code as PBS now uses the
factored out client/API handlers.

Signed-off-by: Samuel Rufinatscha <s.rufinatscha@proxmox.com>
---
 src/acme/mod.rs           |   1 -
 src/acme/plugin.rs        | 335 --------------------------------------
 src/api2/types/acme.rs    |  38 -----
 src/api2/types/mod.rs     |   3 -
 src/config/acme/mod.rs    |   1 -
 src/config/acme/plugin.rs | 105 ------------
 src/config/mod.rs         |   1 -
 src/lib.rs                |   2 -
 8 files changed, 486 deletions(-)
 delete mode 100644 src/acme/mod.rs
 delete mode 100644 src/acme/plugin.rs
 delete mode 100644 src/api2/types/acme.rs
 delete mode 100644 src/config/acme/mod.rs
 delete mode 100644 src/config/acme/plugin.rs

diff --git a/src/acme/mod.rs b/src/acme/mod.rs
deleted file mode 100644
index 700d90d7..00000000
--- a/src/acme/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub(crate) mod plugin;
diff --git a/src/acme/plugin.rs b/src/acme/plugin.rs
deleted file mode 100644
index 6804243c..00000000
--- a/src/acme/plugin.rs
+++ /dev/null
@@ -1,335 +0,0 @@
-use std::future::Future;
-use std::net::{IpAddr, SocketAddr};
-use std::pin::Pin;
-use std::process::Stdio;
-use std::sync::Arc;
-use std::time::Duration;
-
-use anyhow::{bail, format_err, Error};
-use bytes::Bytes;
-use futures::TryFutureExt;
-use http_body_util::Full;
-use hyper::body::Incoming;
-use hyper::server::conn::http1;
-use hyper::service::service_fn;
-use hyper::{Request, Response};
-use hyper_util::rt::TokioIo;
-use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWriteExt, BufReader};
-use tokio::net::TcpListener;
-use tokio::process::Command;
-
-use proxmox_acme::async_client::AcmeClient;
-use proxmox_acme::{Authorization, Challenge};
-use proxmox_rest_server::WorkerTask;
-
-use crate::api2::types::AcmeDomain;
-use crate::config::acme::plugin::{DnsPlugin, PluginData};
-
-const PROXMOX_ACME_SH_PATH: &str = "/usr/share/proxmox-acme/proxmox-acme";
-
-pub(crate) fn get_acme_plugin(
-    plugin_data: &PluginData,
-    name: &str,
-) -> Result<Option<Box<dyn AcmePlugin + Send + Sync + 'static>>, Error> {
-    let (ty, data) = match plugin_data.get(name) {
-        Some(plugin) => plugin,
-        None => return Ok(None),
-    };
-
-    Ok(Some(match ty.as_str() {
-        "dns" => {
-            let plugin: DnsPlugin = serde::Deserialize::deserialize(data)?;
-            Box::new(plugin)
-        }
-        "standalone" => {
-            // this one has no config
-            Box::<StandaloneServer>::default()
-        }
-        other => bail!("missing implementation for plugin type '{}'", other),
-    }))
-}
-
-pub(crate) trait AcmePlugin {
-    /// Setup everything required to trigger the validation and return the corresponding validation
-    /// URL.
-    fn setup<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
-        &'a mut self,
-        client: &'b mut AcmeClient,
-        authorization: &'c Authorization,
-        domain: &'d AcmeDomain,
-        task: Arc<WorkerTask>,
-    ) -> Pin<Box<dyn Future<Output = Result<&'c str, Error>> + Send + 'fut>>;
-
-    fn teardown<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
-        &'a mut self,
-        client: &'b mut AcmeClient,
-        authorization: &'c Authorization,
-        domain: &'d AcmeDomain,
-        task: Arc<WorkerTask>,
-    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'fut>>;
-}
-
-fn extract_challenge<'a>(
-    authorization: &'a Authorization,
-    ty: &str,
-) -> Result<&'a Challenge, Error> {
-    authorization
-        .challenges
-        .iter()
-        .find(|ch| ch.ty == ty)
-        .ok_or_else(|| format_err!("no supported challenge type ({}) found", ty))
-}
-
-async fn pipe_to_tasklog<T: AsyncRead + Unpin>(
-    pipe: T,
-    task: Arc<WorkerTask>,
-) -> Result<(), std::io::Error> {
-    let mut pipe = BufReader::new(pipe);
-    let mut line = String::new();
-    loop {
-        line.clear();
-        match pipe.read_line(&mut line).await {
-            Ok(0) => return Ok(()),
-            Ok(_) => task.log_message(line.as_str()),
-            Err(err) => return Err(err),
-        }
-    }
-}
-
-impl DnsPlugin {
-    async fn action<'a>(
-        &self,
-        client: &mut AcmeClient,
-        authorization: &'a Authorization,
-        domain: &AcmeDomain,
-        task: Arc<WorkerTask>,
-        action: &str,
-    ) -> Result<&'a str, Error> {
-        let challenge = extract_challenge(authorization, "dns-01")?;
-        let mut stdin_data = client
-            .dns_01_txt_value(
-                challenge
-                    .token()
-                    .ok_or_else(|| format_err!("missing token in challenge"))?,
-            )?
-            .into_bytes();
-        stdin_data.push(b'\n');
-        stdin_data.extend(self.data.as_bytes());
-        if stdin_data.last() != Some(&b'\n') {
-            stdin_data.push(b'\n');
-        }
-
-        let mut command = Command::new("/usr/bin/setpriv");
-
-        #[rustfmt::skip]
-        command.args([
-            "--reuid", "nobody",
-            "--regid", "nogroup",
-            "--clear-groups",
-            "--reset-env",
-            "--",
-            "/bin/bash",
-                PROXMOX_ACME_SH_PATH,
-                action,
-                &self.core.api,
-                domain.alias.as_deref().unwrap_or(&domain.domain),
-        ]);
-
-        // We could use 1 socketpair, but tokio wraps them all in `File` internally causing `close`
-        // to be called separately on all of them without exception, so we need 3 pipes :-(
-
-        let mut child = command
-            .stdin(Stdio::piped())
-            .stdout(Stdio::piped())
-            .stderr(Stdio::piped())
-            .spawn()?;
-
-        let mut stdin = child.stdin.take().expect("Stdio::piped()");
-        let stdout = child.stdout.take().expect("Stdio::piped() failed?");
-        let stdout = pipe_to_tasklog(stdout, Arc::clone(&task));
-        let stderr = child.stderr.take().expect("Stdio::piped() failed?");
-        let stderr = pipe_to_tasklog(stderr, Arc::clone(&task));
-        let stdin = async move {
-            stdin.write_all(&stdin_data).await?;
-            stdin.flush().await?;
-            Ok::<_, std::io::Error>(())
-        };
-        match futures::try_join!(stdin, stdout, stderr) {
-            Ok(((), (), ())) => (),
-            Err(err) => {
-                if let Err(err) = child.kill().await {
-                    task.log_message(format!(
-                        "failed to kill '{PROXMOX_ACME_SH_PATH} {action}' command: {err}"
-                    ));
-                }
-                bail!("'{}' failed: {}", PROXMOX_ACME_SH_PATH, err);
-            }
-        }
-
-        let status = child.wait().await?;
-        if !status.success() {
-            bail!(
-                "'{} {}' exited with error ({})",
-                PROXMOX_ACME_SH_PATH,
-                action,
-                status.code().unwrap_or(-1)
-            );
-        }
-
-        Ok(&challenge.url)
-    }
-}
-
-impl AcmePlugin for DnsPlugin {
-    fn setup<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
-        &'a mut self,
-        client: &'b mut AcmeClient,
-        authorization: &'c Authorization,
-        domain: &'d AcmeDomain,
-        task: Arc<WorkerTask>,
-    ) -> Pin<Box<dyn Future<Output = Result<&'c str, Error>> + Send + 'fut>> {
-        Box::pin(async move {
-            let result = self
-                .action(client, authorization, domain, task.clone(), "setup")
-                .await;
-
-            let validation_delay = self.core.validation_delay.unwrap_or(30) as u64;
-            if validation_delay > 0 {
-                task.log_message(format!(
-                    "Sleeping {validation_delay} seconds to wait for TXT record propagation"
-                ));
-                tokio::time::sleep(Duration::from_secs(validation_delay)).await;
-            }
-            result
-        })
-    }
-
-    fn teardown<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
-        &'a mut self,
-        client: &'b mut AcmeClient,
-        authorization: &'c Authorization,
-        domain: &'d AcmeDomain,
-        task: Arc<WorkerTask>,
-    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'fut>> {
-        Box::pin(async move {
-            self.action(client, authorization, domain, task, "teardown")
-                .await
-                .map(drop)
-        })
-    }
-}
-
-#[derive(Default)]
-struct StandaloneServer {
-    abort_handle: Option<futures::future::AbortHandle>,
-}
-
-// In case the "order_certificates" future gets dropped between setup & teardown, let's also cancel
-// the HTTP listener on Drop:
-impl Drop for StandaloneServer {
-    fn drop(&mut self) {
-        self.stop();
-    }
-}
-
-impl StandaloneServer {
-    fn stop(&mut self) {
-        if let Some(abort) = self.abort_handle.take() {
-            abort.abort();
-        }
-    }
-}
-
-async fn standalone_respond(
-    req: Request<Incoming>,
-    path: Arc<String>,
-    key_auth: Arc<String>,
-) -> Result<Response<Full<Bytes>>, hyper::Error> {
-    if req.method() == hyper::Method::GET && req.uri().path() == path.as_str() {
-        Ok(Response::builder()
-            .status(hyper::http::StatusCode::OK)
-            .body(key_auth.as_bytes().to_vec().into())
-            .unwrap())
-    } else {
-        Ok(Response::builder()
-            .status(hyper::http::StatusCode::NOT_FOUND)
-            .body("Not found.".into())
-            .unwrap())
-    }
-}
-
-impl AcmePlugin for StandaloneServer {
-    fn setup<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
-        &'a mut self,
-        client: &'b mut AcmeClient,
-        authorization: &'c Authorization,
-        _domain: &'d AcmeDomain,
-        _task: Arc<WorkerTask>,
-    ) -> Pin<Box<dyn Future<Output = Result<&'c str, Error>> + Send + 'fut>> {
-        Box::pin(async move {
-            self.stop();
-
-            let challenge = extract_challenge(authorization, "http-01")?;
-            let token = challenge
-                .token()
-                .ok_or_else(|| format_err!("missing token in challenge"))?;
-            let key_auth = Arc::new(client.key_authorization(token)?);
-            let path = Arc::new(format!("/.well-known/acme-challenge/{token}"));
-
-            // `[::]:80` first, then `*:80`
-            let dual = SocketAddr::new(IpAddr::from([0u16; 8]), 80);
-            let ipv4 = SocketAddr::new(IpAddr::from([0u8; 4]), 80);
-            let incoming = TcpListener::bind(dual)
-                .or_else(|_| TcpListener::bind(ipv4))
-                .await?;
-
-            let server = async move {
-                loop {
-                    let key_auth = Arc::clone(&key_auth);
-                    let path = Arc::clone(&path);
-                    match incoming.accept().await {
-                        Ok((tcp, _)) => {
-                            let io = TokioIo::new(tcp);
-                            let service = service_fn(move |request| {
-                                standalone_respond(
-                                    request,
-                                    Arc::clone(&path),
-                                    Arc::clone(&key_auth),
-                                )
-                            });
-
-                            tokio::task::spawn(async move {
-                                if let Err(err) =
-                                    http1::Builder::new().serve_connection(io, service).await
-                                {
-                                    println!("Error serving connection: {err:?}");
-                                }
-                            });
-                        }
-                        Err(err) => println!("Error accepting connection: {err:?}"),
-                    }
-                }
-            };
-            let (future, abort) = futures::future::abortable(server);
-            self.abort_handle = Some(abort);
-            tokio::spawn(future);
-
-            Ok(challenge.url.as_str())
-        })
-    }
-
-    fn teardown<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>(
-        &'a mut self,
-        _client: &'b mut AcmeClient,
-        _authorization: &'c Authorization,
-        _domain: &'d AcmeDomain,
-        _task: Arc<WorkerTask>,
-    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'fut>> {
-        Box::pin(async move {
-            if let Some(abort) = self.abort_handle.take() {
-                abort.abort();
-            }
-            Ok(())
-        })
-    }
-}
diff --git a/src/api2/types/acme.rs b/src/api2/types/acme.rs
deleted file mode 100644
index b83b9882..00000000
--- a/src/api2/types/acme.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-use pbs_api_types::{DNS_ALIAS_FORMAT, DNS_NAME_FORMAT, PROXMOX_SAFE_ID_FORMAT};
-use proxmox_schema::api;
-
-#[api(
-    properties: {
-        "domain": { format: &DNS_NAME_FORMAT },
-        "alias": {
-            optional: true,
-            format: &DNS_ALIAS_FORMAT,
-        },
-        "plugin": {
-            optional: true,
-            format: &PROXMOX_SAFE_ID_FORMAT,
-        },
-    },
-    default_key: "domain",
-)]
-#[derive(Deserialize, Serialize)]
-/// A domain entry for an ACME certificate.
-pub struct AcmeDomain {
-    /// The domain to certify for.
-    pub domain: String,
-
-    /// The domain to use for challenges instead of the default acme challenge domain.
-    ///
-    /// This is useful if you use CNAME entries to redirect `_acme-challenge.*` domains to a
-    /// different DNS server.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub alias: Option<String>,
-
-    /// The plugin to use to validate this domain.
-    ///
-    /// Empty means standalone HTTP validation is used.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub plugin: Option<String>,
-}
diff --git a/src/api2/types/mod.rs b/src/api2/types/mod.rs
index afc34b30..34193685 100644
--- a/src/api2/types/mod.rs
+++ b/src/api2/types/mod.rs
@@ -4,9 +4,6 @@ use anyhow::bail;
 
 use proxmox_schema::*;
 
-mod acme;
-pub use acme::*;
-
 // File names: may not contain slashes, may not start with "."
 pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
     if name.starts_with('.') {
diff --git a/src/config/acme/mod.rs b/src/config/acme/mod.rs
deleted file mode 100644
index 962cb1bb..00000000
--- a/src/config/acme/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod plugin;
diff --git a/src/config/acme/plugin.rs b/src/config/acme/plugin.rs
deleted file mode 100644
index e5a41f99..00000000
--- a/src/config/acme/plugin.rs
+++ /dev/null
@@ -1,105 +0,0 @@
-use anyhow::Error;
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-
-use pbs_api_types::PROXMOX_SAFE_ID_FORMAT;
-use proxmox_schema::{api, Schema, StringSchema, Updater};
-use proxmox_section_config::SectionConfigData;
-
-pub const PLUGIN_ID_SCHEMA: Schema = StringSchema::new("ACME Challenge Plugin ID.")
-    .format(&PROXMOX_SAFE_ID_FORMAT)
-    .min_length(1)
-    .max_length(32)
-    .schema();
-
-#[api(
-    properties: {
-        id: { schema: PLUGIN_ID_SCHEMA },
-        disable: {
-            optional: true,
-            default: false,
-        },
-        "validation-delay": {
-            default: 30,
-            optional: true,
-            minimum: 0,
-            maximum: 2 * 24 * 60 * 60,
-        },
-    },
-)]
-/// DNS ACME Challenge Plugin core data.
-#[derive(Deserialize, Serialize, Updater)]
-#[serde(rename_all = "kebab-case")]
-pub struct DnsPluginCore {
-    /// Plugin ID.
-    #[updater(skip)]
-    pub id: String,
-
-    /// DNS API Plugin Id.
-    pub api: String,
-
-    /// Extra delay in seconds to wait before requesting validation.
-    ///
-    /// Allows to cope with long TTL of DNS records.
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub validation_delay: Option<u32>,
-
-    /// Flag to disable the config.
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub disable: Option<bool>,
-}
-
-#[api(
-    properties: {
-        core: { type: DnsPluginCore },
-    },
-)]
-/// DNS ACME Challenge Plugin.
-#[derive(Deserialize, Serialize)]
-#[serde(rename_all = "kebab-case")]
-pub struct DnsPlugin {
-    #[serde(flatten)]
-    pub core: DnsPluginCore,
-
-    // We handle this property separately in the API calls.
-    /// DNS plugin data (base64url encoded without padding).
-    #[serde(with = "proxmox_serde::string_as_base64url_nopad")]
-    pub data: String,
-}
-
-impl DnsPlugin {
-    pub fn decode_data(&self, output: &mut Vec<u8>) -> Result<(), Error> {
-        Ok(proxmox_base64::url::decode_to_vec(&self.data, output)?)
-    }
-}
-
-pub struct PluginData {
-    data: SectionConfigData,
-}
-
-// And some convenience helpers.
-impl PluginData {
-    pub fn remove(&mut self, name: &str) -> Option<(String, Value)> {
-        self.data.sections.remove(name)
-    }
-
-    pub fn contains_key(&mut self, name: &str) -> bool {
-        self.data.sections.contains_key(name)
-    }
-
-    pub fn get(&self, name: &str) -> Option<&(String, Value)> {
-        self.data.sections.get(name)
-    }
-
-    pub fn get_mut(&mut self, name: &str) -> Option<&mut (String, Value)> {
-        self.data.sections.get_mut(name)
-    }
-
-    pub fn insert(&mut self, id: String, ty: String, plugin: Value) {
-        self.data.sections.insert(id, (ty, plugin));
-    }
-
-    pub fn iter(&self) -> impl Iterator<Item = (&String, &(String, Value))> + Send {
-        self.data.sections.iter()
-    }
-}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 19246742..f05af90d 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -15,7 +15,6 @@ use proxmox_lang::try_block;
 use pbs_api_types::{PamRealmConfig, PbsRealmConfig};
 use pbs_buildcfg::{self, configdir};
 
-pub mod acme;
 pub mod node;
 pub mod tfa;
 
diff --git a/src/lib.rs b/src/lib.rs
index 8633378c..828f5842 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -27,8 +27,6 @@ pub(crate) mod auth;
 
 pub mod tape;
 
-pub mod acme;
-
 pub mod client_helpers;
 
 pub mod traffic_control_cache;
-- 
2.47.3



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


      parent reply	other threads:[~2026-01-16 11:29 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-16 11:28 [pbs-devel] [PATCH proxmox{, -backup} v6 0/5] fix #6939: acme: support servers returning 204 for nonce requests Samuel Rufinatscha
2026-01-16 11:28 ` [pbs-devel] [PATCH proxmox v6 1/3] acme-api: add ACME completion helpers Samuel Rufinatscha
2026-01-16 11:28 ` [pbs-devel] [PATCH proxmox v6 2/3] acme: introduce http_status module Samuel Rufinatscha
2026-01-16 11:28 ` [pbs-devel] [PATCH proxmox v6 3/3] fix #6939: acme: support servers returning 204 for nonce requests Samuel Rufinatscha
2026-01-16 11:28 ` [pbs-devel] [PATCH proxmox-backup v6 1/2] acme: remove local AcmeClient and use proxmox-acme-api handlers Samuel Rufinatscha
2026-01-16 11:28 ` Samuel Rufinatscha [this message]

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=20260116112859.194016-6-s.rufinatscha@proxmox.com \
    --to=s.rufinatscha@proxmox.com \
    --cc=pbs-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