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

nordisk/holger

server/downstream/src/lib.rs

//! # `holger-downstream`: the on-prem gateway's rules (Holger v2.0 H3)
//!
//! A downstream Holger (`cargo install holger --no-default-features --features downstream`)
//! sits inside the air-gapped site and serves its build systems. Its packages come from ONE
//! upstream Holger: a signed znippy bundle carried in on a USB stick, or the same signed
//! bundles pulled from that upstream over mutual TLS (a `mirror:` block with one
//! `ron_kind: Holger` source). It verifies and serves; it never scans, never fetches new
//! trust, and never dials anything but that upstream.
//!
//! This crate is the decision, kept pure so it can be tested without a server: from the
//! facts a config states ([`Facts`]) it either gives the [`Plan`] (the one upstream origin
//! the box may dial, or none) or refuses the config by name
//! (`holger-downstream-1`, [`Refusal`]). The server asks [`Plan::may_dial`] before every
//! outbound connection (`server/lib/src/egress.rs`, `holger-downstream-2`).

use holger_errcode::DOWNSTREAM_CONFIG_REFUSED;

/// How each downstream reaches its cloud Holger: `via: Front` (default) or
/// `via: Wireguard(key: ..)` (lane HOL-WGDS).
pub mod via;

/// The front's report to main: its doors, the names it serves and where each goes, with a
/// proof of its own (lane HOL-FLOW-FRONT).
pub mod front;

/// What a mirror source speaks (`mirror.ron_sources[].ron_kind`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MirrorKind {
    /// Another Holger: the only upstream a downstream takes.
    Holger,
    /// Anything else (Nexus, Artifactory): never an upstream of a downstream.
    Other,
}

/// One `mirror.ron_sources` entry, as far as the rules care.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MirrorFact {
    pub name: String,
    pub kind: MirrorKind,
    /// `https://upstream.example:50051`.
    pub endpoint: String,
}

/// The facts of one config that decide whether it is a downstream's.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Facts {
    /// Repositories of a `*-remote` type (they fetch from a registry): `(name, type)`.
    pub remote_repositories: Vec<(String, String)>,
    /// The `mirror:` block's sources.
    pub mirror_sources: Vec<MirrorFact>,
    /// `pull_through` sources that name a `url` (they fetch on a miss): `(name, url)`.
    pub fetching_sources: Vec<(String, String)>,
    /// The box is declared a twin (it would follow a primary).
    pub twin: bool,
    /// `recharge_from:` (it would pull a whole store from a twin).
    pub recharge_from: Option<String>,
    /// `intel: (offline: false)` (it would fetch the OSV/NVD/KEV feeds).
    pub intel_online: bool,
    /// `promotion.intake.scan` or Tier 1 scanner steps (a downstream runs no scanner).
    pub scans: bool,
}

/// What a downstream may dial: its upstream's origin, or nothing at all.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Plan {
    /// `(mirror source name, origin)`; `None` = a USB-only downstream with zero egress.
    pub upstream: Option<(String, String)>,
}

impl Plan {
    /// The origins this box may dial (none, or its upstream's).
    pub fn origins(&self) -> Vec<String> {
        self.upstream.iter().map(|(_, o)| o.clone()).collect()
    }

    /// ★ **May this box dial `target`?** Only its upstream's origin (scheme, host and port
    /// exactly as configured); everything else is refused.
    pub fn may_dial(&self, target: &str) -> bool {
        match (&self.upstream, origin_of(target)) {
            (Some((_, o)), Some(t)) => *o == t,
            _ => false,
        }
    }

    /// One line for the boot log.
    pub fn summary(&self) -> String {
        match &self.upstream {
            Some((name, origin)) => format!(
                "downstream: zero egress except its upstream Holger '{name}' at {origin} (signed bundles pulled over that line, or carried in)"
            ),
            None => "downstream: zero egress; packages arrive as signed bundles only (USB, `bundle import`, `bundle.inbox`)".to_string(),
        }
    }
}

/// A config a downstream refuses (`holger-downstream-1`), with every reason.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Refusal {
    pub reasons: Vec<String>,
}

impl std::fmt::Display for Refusal {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            DOWNSTREAM_CONFIG_REFUSED.tag(format_args!(
                "this Holger is a downstream: it takes packages from ONE upstream Holger and dials nothing else; {}",
                self.reasons.join("; ")
            ))
        )
    }
}

impl std::error::Error for Refusal {}

/// ★ **The plan of a downstream, or the refusal of its config.** Every reason is named, not
/// only the first, so one boot tells the operator everything to change.
pub fn plan(f: &Facts) -> Result<Plan, Refusal> {
    let mut reasons = Vec::new();
    for (name, ty) in &f.remote_repositories {
        reasons.push(format!(
            "repository '{name}' is a '{ty}' upstream, which fetches from a registry"
        ));
    }
    for (name, url) in &f.fetching_sources {
        reasons.push(format!(
            "`pull_through` source '{name}' fetches from {url}; a downstream's source names no `url` and serves what its upstream sent"
        ));
    }
    if f.twin {
        reasons.push("a twin follows its primary over the network".into());
    }
    if let Some(from) = &f.recharge_from {
        reasons.push(format!("`recharge_from: {from}` pulls a store from a twin"));
    }
    if f.intel_online {
        reasons.push("`intel: (offline: false)` fetches advisory feeds; a downstream runs no scanner and reads no intel".into());
    }
    if f.scans {
        reasons.push("it declares scanners; a downstream trusts its upstream's signed scan evidence and runs none".into());
    }
    let mut upstream = None;
    match f.mirror_sources.as_slice() {
        [] => {}
        [m] if m.kind == MirrorKind::Holger => match origin_of(&m.endpoint) {
            Some(o) => upstream = Some((m.name.clone(), o)),
            None => reasons.push(format!(
                "mirror source '{}' names no http(s) origin ({:?})",
                m.name, m.endpoint
            )),
        },
        [m] => reasons.push(format!(
            "mirror source '{}' is not a Holger; a downstream's one upstream is a Holger",
            m.name
        )),
        many => reasons.push(format!(
            "the `mirror:` block names {} sources ({}); a downstream has ONE upstream",
            many.len(),
            many.iter()
                .map(|m| m.name.as_str())
                .collect::<Vec<_>>()
                .join(", ")
        )),
    }
    if reasons.is_empty() {
        Ok(Plan { upstream })
    } else {
        Err(Refusal { reasons })
    }
}

