117 lines
3.2 KiB
Rust
117 lines
3.2 KiB
Rust
use crate::policy::LeakPolicy;
|
|
use crate::report::{EnrichedEvent, LeakTransport, LeakType, Severity};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct LeakDecision {
|
|
pub leak_type: LeakType,
|
|
pub severity: Severity,
|
|
pub policy_rule_id: String,
|
|
}
|
|
|
|
pub fn evaluate(event: &EnrichedEvent, policy: &LeakPolicy) -> Option<LeakDecision> {
|
|
match event.transport {
|
|
LeakTransport::Udp53 | LeakTransport::Tcp53 => {
|
|
if is_proxy_required(event, policy) && !is_allowed(event, policy) {
|
|
return Some(LeakDecision {
|
|
leak_type: LeakType::B,
|
|
severity: Severity::P1,
|
|
policy_rule_id: "LEAK_B_PROXY_REQUIRED".to_string(),
|
|
});
|
|
}
|
|
if !is_allowed(event, policy) {
|
|
return Some(LeakDecision {
|
|
leak_type: LeakType::A,
|
|
severity: Severity::P0,
|
|
policy_rule_id: "LEAK_A_PLAINTEXT".to_string(),
|
|
});
|
|
}
|
|
}
|
|
LeakTransport::Dot | LeakTransport::Doh => {
|
|
if !is_allowed(event, policy) {
|
|
return Some(LeakDecision {
|
|
leak_type: LeakType::C,
|
|
severity: Severity::P1,
|
|
policy_rule_id: "LEAK_C_ENCRYPTED".to_string(),
|
|
});
|
|
}
|
|
}
|
|
LeakTransport::Unknown => {}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn is_allowed(event: &EnrichedEvent, policy: &LeakPolicy) -> bool {
|
|
let has_rules = !policy.allowed_ifaces.is_empty()
|
|
|| !policy.allowed_destinations.is_empty()
|
|
|| !policy.allowed_ports.is_empty()
|
|
|| !policy.allowed_processes.is_empty();
|
|
if !has_rules {
|
|
return false;
|
|
}
|
|
|
|
if let Some(iface) = event.iface_name.as_ref() {
|
|
if policy
|
|
.allowed_ifaces
|
|
.iter()
|
|
.any(|allowed| allowed.eq_ignore_ascii_case(iface))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if policy
|
|
.allowed_ports
|
|
.iter()
|
|
.any(|port| *port == event.dst_port)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if policy
|
|
.allowed_destinations
|
|
.iter()
|
|
.any(|net| net.contains(&event.dst_ip))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if let Some(name) = event
|
|
.owner
|
|
.as_ref()
|
|
.and_then(|owner| owner.process_name.as_ref())
|
|
{
|
|
if policy
|
|
.allowed_processes
|
|
.iter()
|
|
.any(|value| value.eq_ignore_ascii_case(name))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn is_proxy_required(event: &EnrichedEvent, policy: &LeakPolicy) -> bool {
|
|
let Some(qname) = event.qname.as_ref() else {
|
|
return false;
|
|
};
|
|
let qname = qname.to_ascii_lowercase();
|
|
if policy.proxy_required_domains.iter().any(|domain| {
|
|
let domain = domain.to_ascii_lowercase();
|
|
qname == domain || qname.ends_with(&format!(".{domain}"))
|
|
}) {
|
|
return true;
|
|
}
|
|
|
|
if !policy.allowlist_domains.is_empty() {
|
|
let allowed = policy.allowlist_domains.iter().any(|domain| {
|
|
let domain = domain.to_ascii_lowercase();
|
|
qname == domain || qname.ends_with(&format!(".{domain}"))
|
|
});
|
|
return !allowed;
|
|
}
|
|
|
|
false
|
|
}
|