From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id D91EC782A8 for ; Thu, 29 Apr 2021 15:14:05 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id CCFE31D01B for ; Thu, 29 Apr 2021 15:13:35 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 0BF2A1CDC2 for ; Thu, 29 Apr 2021 15:13:29 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id D4E0E42E58 for ; Thu, 29 Apr 2021 15:13:28 +0200 (CEST) From: Wolfgang Bumiller To: pbs-devel@lists.proxmox.com Date: Thu, 29 Apr 2021 15:13:20 +0200 Message-Id: <20210429131322.24319-13-w.bumiller@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210429131322.24319-1-w.bumiller@proxmox.com> References: <20210429131322.24319-1-w.bumiller@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.025 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [plugin.rs, certificates.rs] Subject: [pbs-devel] [REBASED backup 12/14] acme: pipe plugin output to task log X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 29 Apr 2021 13:14:05 -0000 Signed-off-by: Wolfgang Bumiller --- src/api2/node/certificates.rs | 9 ++++-- src/config/acme/plugin.rs | 57 +++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/api2/node/certificates.rs b/src/api2/node/certificates.rs index 6ec9f52a..26cf4414 100644 --- a/src/api2/node/certificates.rs +++ b/src/api2/node/certificates.rs @@ -348,11 +348,16 @@ async fn order_certificate( })?; worker.log("Setting up validation plugin"); - let validation_url = plugin_cfg.setup(&mut acme, &auth, domain_config).await?; + let validation_url = plugin_cfg + .setup(&mut acme, &auth, domain_config, Arc::clone(&worker)) + .await?; let result = request_validation(&worker, &mut acme, auth_url, validation_url).await; - if let Err(err) = plugin_cfg.teardown(&mut acme, &auth, domain_config).await { + if let Err(err) = plugin_cfg + .teardown(&mut acme, &auth, domain_config, Arc::clone(&worker)) + .await + { worker.warn(format!( "Failed to teardown plugin '{}' for domain '{}' - {}", plugin_id, domain, err diff --git a/src/config/acme/plugin.rs b/src/config/acme/plugin.rs index 7c5a9b72..5a4851ee 100644 --- a/src/config/acme/plugin.rs +++ b/src/config/acme/plugin.rs @@ -8,7 +8,7 @@ use hyper::{Body, Request, Response}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use serde_json::Value; -use tokio::io::AsyncWriteExt; +use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWriteExt, BufReader}; use tokio::process::Command; use proxmox::api::{ @@ -24,6 +24,7 @@ use proxmox_acme_rs::{Authorization, Challenge}; use crate::acme::AcmeClient; use crate::api2::types::PROXMOX_SAFE_ID_FORMAT; use crate::config::acme::AcmeDomain; +use crate::server::WorkerTask; const ACME_PATH: &str = "/usr/share/proxmox-acme/proxmox-acme"; @@ -280,6 +281,7 @@ pub trait AcmePlugin { client: &'b mut AcmeClient, authorization: &'c Authorization, domain: &'d AcmeDomain, + task: Arc, ) -> Pin> + Send + 'fut>>; fn teardown<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>( @@ -287,6 +289,7 @@ pub trait AcmePlugin { client: &'b mut AcmeClient, authorization: &'c Authorization, domain: &'d AcmeDomain, + task: Arc, ) -> Pin> + Send + 'fut>>; } @@ -301,12 +304,29 @@ fn extract_challenge<'a>( .ok_or_else(|| format_err!("no supported challenge type (dns-01) found")) } +async fn pipe_to_tasklog( + pipe: T, + task: Arc, +) -> 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(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, action: &str, ) -> Result<&'a str, Error> { let challenge = extract_challenge(authorization, "dns-01")?; @@ -339,20 +359,33 @@ impl DnsPlugin { domain.alias.as_deref().unwrap_or(&domain.domain), ]); - let mut child = command.stdin(Stdio::piped()).spawn()?; + // 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()"); - match async move { + 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>(()) - } - .await - { - Ok(()) => (), + }; + match futures::try_join!(stdin, stdout, stderr) { + Ok(((), (), ())) => (), Err(err) => { if let Err(err) = child.kill().await { - eprintln!("failed to kill '{} {}' command: {}", ACME_PATH, action, err); + task.log(format!( + "failed to kill '{} {}' command: {}", + ACME_PATH, action, err + )); } bail!("'{}' failed: {}", ACME_PATH, err); } @@ -378,8 +411,9 @@ impl AcmePlugin for DnsPlugin { client: &'b mut AcmeClient, authorization: &'c Authorization, domain: &'d AcmeDomain, + task: Arc, ) -> Pin> + Send + 'fut>> { - Box::pin(self.action(client, authorization, domain, "setup")) + Box::pin(self.action(client, authorization, domain, task, "setup")) } fn teardown<'fut, 'a: 'fut, 'b: 'fut, 'c: 'fut, 'd: 'fut>( @@ -387,9 +421,10 @@ impl AcmePlugin for DnsPlugin { client: &'b mut AcmeClient, authorization: &'c Authorization, domain: &'d AcmeDomain, + task: Arc, ) -> Pin> + Send + 'fut>> { Box::pin(async move { - self.action(client, authorization, domain, "teardown") + self.action(client, authorization, domain, task, "teardown") .await .map(drop) }) @@ -441,6 +476,7 @@ impl AcmePlugin for StandaloneServer { client: &'b mut AcmeClient, authorization: &'c Authorization, _domain: &'d AcmeDomain, + _task: Arc, ) -> Pin> + Send + 'fut>> { use hyper::server::conn::AddrIncoming; use hyper::service::{make_service_fn, service_fn}; @@ -484,6 +520,7 @@ impl AcmePlugin for StandaloneServer { _client: &'b mut AcmeClient, _authorization: &'c Authorization, _domain: &'d AcmeDomain, + _task: Arc, ) -> Pin> + Send + 'fut>> { Box::pin(async move { if let Some(abort) = self.abort_handle.take() { -- 2.20.1