Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

server/lib/tests/every_refusal_leaves_an_audit_row.rs

//! ★ **Every socket asks [`egress::check`] first, and every refusal leaves an audit row**
//! (lane HOL-NCSC, NCSC P1 item 5).
//!
//! * a dial `network: Offline` refuses is written to the audit log (`refusals`), with its code;
//! * an offline Holger exports its audit log to local sinks only (`local_only` is implied), and a
//!   SIEM socket to a local collector is the one dial an offline box still makes
//!   (`egress::check_site_local`);
//! * the off-site copy's S3 store is a dial like any other;
//! * a source guard: every raw socket in server-lib and server-cli (TCP, UDP, a WireGuard
//!   dataplane) is preceded in its function by an `egress::check`.
//!
//! Its own test binary: `egress::set_offline` is one-way and process-wide.

use std::sync::Arc;

use holger_server_lib::audit::{siem::SiemSettings, MemoryAuditLog};
use holger_server_lib::{egress, offsite::Remote as _, refusals};

fn siem(target: &str) -> SiemSettings {
    ron::from_str(&format!(
        r#"(sinks: [(name: "soc", enabled: true, format: Syslog, target: {target})])"#
    ))
    .unwrap()
}

#[test]
fn an_offline_holgers_refusals_are_audited_and_its_siem_stays_on_site() {
    let log = Arc::new(MemoryAuditLog::new());
    refusals::set_sink(log.clone());
    egress::set_offline();

    // A refused dial leaves its row, with the code.
    let e = egress::check("pull-through", "https://pypi.org/simple/").unwrap_err();
    assert!(e.to_string().contains("[holger-offline-1]"), "{e}");
    let rows = log.events();
    let row = rows
        .iter()
        .find(|r| r.detail.contains("pull-through"))
        .unwrap_or_else(|| panic!("no audit row for the refused dial: {rows:?}"));
    assert!(
        row.detail.contains("[holger-offline-1]") && row.detail.contains("pypi.org"),
        "{row:?}"
    );

    // Offline implies local_only: a public collector is refused, a local one kept.
    let e = siem(r#"Tcp("203.0.113.5:514")"#)
        .enabled_sinks()
        .unwrap_err();
    assert!(e.contains("local sinks only"), "{e}");
    siem(r#"Tcp("10.0.0.5:514")"#)
        .enabled_sinks()
        .expect("a local collector");
    // The SIEM's own socket: on site yes, off site no.
    egress::check_site_local("siem", "10.0.0.5:514").expect("a local collector is dialled");
    egress::check_site_local("siem", "127.0.0.1:514").expect("loopback");
    assert!(
        egress::check_site_local("siem", "203.0.113.5:514").is_err(),
        "an off-site collector is refused"
    );
    assert!(
        egress::check_site_local("siem", "siem.example:514").is_err(),
        "a name is not on site"
    );

    // The off-site copy dials its S3 store through the same check.
    let s3 = holger_server_lib::offsite::S3Remote::new(
        "https://r2.example",
        "bucket",
        "auto",
        "ak",
        "sk",
    );
    let e = s3.put("k", b"v").unwrap_err();
    assert!(e.contains("[holger-offline-1]"), "{e}");
}

/// Pure: the function around line `at` (from its `fn` line).
fn enclosing_fn(lines: &[&str], at: usize) -> String {
    let start = (0..=at).rev().find(|i| {
        let t = lines[*i].trim_start();
        t.starts_with("fn ")
            || t.starts_with("pub fn ")
            || t.starts_with("async fn ")
            || t.starts_with("pub async fn ")
            || t.starts_with("pub(crate) fn ")
            || t.contains(" fn ")
    });
    lines[start.unwrap_or(0)..=at].join("\n")
}

/// The sockets that are not egress, each with why.
const NOT_EGRESS: &[(&str, &str, &str)] = &[
    (
        "server/cli/src/paired.rs",
        "fn doors_answer",
        "the box's own doors, on this machine",
    ),
    (
        "server/cli/src/passkey_cli.rs",
        "fn post",
        "a CLI verb dialling the door the operator named, not the server's egress",
    ),
];

#[test]
fn every_socket_asks_egress_first() {
    let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
    let mut files = Vec::new();
    for dir in ["server/lib/src", "server/cli/src"] {
        let mut stack = vec![root.join(dir)];
        while let Some(d) = stack.pop() {
            for e in std::fs::read_dir(&d).unwrap().flatten() {
                let p = e.path();
                if p.is_dir() {
                    stack.push(p);
                } else if p.extension().is_some_and(|x| x == "rs")
                    && !p.ends_with("tests.rs")
                    && !p.to_string_lossy().contains("/generated/")
                {
                    files.push(p);
                }
            }
        }
    }
    let mut bare = Vec::new();
    for f in &files {
        let text = std::fs::read_to_string(f).unwrap();
        let text = match text.find("#[cfg(test)]\nmod tests") {
            Some(i) => &text[..i],
            None => &text[..],
        };
        let lines: Vec<&str> = text.lines().collect();
        let rel = f
            .strip_prefix(&root)
            .unwrap_or(f)
            .to_string_lossy()
            .replace("../", "");
        for (i, l) in lines.iter().enumerate() {
            let code = l.split("//").next().unwrap_or_default();
            if !["TcpStream::connect", "UdpSocket::bind", "dataplane::spawn"]
                .iter()
                .any(|p| code.contains(p))
            {
                continue;
            }
            let body = enclosing_fn(&lines, i);
            if body.contains("egress::check") {
                continue;
            }
            if NOT_EGRESS
                .iter()
                .any(|(file, func, _)| rel.ends_with(file) && body.contains(func))
            {
                continue;
            }
            bare.push(format!("{rel}:{}: {}", i + 1, l.trim()));
        }
    }
    assert!(
        bare.is_empty(),
        "a socket that does not ask egress::check first:\n{}",
        bare.join("\n")
    );
}