use serde::{Deserialize, Serialize};
use super::{error::MantaError, kafka::Kafka};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Auditor {
pub kafka: Kafka,
}
pub trait Audit {
#[allow(async_fn_in_trait)]
async fn produce_message(&self, data: &[u8]) -> Result<(), MantaError>;
}
async fn send_audit_message(kafka: &Kafka, msg_json: serde_json::Value) {
let msg_data = match serde_json::to_string(&msg_json) {
Ok(data) => data,
Err(e) => {
tracing::warn!("Failed serializing audit message: {}", e);
return;
}
};
if let Err(e) = kafka.produce_message(msg_data.as_bytes()).await {
tracing::warn!("Failed producing audit message: {}", e);
}
}
pub(crate) fn build_auth_audit_message(
outcome: &str,
username: &str,
source_ip: &str,
site: &str,
) -> serde_json::Value {
serde_json::json!({
"event": "auth_attempt",
"outcome": outcome,
"username": username,
"source_ip": source_ip,
"site": site,
})
}
pub async fn send_auth_audit(
kafka_opt: Option<&Kafka>,
outcome: &str,
username: &str,
source_ip: &str,
site: &str,
) {
let Some(kafka) = kafka_opt else { return };
send_audit_message(
kafka,
build_auth_audit_message(outcome, username, source_ip, site),
)
.await;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn auth_audit_has_expected_wire_shape() {
let msg = build_auth_audit_message("success", "alice", "10.0.0.1", "alps");
assert_eq!(msg["event"], "auth_attempt");
assert_eq!(msg["outcome"], "success");
assert_eq!(msg["username"], "alice");
assert_eq!(msg["source_ip"], "10.0.0.1");
assert_eq!(msg["site"], "alps");
}
#[test]
fn auth_audit_payload_has_no_password_field_by_construction() {
let msg = build_auth_audit_message("failure", "alice", "10.0.0.1", "alps");
let obj = msg.as_object().expect("payload is an object");
for forbidden in ["password", "passwd", "secret", "token"] {
assert!(
!obj.contains_key(forbidden),
"auth audit payload must not contain `{forbidden}`"
);
}
}
#[test]
fn auth_audit_handles_empty_strings_without_panicking() {
let msg = build_auth_audit_message("failure", "", "", "");
assert_eq!(msg["username"], "");
assert_eq!(msg["source_ip"], "");
assert_eq!(msg["site"], "");
assert_eq!(msg["event"], "auth_attempt");
}
}