holger-estate-front/tests/holger_cli_reaches_the_estate_through_the_front.rs
//! **`holger-cli --at https://<repo host>` reaches the estate through the FRONT** (holger
//! lane B3, owner 2026-10-26; the pattern of Gunnar's A6).
//!
//! vm1's :443 (edda `estate-front::web`, the certificate the installer FED) carries a gRPC
//! call to the repository host (`repo_hosts`) to the PRIMARY's control plane on its utility
//! address, with no logic of its own: TLS ends at the front, the call goes on as h2c, the
//! trailers come back, rate-limited per caller (`ROUTE_GRPC_BURST`). Here the primary is a
//! REAL holger in this test process, and the client is the real `holger-cli`, dialling the
//! front by `--connect` while the name it asks for stays the repository host's.
use std::collections::BTreeMap;
use std::io::Write as _;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use nordisk_pairing::join::{Assigned, JoinReply, Peer};
use nordisk_pairing::Role;
const CONSOLE: &str = "c0nsole-bearer-0123456789abcdefghij";
fn reply(feed: &BTreeMap<String, String>) -> JoinReply {
let peer = |name: &str, role, last: u8| Peer {
name: name.into(),
role,
tunnel_pubkey: String::new(),
overlay: nordisk_pairing::OVERLAY_BOX_A,
endpoint: None,
utility: Some([127, 0, 0, last].into()),
};
JoinReply {
estate: "holger.test".into(),
me: Assigned {
name: "vm1".into(),
overlay: nordisk_pairing::OVERLAY_FRONT,
},
cert_pem: None,
role: Some(Role::Front),
primary_overlay: None,
primary_utility: None,
peers: vec![peer("vm2", Role::Primary, 1), peer("vm3", Role::Twin, 3)],
feed: feed.clone(),
}
}
/// A real holger on 127.0.0.1 (the primary's utility address): one raw repository, the
/// console's static bearer as Admin. Answers `(its data dir, the server, its gRPC port)`.
fn primary() -> (tempfile::TempDir, holger_server_lib::Holger, u16) {
for _ in 0..8 {
let dir = tempfile::tempdir().unwrap();
let d = dir.path().display().to_string();
let free = || {
std::net::TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap()
.port()
};
let (grpc, http) = (free(), free());
let ron = format!(
r#"(
repositories: [ (ron_name: "files", ron_repo_type: "raw", ron_upstreams: [], ron_in: None,
ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "door")), ron_data_dir: Some("{d}/files")) ],
exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:{grpc}", ron_http_url: Some("127.0.0.1:{http}")) ],
storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "{d}") ],
audit: (dir: "{d}/audit", enabled: true),
auth: Some(( methods: [ StaticToken(subject: "console", token: Some("{CONSOLE}")) ], roles: {{"console": Admin}} )),
)"#
);
let cfg = dir.path().join("holger.ron");
std::fs::File::create(&cfg)
.unwrap()
.write_all(ron.as_bytes())
.unwrap();
let started = (|| -> Result<holger_server_lib::Holger, String> {
let mut h = holger_server_lib::read_ron_config(&cfg).map_err(|e| e.to_string())?;
h.instantiate_backends().map_err(|e| e.to_string())?;
holger_server_lib::wire_holger(&mut h).map_err(|e| e.to_string())?;
h.start().map_err(|e| e.to_string())?;
Ok(h)
})();
if let Ok(h) = started {
return (dir, h, grpc);
}
}
panic!("the primary never started");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn holger_cli_reaches_the_estate_through_the_fronts_repository_route() {
let _ = rustls::crypto::ring::default_provider().install_default();
let (pdir, _holger, grpc) = primary();
let dir = tempfile::tempdir().unwrap();
let (chain, key) =
estate_front::web::certs::self_signed(&["holger.test", "repo.holger.test"], 80);
let ca = dir.path().join("estate-ca.pem");
std::fs::write(&ca, &chain).unwrap();
let token = dir.path().join("console-token");
std::fs::write(&token, CONSOLE).unwrap();
let feed = BTreeMap::from([
(
"web_hosts".to_string(),
"holger.test,repo.holger.test".to_string(),
),
("repo_hosts".to_string(), "repo.holger.test".to_string()),
("repo_grpc_port".to_string(), grpc.to_string()),
(
estate_front::web::certs::fed_cert_key("holger.test"),
chain.clone(),
),
(estate_front::web::certs::fed_key_key("holger.test"), key),
]);
let latest: Arc<Mutex<JoinReply>> = Arc::new(Mutex::new(reply(&feed)));
let l80 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let l443 = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let a443 = l443.local_addr().unwrap();
let f = latest.clone();
let web = estate_front::web::start_on(
dir.path(),
"holger.test".into(),
Arc::new(move || f.lock().unwrap().feed.clone()),
l80,
l443,
);
let r = latest.clone();
estate_front::route::start(
web.app.clone(),
Arc::new(move || {
let reply = r.lock().unwrap().clone();
Some((
reply.feed.clone(),
nordisk_pairing::frontside::primary_utility(&reply),
))
}),
"holger.test".into(),
holger_estate_front::PRODUCT,
Some((
holger_estate_front::HTTP_PORT,
holger_estate_front::CONTROL_PORT,
)),
holger_estate_front::CARRIED_GRPC,
);
let at = format!("https://repo.holger.test:{}", a443.port());
let connect = a443.to_string();
let home = dir.path().join("cli-home").display().to_string();
let cli = |who: Option<&str>, args: &[&str]| {
let mut argv: Vec<String> = [
"holger-cli",
"--at",
&at,
"--connect",
&connect,
"--ca",
&ca.display().to_string(),
]
.map(String::from)
.to_vec();
if let Some(w) = who {
argv.extend(["--token-file".to_string(), w.to_string()]);
}
argv.extend(args.iter().map(|a| a.to_string()));
let home = home.clone();
holger_cli::run_args(argv, move |k| {
(k == "HOLGER_CLI_HOME").then(|| home.clone())
})
};
// The fed certificate is served and the route is up within a feed tick.
let mut health = Err(holger_cli::CliError::Local("not asked".into()));
for _ in 0..100 {
tokio::time::sleep(Duration::from_millis(100)).await;
health = cli(None, &["health"]).await;
if health.is_ok() {
break;
}
}
let health = health.unwrap_or_else(|e| panic!("health through the front: {e}"));
assert!(health.contains("status: \"ok\""), "{health}");
// The person's own credential rides through, and the primary decides.
let t = token.display().to_string();
let me = cli(Some(&t), &["whoami"]).await.unwrap();
assert!(
me.contains("subject: \"console\"") && me.contains("authenticated: true"),
"{me}"
);
let repos = cli(Some(&t), &["repos"]).await.unwrap();
assert!(repos.contains("name: \"files\""), "{repos}");
// A refusal comes back with its code in the trailers, carried as it came.
match cli(None, &["snapshot"]).await {
Err(holger_cli::CliError::Server(s)) => assert!(
matches!(
s.code(),
tonic::Code::Unauthenticated | tonic::Code::PermissionDenied
),
"{s:?}"
),
other => panic!("an anonymous snapshot through the front: {other:?}"),
}
// The primary audited the call it served.
let mut audit = String::new();
for e in std::fs::read_dir(pdir.path().join("audit"))
.unwrap()
.flatten()
{
audit.push_str(&String::from_utf8_lossy(
&std::fs::read(e.path()).unwrap_or_default(),
));
}
assert!(
!audit.is_empty(),
"the primary keeps its audit of the calls it served"
);
}