/// Pure: **the origin of an endpoint**, `scheme://host:port` in lower case with the
/// scheme's default port written out; `None` for anything that is not http(s).
pub fn origin_of(endpoint: &str) -> Option<String> {
    let e = endpoint.trim();
    let (scheme, rest) = e.split_once("://")?;
    let scheme = scheme.to_ascii_lowercase();
    let default_port = match scheme.as_str() {
        "http" => 80,
        "https" => 443,
        _ => return None,
    };
    let authority = rest.split(['/', '?', '#']).next()?;
    let authority = authority.rsplit('@').next()?;
    if authority.is_empty() {
        return None;
    }
    let (host, port) = if let Some(v6) = authority.strip_prefix('[') {
        let (h, tail) = v6.split_once(']')?;
        let port = match tail.strip_prefix(':') {
            Some(p) => p.parse::<u16>().ok()?,
            None if tail.is_empty() => default_port,
            None => return None,
        };
        (format!("[{}]", h.to_ascii_lowercase()), port)
    } else {
        match authority.rsplit_once(':') {
            Some((h, p)) => (h.to_ascii_lowercase(), p.parse::<u16>().ok()?),
            None => (authority.to_ascii_lowercase(), default_port),
        }
    };
    if host.is_empty() {
        return None;
    }
    Some(format!("{scheme}://{host}:{port}"))
}

#[cfg(test)]
mod tests {
    use super::*;

    fn holger(name: &str, endpoint: &str) -> MirrorFact {
        MirrorFact {
            name: name.into(),
            kind: MirrorKind::Holger,
            endpoint: endpoint.into(),
        }
    }

    #[test]
    fn a_usb_only_downstream_dials_nothing() {
        let p = plan(&Facts::default()).unwrap();
        assert_eq!(p.upstream, None);
        assert!(p.origins().is_empty());
        assert!(!p.may_dial("https://pypi.org/simple/"));
        assert!(p.summary().contains("zero egress"));
    }

    #[test]
    fn one_upstream_holger_is_the_only_line_out() {
        let p = plan(&Facts {
            mirror_sources: vec![holger("cloud", "https://Up.Example:50051")],
            ..Default::default()
        })
        .unwrap();
        assert_eq!(p.origins(), vec!["https://up.example:50051".to_string()]);
        assert!(p.may_dial("https://up.example:50051"));
        assert!(p.may_dial("https://up.example:50051/holger.v1.RepositoryService/ListArtifacts"));
        assert!(
            !p.may_dial("https://up.example:50052"),
            "another port is another line"
        );
        assert!(
            !p.may_dial("http://up.example:50051"),
            "another scheme is another line"
        );
        assert!(!p.may_dial("https://index.crates.io/config.json"));
        assert!(!p.may_dial("not a url"));
    }

    #[test]
    fn every_reason_is_named_and_coded() {
        let f = Facts {
            remote_repositories: vec![("crates".into(), "rust-remote".into())],
            mirror_sources: vec![holger("a", "https://a:1"), holger("b", "https://b:1")],
            fetching_sources: vec![("pypi".into(), "https://pypi.org/simple/".into())],
            twin: true,
            recharge_from: Some("twin".into()),
            intel_online: true,
            scans: true,
        };
        let e = plan(&f).unwrap_err();
        assert_eq!(e.reasons.len(), 7, "{e}");
        let s = e.to_string();
        assert!(s.contains("[holger-downstream-1]"), "{s}");
        for want in [
            "'crates'",
            "pypi.org",
            "twin",
            "recharge_from",
            "intel",
            "scanners",
            "ONE upstream",
        ] {
            assert!(s.contains(want), "{want} not named in: {s}");
        }
    }

    #[test]
    fn a_nexus_is_never_a_downstreams_upstream() {
        let f = Facts {
            mirror_sources: vec![MirrorFact {
                name: "nx".into(),
                kind: MirrorKind::Other,
                endpoint: "https://nexus:8081".into(),
            }],
            ..Default::default()
        };
        assert!(plan(&f).unwrap_err().to_string().contains("not a Holger"));
    }

    #[test]
    fn origins_are_normalised() {
        assert_eq!(origin_of("https://h").as_deref(), Some("https://h:443"));
        assert_eq!(
            origin_of("http://user@H:8080/x?y").as_deref(),
            Some("http://h:8080")
        );
        assert_eq!(
            origin_of("https://[::1]:50051").as_deref(),
            Some("https://[::1]:50051")
        );
        assert_eq!(
            origin_of("https://[::1]").as_deref(),
            Some("https://[::1]:443")
        );
        assert_eq!(origin_of("grpc://h:1"), None);
        assert_eq!(origin_of("https://:1"), None);
        assert_eq!(origin_of("h:1"), None);
    }
}