This commit is contained in:
Mateusz Gruszczyński committed 2026-10-07 13:40:21 +02:00
1 parent 3a2eb4fe79
commit e0194fbaff
63 files changed
+1553 -379

No files matched your search

+6 -85
View File
@@ -6,13 +6,8 @@ use base64::Engine as _;
use http::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
path::PathBuf,
process::Stdio,
sync::{Arc, Mutex},
time::Duration,
};
use tokio::{io::AsyncWriteExt, process::Command, sync::Semaphore};
use std::sync::{Arc, Mutex};
use tokio::sync::Semaphore;
use zeroize::{Zeroize, Zeroizing};
#[derive(Debug, Clone, Serialize)]
@@ -229,6 +224,8 @@ fn provider_credential_bytes(
pub struct Manager {
slot: Arc<Semaphore>,
runtime_slot: Arc<Semaphore>,
runtime: Mutex<RuntimeStatus>,
job: Mutex<Option<Job>>,
renewals: Mutex<std::collections::BTreeMap<String, RenewalState>>,
}
@@ -236,6 +233,8 @@ impl Manager {
pub fn new() -> Arc<Self> {
Arc::new(Self {
slot: Arc::new(Semaphore::new(1)),
runtime_slot: Arc::new(Semaphore::new(1)),
runtime: Mutex::new(RuntimeStatus::default()),
job: Mutex::new(None),
renewals: Mutex::new(Default::default()),
})
@@ -418,81 +417,3 @@ struct Output {
cert_file: String,
key_file: String,
}
/// Kill the entire private process group if execution is cancelled/abandoned.
/// Certbot inherits the group, so it cannot outlive a stopped RustProxy worker.
struct ProcessGroup(Option<u32>);
impl Drop for ProcessGroup {
fn drop(&mut self) {
#[cfg(unix)]
if let Some(id) = self.0 {
// SAFETY: this is the process group created solely for this child.
unsafe {
libc::kill(-(id as i32), libc::SIGKILL);
}
}
}
}
async fn worker(state: &App, directory: PathBuf, raw: Zeroizing<String>) -> Result<Value, String> {
let mut command = Command::new("python3");
command
.arg("-I")
.arg("-c")
.arg(include_str!("../../scripts/acme_provider.py"))
.arg("--cert-dir")
.arg(&directory)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.kill_on_drop(true);
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
command.as_std_mut().process_group(0);
}
let mut child = command
.spawn()
.map_err(|_| "acme.python_not_available".to_owned())?;
let mut group = ProcessGroup(child.id());
let stdin = child
.stdin
.take()
.ok_or_else(|| "acme.worker_io".to_owned())?;
let work = async move {
let mut stdin = stdin;
stdin
.write_all(raw.as_bytes())
.await
.map_err(|_| "acme.worker_io".to_owned())?;
stdin
.shutdown()
.await
.map_err(|_| "acme.worker_io".to_owned())?;
drop(stdin);
drop(raw);
child
.wait_with_output()
.await
.map_err(|_| "acme.worker_io".to_owned())
};
let output = tokio::select! {
_ = state.shutdown.cancelled() => return Err("acme.cancelled".into()),
result = tokio::time::timeout(Duration::from_secs(2160), work) => result.map_err(|_| "acme.provider_timeout".to_owned())??,
};
group.0 = None; // the process has been reaped, never kill a reused PID
if output.stdout.len() > 16384 {
return Err("acme.invalid_worker_response".into());
}
let value: Value = serde_json::from_slice(&output.stdout)
.map_err(|_| "acme.invalid_worker_response".to_owned())?;
if !output.status.success() {
let reason = value.get("error").and_then(Value::as_str).filter(|s| {
s.starts_with("acme.")
&& s.len() < 96
&& s.bytes()
.all(|c| c.is_ascii_lowercase() || c == b'_' || c == b'.')
});
return Err(reason.unwrap_or("acme.worker_failed").to_owned());
}
Ok(value)
}