gunnar-server 3.0.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
Documentation
//! **`Bootstrap.BecomeTwin` and `Bootstrap.InstallTwinCertificate` take a
//! credential** (h-bootstrap-open, 2026-09-28).
//!
//! Both are served outside the gate, beside `Bootstrap.Restore`, on the utility
//! NIC as plaintext h2c. Before this lane either could be called by anybody who
//! reached :50051 on a fresh box:
//!
//! * `BecomeTwin` made the box the permanent twin of ANY primary the caller
//!   named — whose log then brings its own KEK and admins (a takeover);
//! * `InstallTwinCertificate` pinned the FIRST issuer it was handed (trust on
//!   first use), so a stranger could pin theirs and leave the twin unusable.
//!
//! Now both check the same `gunnar-sshsig-v2` credential `Restore` does, bound
//! to THEIR method, against the keys the box was FED (`restore_keys`: the
//! installer's `admin_pubkey`, the admin principals of `pairing/joined.toml`,
//! `--restore-pubkey`). The callers that hold such a key already sign: the
//! front's pairing door (the console key) and `gunnar control twin join -i`.

use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};

use gunnar_core::ssh_key::PrivateKey;
use gunnar_grpc::{EventBus, ServerConfig, ServiceContext};
use gunnar_proto::bootstrap_client::BootstrapClient;
use gunnar_server::stores::ServerStores;
use gunnar_server::twin;
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 wait<T>(what: &str, within: Duration, mut probe: impl FnMut() -> Option<T>) -> T {
    let t0 = Instant::now();
    loop {
        if let Some(v) = probe() {
            return v;
        }
        assert!(t0.elapsed() < within, "{what}: not within {within:?}");
        std::thread::sleep(Duration::from_millis(25));
    }
}

struct Served {
    rt: tokio::runtime::Runtime,
    addr: SocketAddr,
}

/// The fresh box's control plane as `serve` serves it: the real router,
/// authentication REQUIRED (the default), Bootstrap outside the gate.
fn serve(stores: &Arc<ServerStores>) -> Served {
    let plane: Arc<dyn gunnar_grpc::ControlPlane> =
        Arc::new(CoreControlPlane::new(Arc::clone(stores)));
    let ctx = ServiceContext::new(plane, Arc::new(EventBus::new(16)), ServerConfig::default());
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(2)
        .enable_all()
        .build()
        .expect("rt");
    let addr = free_addr();
    rt.spawn(async move {
        let _ = gunnar_grpc::router(ctx, || 0).serve(addr).await;
    });
    wait("the box listens", Duration::from_secs(10), || {
        std::net::TcpStream::connect(addr).ok()
    });
    Served { rt, addr }
}

fn with_credential<T>(message: T, credential: Option<String>) -> tonic::Request<T> {
    let mut request = tonic::Request::new(message);
    if let Some(value) = credential {
        request.metadata_mut().insert(
            gunnar_grpc::CREDENTIAL_METADATA_KEY,
            value.parse().expect("a legal header"),
        );
    }
    request
}

impl Served {
    fn client(&self) -> BootstrapClient<tonic::transport::Channel> {
        // A lazy channel spawns onto the runtime it is made in.
        let _in = self.rt.enter();
        let channel = tonic::transport::Channel::from_shared(format!("http://{}", self.addr))
            .expect("url")
            .connect_lazy();
        BootstrapClient::new(channel)
    }

    fn become_twin(
        &self,
        primary: &str,
        credential: Option<String>,
    ) -> Result<gunnar_proto::BecomeTwinResponse, tonic::Status> {
        let mut c = self.client();
        let req = with_credential(
            gunnar_proto::BecomeTwinRequest {
                primary: primary.to_string(),
            },
            credential,
        );
        self.rt
            .block_on(async move { c.become_twin(req).await.map(|r| r.into_inner()) })
    }

    fn install(
        &self,
        credential: Option<String>,
    ) -> Result<gunnar_proto::InstallTwinCertificateResponse, tonic::Status> {
        let mut c = self.client();
        let req = with_credential(
            gunnar_proto::InstallTwinCertificateRequest {
                cert_pem: "-----BEGIN CERTIFICATE-----\nnot-one\n-----END CERTIFICATE-----\n"
                    .into(),
                ca_pem: "-----BEGIN CERTIFICATE-----\nnot-one-either\n-----END CERTIFICATE-----\n"
                    .into(),
                replication_port: 50053,
                replication_host: "10.13.0.2".into(),
            },
            credential,
        );
        self.rt.block_on(async move {
            c.install_twin_certificate(req)
                .await
                .map(|r| r.into_inner())
        })
    }
}

fn key() -> (PrivateKey, String) {
    let pem = gunnar_core::mint_ed25519().expect("mint");
    let key = PrivateKey::from_openssh(std::str::from_utf8(&pem).unwrap()).expect("parse");
    let line = key.public_key().to_openssh().expect("public line");
    (key, line)
}

fn credential(key: &PrivateKey, method: &str) -> String {
    use std::sync::atomic::{AtomicU64, Ordering};
    static N: AtomicU64 = AtomicU64::new(0);
    let now = twin::now_ms();
    let nonce = format!(
        "bootstrap-credential-test-{}-{}-{now}",
        std::process::id(),
        N.fetch_add(1, Ordering::SeqCst)
    );
    gunnar_grpc::sign_credential_for(key, now, &nonce, method)
        .expect("sign")
        .to_header_value()
}

