use super::{Host, HttpExpect, HttpRequest, Position, Probe, Resolved, TcpExchange, TcpExpect, Url, can_run_checks};
use isoloom_spec::model::{Machine, Port, Spec};
#[must_use]
pub fn allowed(spec: &Spec, from: &Machine, network: &str, port: u16) -> bool {
from.networks.contains_key(network)
|| spec
.reach
.iter()
.any(|r| from.networks.contains_key(&r.from) && r.to == network && (r.ports.is_empty() || r.ports.contains(&port)))
}
pub fn derived(spec: &Spec) -> Vec<Resolved> {
if !spec.machines.values().any(can_run_checks) {
return from_networks(spec);
}
let mut out = Vec::new();
for (a, ma) in &spec.machines {
if !can_run_checks(ma) {
continue;
}
let position = Position::Machine(a.clone());
for (b, mb) in &spec.machines {
if a == b {
continue;
}
for svc in mb.ports.iter().filter(|s| s.protocol.is_tcp()) {
out.extend(service_from(spec, &position, (a, ma), (b, mb), svc));
}
}
if !ma.networks.keys().any(|n| spec.networks.get(n).is_some_and(|net| net.internet)) {
let probe = Probe::Http {
url: Url {
https: false,
host: Host::Literal("1.1.1.1".into()),
port: None,
path: "/".into(),
},
expect: HttpExpect::Blocked,
request: HttpRequest::default(),
};
out.push(derived_check(format!("no internet from {a}"), position, probe, 0));
}
}
out
}
fn service_from(spec: &Spec, position: &Position, (a, ma): (&str, &Machine), (b, mb): (&str, &Machine), svc: &Port) -> Vec<Resolved> {
let paths: Vec<(&String, bool)> = mb.networks.keys().map(|n| (n, allowed(spec, ma, n, svc.port))).collect();
if paths.iter().all(|(_, ok)| !ok) {
return paths
.iter()
.flat_map(|(n, _)| hosts(spec, b, mb, n))
.map(|(host, at)| {
let probe = Probe::Tcp {
host,
port: svc.port,
expect: TcpExpect::Blocked,
exchange: TcpExchange::default(),
};
derived_check(format!("{b}:{}{at} blocked from {a}", svc.port), position.clone(), probe, 0)
})
.collect();
}
if svc.dormant {
return Vec::new();
}
paths
.iter()
.filter(|(_, ok)| *ok)
.flat_map(|(n, _)| hosts(spec, b, mb, n))
.map(|(host, at)| derived_check(format!("{b}:{}{at} from {a}", svc.port), position.clone(), answers(host, svc), 30))
.collect()
}
fn hosts(spec: &Spec, b: &str, mb: &Machine, n: &str) -> Vec<(Host, String)> {
let primary = Host::Machine {
name: b.to_owned(),
network: n.to_owned(),
};
let more = crate::more_addresses::of(spec, mb).swap_remove(n).unwrap_or_default();
std::iter::once((primary, on_network(mb, n)))
.chain(more.into_iter().map(|a| (Host::Literal(a.to_string()), format!(" at {a}"))))
.collect()
}
fn on_network(m: &Machine, n: &str) -> String {
if m.networks.len() > 1 { format!(" on {n}") } else { String::new() }
}
fn derived_check(name: String, position: Position, probe: Probe, wait: u32) -> Resolved {
Resolved {
name,
position,
probe,
wait,
derived: true,
}
}
#[must_use]
pub fn from_networks(spec: &Spec) -> Vec<Resolved> {
let mut out = Vec::new();
for (b, mb) in &spec.machines {
for svc in mb.ports.iter().filter(|s| !s.dormant && s.protocol.is_tcp()) {
for (host, at) in mb.networks.keys().flat_map(|n| hosts(spec, b, mb, n)) {
let name = format!("{b}:{}{at} from the networks", svc.port);
out.push(derived_check(name, Position::Networks, answers(host, svc), 30));
}
}
}
out
}
fn answers(host: Host, svc: &Port) -> Probe {
if svc.http {
Probe::Http {
url: Url {
https: svc.tls,
host,
port: Some(svc.port),
path: "/".into(),
},
expect: HttpExpect::Any,
request: HttpRequest::default(),
}
} else {
Probe::Tcp {
host,
port: svc.port,
expect: TcpExpect::Open,
exchange: TcpExchange::default(),
}
}
}
#[cfg(test)]
mod tests;