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);
}
}