/// What the installer's door leaves on a box it fed: `admin_pubkey` is the operator.
fn feed(root: &std::path::Path, admin: &str) {
    std::fs::create_dir_all(root.join("pairing")).unwrap();
    let text = format!(
        "estate = \"bootstrap-credential-test\"\nrole = \"twin\"\npeers = []\n\n[me]\nname = \"twin\"\noverlay = \"10.13.0.3\"\n\n\
         [feed]\nadmin_pubkey = \"{admin}\"\n"
    );
    std::fs::write(root.join("pairing/joined.toml"), text).unwrap();
}

fn fresh_fed() -> (tempfile::TempDir, Arc<ServerStores>, PrivateKey) {
    let fd = tempfile::tempdir().unwrap();
    let stores = Arc::new(ServerStores::open(fd.path()).unwrap());
    let (operator, line) = key();
    feed(fd.path(), &line);
    (fd, stores, operator)
}

/// **RED first**: a stranger with NO credential tells a fresh box whose twin it is.
#[test]
fn become_twin_without_a_credential_is_refused_and_the_box_stays_its_own() {
    let (fd, stores, _operator) = fresh_fed();
    let served = serve(&stores);
    let refused = served
        .become_twin("10.66.6.6:50051", None)
        .expect_err("no credential, no twin");
    assert_eq!(refused.code(), tonic::Code::Unauthenticated, "{refused:?}");
    assert!(
        refused.message().contains("Bootstrap.BecomeTwin"),
        "the refusal names the door: {refused:?}"
    );
    assert!(
        !twin::is_twin_root(fd.path()),
        "the box did not become anybody's twin"
    );
    assert!(!stores.is_twin());
}

/// A stranger's OWN key, correctly signed for the method, is not a fed key.
#[test]
fn become_twin_from_a_key_the_box_was_not_fed_is_refused() {
    let (fd, stores, _operator) = fresh_fed();
    let served = serve(&stores);
    let (stranger, _) = key();
    let refused = served
        .become_twin(
            "10.66.6.6:50051",
            Some(credential(&stranger, "Bootstrap.BecomeTwin")),
        )
        .expect_err("a stranger");
    assert_eq!(refused.code(), tonic::Code::Unauthenticated, "{refused:?}");
    assert!(!twin::is_twin_root(fd.path()));
}

/// A credential lifted off another Bootstrap call does not open this one.
#[test]
fn a_restore_credential_does_not_make_a_twin() {
    let (fd, stores, operator) = fresh_fed();
    let served = serve(&stores);
    let refused = served
        .become_twin(
            "10.66.6.6:50051",
            Some(credential(&operator, "Bootstrap.Restore")),
        )
        .expect_err("bound elsewhere");
    assert_eq!(refused.code(), tonic::Code::Unauthenticated, "{refused:?}");
    assert!(!twin::is_twin_root(fd.path()));
}

/// The fed key, signed for the method: the box becomes the twin, as before.
#[test]
fn become_twin_from_the_fed_key_is_taken() {
    let (fd, stores, operator) = fresh_fed();
    let served = serve(&stores);
    let ok = served
        .become_twin(
            "10.13.0.2:50051",
            Some(credential(&operator, "Bootstrap.BecomeTwin")),
        )
        .expect("the operator makes the twin");
    assert!(!ok.twin_public_key.is_empty());
    assert!(twin::is_twin_root(fd.path()));
}

/// **RED first**: no credential, and the twin would pin whatever issuer came first.
#[test]
fn install_twin_certificate_without_a_credential_is_refused_before_anything_is_pinned() {
    let (fd, stores, _operator) = fresh_fed();
    let served = serve(&stores);
    let refused = served.install(None).expect_err("no credential, no pin");
    assert_eq!(refused.code(), tonic::Code::Unauthenticated, "{refused:?}");
    assert!(
        refused
            .message()
            .contains("Bootstrap.InstallTwinCertificate"),
        "{refused:?}"
    );
    let (stranger, _) = key();
    let refused = served
        .install(Some(credential(
            &stranger,
            "Bootstrap.InstallTwinCertificate",
        )))
        .expect_err("a stranger");
    assert_eq!(refused.code(), tonic::Code::Unauthenticated, "{refused:?}");
    let _ = fd;
}

/// A box nobody fed takes neither call from anybody — fail closed, by name.
#[test]
fn an_unfed_box_refuses_both_by_name() {
    let fd = tempfile::tempdir().unwrap();
    let stores = Arc::new(ServerStores::open(fd.path()).unwrap());
    let served = serve(&stores);
    let (someone, _) = key();
    let refused = served
        .become_twin(
            "10.13.0.2:50051",
            Some(credential(&someone, "Bootstrap.BecomeTwin")),
        )
        .expect_err("unfed");
    assert_eq!(refused.code(), tonic::Code::PermissionDenied, "{refused:?}");
    assert!(
        refused.message().contains("[bootstrap-no-key]"),
        "{refused:?}"
    );
    let refused = served
        .install(Some(credential(
            &someone,
            "Bootstrap.InstallTwinCertificate",
        )))
        .expect_err("unfed");
    assert_eq!(refused.code(), tonic::Code::PermissionDenied, "{refused:?}");
    assert!(
        refused.message().contains("[bootstrap-no-key]"),
        "{refused:?}"
    );
    assert!(!twin::is_twin_root(fd.path()));
}