#![cfg(all(feature = "daemon", unix))]
mod common;
use std::io::{BufRead, BufReader, Read, Write};
use std::os::unix::net::UnixStream;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use common::{SIMPLE_RULE, rsigma_bin, temp_file};
struct Daemon(std::process::Child);
impl Drop for Daemon {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
fn uds_get(path: &std::path::Path, route: &str) -> std::io::Result<(u16, String)> {
let mut stream = UnixStream::connect(path)?;
stream.set_read_timeout(Some(Duration::from_secs(2)))?;
write!(
stream,
"GET {route} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
)?;
stream.flush()?;
let mut raw = Vec::new();
stream.read_to_end(&mut raw)?;
let text = String::from_utf8_lossy(&raw);
let status = text
.split_whitespace()
.nth(1)
.and_then(|s| s.parse::<u16>().ok())
.unwrap_or(0);
let body = text.split("\r\n\r\n").nth(1).unwrap_or("").to_string();
Ok((status, body))
}
#[test]
fn api_listener_serves_healthz_over_unix_socket() {
let rule = temp_file(".yml", SIMPLE_RULE);
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("api.sock");
let child = Command::new(rsigma_bin())
.args([
"engine",
"daemon",
"-r",
rule.path().to_str().unwrap(),
"--input",
"http",
"--api-addr",
&format!("unix://{}", sock.display()),
])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("failed to spawn daemon");
let _daemon = Daemon(child);
let deadline = Instant::now() + Duration::from_secs(10);
loop {
if let Ok((200, body)) = uds_get(&sock, "/healthz") {
let v: serde_json::Value = serde_json::from_str(&body).unwrap();
assert_eq!(v["status"], "ok");
return;
}
if Instant::now() >= deadline {
panic!("daemon /healthz never answered over the unix socket within 10s");
}
std::thread::sleep(Duration::from_millis(50));
}
}
#[cfg(feature = "daemon-tls")]
#[test]
fn tls_cert_with_unix_api_addr_is_rejected() {
let rule = temp_file(".yml", SIMPLE_RULE);
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("api.sock");
let mut child = Command::new(rsigma_bin())
.args([
"engine",
"daemon",
"-r",
rule.path().to_str().unwrap(),
"--input",
"http",
"--api-addr",
&format!("unix://{}", sock.display()),
"--tls-cert",
"/nonexistent/cert.pem",
"--tls-key",
"/nonexistent/key.pem",
])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn daemon");
let stderr = child.stderr.take().unwrap();
let mut message = String::new();
for line in BufReader::new(stderr).lines() {
let Ok(line) = line else { break };
if line.contains("unix://") {
message = line;
break;
}
}
let _ = child.kill();
let _ = child.wait();
assert!(
message.contains("cannot be combined with a unix://"),
"expected a TLS-over-UDS rejection, got: {message:?}"
);
}