crates/gunnar-server/tests/repair_key_is_narrow.rs
//! **The repair key is narrow** (owner 2026-10-12: "a twin-only grant plus a fed restore key —
//! it may only drive the restore-from-twin path (and the fence/look it needs), never full
//! admin"). Over the real wire, with real credentials (no insecure mode):
//!
//! * fed as `principal.repair = "machine repair <key>"`, the primary installs it with NO grant;
//! * even GRANTED server admin by hand (the worst case), the `repair` principal reaches only
//! `gunnar_grpc::REPAIR_METHODS`: it reads the log (`Mirror.LogHead`), it fences
//! (`Mirror.SetRole(STANDBY)`) — and it cannot add a principal, read the server's stats,
//! add a key, demote-and-promote (`SetRole(PRIMARY)`), or issue a twin certificate;
//! * it is a restore key of a fresh, fed box (`fed_principals::restore_keys`).
#![cfg(all(feature = "replication", feature = "wg-client"))]
use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use gunnar_core::ssh_key::PrivateKey;
use gunnar_grpc::client_signer::{authed, plain_channel};
use gunnar_grpc::{EventBus, ServerConfig, ServiceContext};
use gunnar_server::stores::ServerStores;
use gunnar_server::CoreControlPlane;
fn free_addr() -> SocketAddr {
let l = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
l.local_addr().expect("addr")
}
fn mint() -> (PrivateKey, String) {
let pem = gunnar_core::mint_ed25519().expect("mint");
let k = PrivateKey::from_openssh(std::str::from_utf8(&pem).unwrap()).expect("parse");
let line = k.public_key().to_openssh().expect("line");
(k, line)
}
#[test]
fn the_repair_key_reads_the_log_and_fences_and_does_nothing_an_admin_does() {
let d = tempfile::tempdir().unwrap();
let stores = Arc::new(ServerStores::open(d.path()).expect("stores"));
let (_admin, admin_line) = mint();
stores
.auth()
.bootstrap_admin(&admin_line)
.expect("the first admin");
let (repair, repair_line) = mint();
let feed: BTreeMap<String, String> = [(
format!("principal.{}", gunnar_grpc::REPAIR_PRINCIPAL),
format!("machine repair {repair_line}"),
)]
.into_iter()
.collect();
let said =
gunnar_server::fed_principals::install(&stores, &feed).expect("the primary installs it");
assert!(
said.iter().any(|l| l.starts_with("repair")
&& l.contains("key registered")
&& !l.contains("gunnar-admin")),
"{said:?}"
);
assert!(
stores
.auth()
.grants(Some(gunnar_grpc::REPAIR_PRINCIPAL))
.unwrap()
.is_empty(),
"fed with NO grant"
);
assert!(
gunnar_server::fed_principals::restore_keys(&feed).contains(&repair_line),
"a fresh fed box takes its restore"
);
// A `repair` role on any other name is refused.
let other: BTreeMap<String, String> = [(
"principal.mallory".to_string(),
format!("machine repair {repair_line}"),
)]
.into_iter()
.collect();
assert!(gunnar_server::fed_principals::parse(&other)
.unwrap_err()
.contains("repair"));
// The worst case: somebody grants it server admin by hand. The narrowing is the name's.
let plane_for_grant = CoreControlPlane::new(Arc::clone(&stores));
gunnar_grpc::ControlPlane::grant(
&plane_for_grant,
gunnar_grpc::REPAIR_PRINCIPAL,
"",
"",
gunnar_proto::GrantLevel::Admin,
)
.expect("granted by hand");
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.expect("rt");
let addr = free_addr();
let bus = Arc::new(EventBus::new(16));
let slot = gunnar_server::mirror::ReplicationSlot::new(
Arc::clone(&stores),
Arc::clone(&bus),
rt.handle().clone(),
);
let plane: Arc<dyn gunnar_grpc::ControlPlane> =
Arc::new(CoreControlPlane::new(Arc::clone(&stores)).with_replication(slot));
let ctx = ServiceContext::new(plane, bus, ServerConfig::default());
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
rt.spawn(async move {
let _ = gunnar_grpc::router(ctx, || 0)
.serve_with_shutdown(addr, async {
let _ = rx.await;
})
.await;
});
let t0 = Instant::now();
while std::net::TcpStream::connect(addr).is_err() {
assert!(t0.elapsed() < Duration::from_secs(10), "the box listens");
std::thread::sleep(Duration::from_millis(25));
}
let at = addr.to_string();
rt.block_on(async {
let ch = plain_channel(&at).expect("a channel");
let mut mirror = gunnar_proto::mirror_client::MirrorClient::new(ch.clone());
let mut admin = gunnar_proto::admin_client::AdminClient::new(ch.clone());
let mut access = gunnar_proto::access_client::AccessClient::new(ch.clone());
// The restore path's look: allowed.
mirror
.log_head(authed(&repair, gunnar_proto::LogHeadRequest::default()))
.await
.expect("Mirror.LogHead: the repair key reads the log");
// The fence: STANDBY allowed (the grant check passes; what the slot answers is the box's).
let standby = mirror
.set_role(authed(
&repair,
gunnar_proto::SetRoleRequest {
role: gunnar_proto::ReplicationRole::Standby as i32,
},
))
.await;
assert!(
standby
.as_ref()
.map(|_| true)
.unwrap_or_else(|s| s.code() != tonic::Code::PermissionDenied),
"the fence is the repair key's: {standby:?}"
);
// Nothing an admin does.
let primary = mirror
.set_role(authed(
&repair,
gunnar_proto::SetRoleRequest {
role: gunnar_proto::ReplicationRole::Primary as i32,
},
))
.await
.unwrap_err();
assert_eq!(
primary.code(),
tonic::Code::PermissionDenied,
"it never promotes: {primary:?}"
);
let stats = admin
.stats(authed(&repair, gunnar_proto::StatsRequest {}))
.await
.unwrap_err();
assert_eq!(stats.code(), tonic::Code::PermissionDenied, "{stats:?}");
assert!(stats.message().contains("narrowed"), "{}", stats.message());
let add = access
.add_principal(authed(
&repair,
gunnar_proto::AddPrincipalRequest {
name: "mallory".into(),
kind: gunnar_proto::PrincipalKind::User as i32,
..Default::default()
},
))
.await
.unwrap_err();
assert_eq!(add.code(), tonic::Code::PermissionDenied, "{add:?}");
let cert = admin
.issue_twin_certificate(authed(
&repair,
gunnar_proto::IssueTwinCertificateRequest::default(),
))
.await
.unwrap_err();
assert_eq!(cert.code(), tonic::Code::PermissionDenied, "{cert:?}");
});
assert!(
stores.auth().principal("mallory").unwrap().is_none(),
"nothing was added"
);
let _ = tx.send(());
}