Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

crates/gunnar-ssh/tests/tunnel_address_verb.rs

//! **`gunnar-tunnel-address`: the sealed box's WireGuard tunnel address, over
//! the SSH door it already has** (2026-09-21, T17).
//!
//! The sealed installer (`serve --no-grpc --admin-key @/data/admin.pub`) runs
//! no control plane: its SSH listener is the only door. The tunnel address is
//! per box and is not on the medium, so it has to arrive through that door —
//! as ONE exec verb, held to server-wide Admin, answered by the same function
//! the gRPC doors call (`gunnar-server`'s `appliance::tunnel_address_answer`,
//! injected here as a closure because this crate may not link the server).
//!
//! Both directions, every time: an admin gets the door's answer; a principal
//! with only a repository grant never reaches the door (and an anonymous
//! session is refused the channel: `tests/anonymous.rs`) (the recorder proves the door was not
//! called, not merely that the answer was an error).

mod common;

use std::sync::{Arc, Mutex};

use common::*;
use gunnar_core::{Grant, Level};
use gunnar_ssh::seam::BannerTransport;
use gunnar_ssh::{Access, GunnarSsh, SshConfig, TUNNEL_ADDRESS_VERB};

/// A door that records what it was asked and answers like the real one.
fn recording_door() -> (gunnar_ssh::TunnelAddressDoor, Arc<Mutex<Vec<String>>>) {
    let calls = Arc::new(Mutex::new(Vec::new()));
    let seen = Arc::clone(&calls);
    let door: gunnar_ssh::TunnelAddressDoor = Arc::new(move |address: &str| {
        seen.lock().unwrap().push(address.to_string());
        match address {
            "" | "10.77.0.2/16" => {
                Ok("wg_public_key K\naddress 10.77.0.2/16\nchanged true\n".to_string())
            }
            other => Err(format!("{other} refused: never re-pointed")),
        }
    });
    (door, calls)
}

async fn serve(
    plane: Plane,
    door: Option<gunnar_ssh::TunnelAddressDoor>,
) -> gunnar_ssh::BoundServer {
    let transport = Arc::new(BannerTransport::new());
    let mut server = GunnarSsh::new(SshConfig::ephemeral_loopback(), plane.control, transport);
    if let Some(door) = door {
        server = server.with_tunnel_address_door(door);
    }
    server.bind(mint_key()).await.expect("bind")
}

#[tokio::test]
async fn only_a_server_admin_reaches_the_tunnel_address_door() {
    let admin = mint_key();
    let reader = mint_key();
    let plane = Plane::empty()
        .with_the_repo()
        .with_principal("admin")
        .with_key("admin", &admin)
        .with_principal("reader")
        .with_key("reader", &reader)
        .with_grant("reader", Access::Admin);
    plane
        .auth()
        .grant(Grant::server("admin", Level::Admin))
        .expect("server admin");
    let (door, calls) = recording_door();
    let server = serve(plane, Some(door)).await;
    let addr = server.local_addr();

    // The admin: the door's answer on stdout, exit 0.
    let s = authenticate(addr, &admin, "git")
        .await
        .unwrap()
        .expect("admin key accepted");
    match exec(&s, &format!("{TUNNEL_ADDRESS_VERB} 10.77.0.2/16"))
        .await
        .unwrap()
    {
        ExecOutcome::Accepted {
            stdout,
            exit_status,
        } => {
            assert_eq!(exit_status, Some(0), "{stdout}");
            assert!(stdout.contains("address 10.77.0.2/16"), "{stdout}");
        }
        other => panic!("a server admin must reach the door: {other:?}"),
    }
    // A read (no argument) is the same door with an empty address.
    match exec(&s, TUNNEL_ADDRESS_VERB).await.unwrap() {
        ExecOutcome::Accepted { exit_status, .. } => assert_eq!(exit_status, Some(0)),
        other => panic!("{other:?}"),
    }
    // The door's refusal is the admin's answer, non-zero, in words.
    match exec(&s, &format!("{TUNNEL_ADDRESS_VERB} 10.77.0.3/16"))
        .await
        .unwrap()
    {
        ExecOutcome::Accepted {
            stdout,
            exit_status,
        } => {
            assert_eq!(exit_status, Some(1), "{stdout}");
            assert!(
                stdout.starts_with("error: ") && stdout.contains("never re-pointed"),
                "{stdout}"
            );
        }
        other => panic!("{other:?}"),
    }
    // Two arguments is not the verb.
    match exec(&s, &format!("{TUNNEL_ADDRESS_VERB} 10.77.0.2/16 extra"))
        .await
        .unwrap()
    {
        ExecOutcome::Accepted {
            stdout,
            exit_status,
        } => {
            assert_eq!(exit_status, Some(1), "{stdout}");
        }
        other => panic!("{other:?}"),
    }
    assert_eq!(
        *calls.lock().unwrap(),
        vec!["10.77.0.2/16", "", "10.77.0.3/16"],
        "the malformed line never reached the door"
    );

    // A principal with ADMIN on a repository — not on the server — is refused
    // before the door.
    let r = authenticate(addr, &reader, "git")
        .await
        .unwrap()
        .expect("reader key accepted");
    match exec(&r, &format!("{TUNNEL_ADDRESS_VERB} 10.77.0.2/16"))
        .await
        .unwrap()
    {
        ExecOutcome::Accepted {
            stdout,
            exit_status,
        } => {
            assert_eq!(
                exit_status,
                Some(gunnar_ssh::server::EXIT_DENIED),
                "{stdout}"
            );
            assert!(stdout.contains("server-wide Admin"), "{stdout}");
        }
        ExecOutcome::Refused => {}
    }
    assert_eq!(
        calls.lock().unwrap().len(),
        3,
        "a repository admin never reaches the door"
    );
}

#[tokio::test]
async fn a_box_with_no_tunnel_says_so_by_name() {
    let admin = mint_key();
    let plane = Plane::empty()
        .with_principal("admin")
        .with_key("admin", &admin);
    plane
        .auth()
        .grant(Grant::server("admin", Level::Admin))
        .expect("server admin");
    // No door: a build without the tunnel says so, by name.
    let server = serve(plane, None).await;
    let s = authenticate(server.local_addr(), &admin, "git")
        .await
        .unwrap()
        .expect("admin");
    match exec(&s, &format!("{TUNNEL_ADDRESS_VERB} 10.77.0.2/16"))
        .await
        .unwrap()
    {
        ExecOutcome::Accepted {
            stdout,
            exit_status,
        } => {
            assert_eq!(exit_status, Some(1), "{stdout}");
            assert!(stdout.contains("no WireGuard tunnel"), "{stdout}");
        }
        other => panic!("{other:?}"),
    }
}