use std::net::IpAddr;
use tracing::warn;
pub fn notify_ban(url: &str, ip: IpAddr, jail: &str, ban_time: i64) {
let payload = serde_json::json!({
"event": "ban",
"ip": ip.to_string(),
"jail": jail,
"ban_time": ban_time,
"timestamp": chrono::Utc::now().to_rfc3339(),
});
let body = match serde_json::to_string(&payload) {
Ok(s) => s,
Err(e) => {
warn!(error = %e, "webhook: failed to serialize payload");
return;
}
};
let url = url.to_string();
tokio::spawn(async move {
let result = tokio::process::Command::new("curl")
.args([
"-s",
"-X",
"POST",
"-H",
"Content-Type: application/json",
"-m",
"10",
"-d",
&body,
&url,
])
.output()
.await;
match result {
Ok(output) if !output.status.success() => {
let stderr = String::from_utf8_lossy(&output.stderr);
warn!(url = %url, stderr = %stderr, "webhook POST failed");
}
Err(e) => {
warn!(url = %url, error = %e, "webhook: curl not available");
}
_ => {}
}
});
}
pub fn notify_unban(url: &str, ip: IpAddr, jail: &str) {
let payload = serde_json::json!({
"event": "unban",
"ip": ip.to_string(),
"jail": jail,
"timestamp": chrono::Utc::now().to_rfc3339(),
});
let body = match serde_json::to_string(&payload) {
Ok(s) => s,
Err(e) => {
warn!(error = %e, "webhook: failed to serialize payload");
return;
}
};
let url = url.to_string();
tokio::spawn(async move {
let result = tokio::process::Command::new("curl")
.args([
"-s",
"-X",
"POST",
"-H",
"Content-Type: application/json",
"-m",
"10",
"-d",
&body,
&url,
])
.output()
.await;
if let Err(e) = result {
warn!(url = %url, error = %e, "webhook: curl not available");
}
});
}
#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr};
#[tokio::test]
async fn notify_ban_does_not_panic() {
crate::webhook::notify_ban(
"http://127.0.0.1:1/test",
IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
"sshd",
3600,
);
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
#[tokio::test]
async fn notify_unban_does_not_panic() {
crate::webhook::notify_unban(
"http://127.0.0.1:1/test",
IpAddr::V4(Ipv4Addr::new(5, 6, 7, 8)),
"nginx",
);
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
}