server/lib/src/exposed/mod.rs
//! Exposed endpoints: the listener side of the wiring graph.
//!
//! An [`ExposedEndpoint`] is a RON-configured serving surface — a gRPC address,
//! optionally a twin HTTP/OCI address (`ron_http_url`) fronting the same repos
//! for ecosystem clients (helm/pip/cargo, OCI registry) plus `/healthz`+`/readyz`.
//! One `ron_tls` covers BOTH ports; absent = cleartext, dev/loopback only.
//! Submodules: `http` (the gateway), `tls` (settings/loading), `fast_routes`
//! (per-endpoint aggregated repo lookup, attached last in the wiring pass).
//!
//! Gotcha: `wired_{in,out}_repositories` hold raw `*const Repository` back-refs
//! and are `#[serde(skip)]` — they are populated by the wiring engine after the
//! graph is indexed, so they are dangling/empty on a freshly deserialized config
//! and must never outlive the `Repository` arena that backs them.
use crate::repository::Repository;
use fast_routes::FastRoutes;
use serde::{Deserialize, Serialize};
pub mod fast_routes;
pub mod http;
pub mod tls;
/// An exposed gRPC endpoint configured in RON
#[derive(Serialize, Deserialize)]
pub struct ExposedEndpoint {
pub ron_name: String,
pub ron_url: String, // e.g. "0.0.0.0:50051"
/// Optional HTTP/OCI listener (e.g. "0.0.0.0:8443") serving the same
/// repositories to ecosystem clients (helm/pip/cargo, OCI registry) plus
/// /healthz and /readyz for Kubernetes probes.
#[serde(default)]
pub ron_http_url: Option<String>,
/// Optional TLS for this endpoint. Applies to BOTH the gRPC port and the
/// HTTP/OCI port. Absent = cleartext (loopback/dev only; logged loudly).
#[serde(default)]
pub ron_tls: Option<crate::exposed::tls::TlsSettings>,
/// Max accepted request-body size for the HTTP/OCI gateway, in bytes.
/// Guards against memory-exhaustion uploads. Default 1 GiB.
#[serde(default)]
pub ron_max_body_bytes: Option<usize>,
/// **Serve this endpoint in clear text off loopback** (lane HOL-NCSC). Default `false`:
/// a listener that is not loopback needs `ron_tls`, or the start is refused
/// (`holger-listen-2`). Set it only where something else encrypts the wire (a WireGuard
/// overlay, a TLS front on the same host or link) and say which in a comment.
#[serde(default)]
pub insecure: bool,
/// **The admin plane's own gRPC door** (lane HOL-NCSC, NCSC P2). Set, AdminService,
/// PromotionService, AuditService, TokenService, the IdentityService, the jobs, admission
/// and migration services and the thin client's doors are served HERE only; `ron_url`
/// keeps the package services (`crate::listen::Plane`). Absent: one listener serves all.
#[serde(default)]
pub ron_admin_url: Option<String>,
/// **The admin plane's HTTP door**: the `/-/auth`, `/-/bundle`, `/-/intel`, `/-/intake`,
/// `/-/audit`, `/-/flow`, `/-/twin`, `/-/sandbox` and sync administration doors
/// (`crate::listen::ADMIN_HTTP_PREFIXES`), here only; `ron_http_url` keeps the packages.
#[serde(default)]
pub ron_admin_http_url: Option<String>,
/// **Who may connect to the admin doors**: CIDRs, required with either admin door
/// (`holger-listen-5`); any other peer is dropped and audited (`holger-listen-4`).
#[serde(default)]
pub ron_admin_allow: Vec<String>,
/// **Who may say what this endpoint's public face is** (HOL-DOCFIND a): CIDRs of the TLS
/// fronts that end TLS and dial this endpoint's HTTP door. Only from a peer in this list
/// are `X-Forwarded-Proto` and `X-Forwarded-Host` believed when Holger writes a link (an
/// npm packument's tarballs, a crates source's `dl`). Empty (the default): nobody, and a
/// link carries the door's own scheme (`https` with `ron_tls`) and the request's host.
#[serde(default)]
pub ron_trusted_proxies: Vec<String>,
#[serde(skip_serializing, skip_deserializing, default)]
pub aggregated_routes: Option<FastRoutes>,
#[serde(skip_serializing, skip_deserializing, default)]
pub wired_in_repositories: Vec<*const Repository>,
#[serde(skip_serializing, skip_deserializing, default)]
pub wired_out_repositories: Vec<*const Repository>,
}
impl ExposedEndpoint {
/// The parsed `ron_trusted_proxies`: `None` when empty, a refusal naming the entry when
/// one is not a CIDR (`holger-listen-6` at the start).
pub fn trusted_proxies(&self) -> Result<Option<crate::listen::AllowList>, String> {
if self.ron_trusted_proxies.is_empty() {
return Ok(None);
}
crate::listen::AllowList::parse_named(&self.ron_trusted_proxies, "ron_trusted_proxies")
.map(Some)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn endpoint(trusted: &str) -> ExposedEndpoint {
ron::from_str(&format!(
r#"(ron_name: "door", ron_url: "127.0.0.1:1", ron_trusted_proxies: {trusted})"#
))
.expect("the RON parses")
}
/// `ron_trusted_proxies` is CIDRs (HOL-DOCFIND a): empty trusts nobody, an entry that is
/// not a network is refused by name (`holger-listen-6` at the start).
#[test]
fn trusted_proxies_are_cidrs_or_refused_by_name() {
assert_eq!(endpoint("[]").trusted_proxies(), Ok(None));
let t = endpoint(r#"["10.0.0.1", "fd00::/8"]"#)
.trusted_proxies()
.unwrap()
.expect("a list");
assert!(t.allows("10.0.0.1".parse().unwrap()));
assert!(!t.allows("10.0.0.2".parse().unwrap()));
let e = endpoint(r#"["front.example"]"#)
.trusted_proxies()
.unwrap_err();
assert!(
e.contains("ron_trusted_proxies") && e.contains("front.example"),
"{e}"
);
}
}