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
//! **The key the appliance signs an actor ticket with.**
//!
//! An actor ticket is the appliance's word that one human may act for one
//! tenant; `Billing.MintActorTicket` makes one and the other side of the
//! monetize seam checks the signature offline, against the public half this
//! box advertises. That arrangement has exactly two failure modes worth a test
//! file, and both are about the key rather than about the ticket:
//!
//! * **the key must be stable.** It is minted on first use and kept in the
//!   secret store. If a reboot minted a new one, every ticket this appliance
//!   had ever advertised a key for would stop verifying, and the symptom would
//!   be "the renewal page is broken" rather than anything naming a key.
//! * **the key must be its own.** The obvious shortcut — sign with the SSH host
//!   key, which every appliance already has — is a cross-protocol reuse with no
//!   domain separation at the signature layer. `gunnar_store::ACTOR_TICKET_KEY`
//!   says at length why that was not taken; this file is what holds the answer
//!   to the code.
//!
//! The target is `required-features = ["metered"]`: the community appliance
//! mints nothing under this name, because it does not contain the service that
//! would spend it.

use std::sync::Arc;

use gunnar_grpc::ControlPlane as _;
use gunnar_server::control_plane::CoreControlPlane;
use gunnar_server::stores::ServerStores;

/// **Minted once, and the same bytes ever after.**
///
/// Two `ServerStores` over one root, because that is what a restart is. The
/// second call must read the first call's key rather than mint a second one:
/// `SecretStoreExt::get_or_create_with` is what makes that atomic, and this is
/// the assertion that says we are really using it.
#[test]
fn the_actor_ticket_key_is_minted_once_and_survives_a_restart() {
    let dir = tempfile::tempdir().expect("tempdir");

    let first = {
        let stores = ServerStores::open(dir.path()).expect("stores");
        stores
            .actor_ticket_key()
            .expect("the first call mints a key")
    };
    let second = {
        let stores = ServerStores::open(dir.path()).expect("stores");
        stores
            .actor_ticket_key()
            .expect("the second call must find the first call's key")
    };

    assert_eq!(
        first.public_key_hex(),
        second.public_key_hex(),
        "the appliance minted a SECOND actor-ticket key on restart. Every ticket signed by \
         the first one stops verifying against the key this box now advertises, and nothing \
         in the failure says the word 'key'"
    );
    // The public half is what an operator copies into a configuration by hand,
    // so its shape is part of the contract and not an implementation detail.
    assert_eq!(first.public_key_hex().len(), 64);
    assert!(
        first
            .public_key_hex()
            .chars()
            .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()),
        "the advertised key must be lowercase hex: {}",
        first.public_key_hex()
    );
}

/// **It is not the SSH host key.**
///
/// The one assertion that would go red if somebody ever "saved a key" by
/// reusing the one the box already has. See `gunnar_store::ACTOR_TICKET_KEY`
/// for why that saving is not worth what it costs.
#[test]
fn the_actor_ticket_key_is_not_the_ssh_host_key() {
    let dir = tempfile::tempdir().expect("tempdir");
    let stores = ServerStores::open(dir.path()).expect("stores");

    let ticket_key = stores.actor_ticket_key().expect("actor-ticket key");
    let host = gunnar_core::ensure_host_key(&stores.secrets().expect("secrets")).expect("host key");

    let host_hex = match host.private_key().public_key().key_data().ed25519() {
        Some(public) => public.0.iter().map(|b| format!("{b:02x}")).collect(),
        None => String::new(),
    };
    assert_ne!(
        ticket_key.public_key_hex(),
        host_hex,
        "the actor-ticket key IS the SSH host key: one key, two signature formats, and only \
         one of them has a domain separator"
    );
}

/// **The signature this key makes is the one the seam verifies.**
///
/// Not a ticket — that is `gunnar-grpc/tests/billing.rs`'s job — but the
/// primitive under it: the bytes `sign` returns must check out under the key
/// `public_key_hex` advertises, decoded the way an operator's configuration
/// would decode it. A mismatch here would make every ticket unverifiable while
/// every test that only re-checked with the same object stayed green.
#[test]
fn what_this_key_signs_verifies_under_the_hex_it_advertises() {
    use ed25519_dalek::{Signature, Verifier, VerifyingKey};

    let dir = tempfile::tempdir().expect("tempdir");
    let stores = ServerStores::open(dir.path()).expect("stores");
    let key = stores.actor_ticket_key().expect("actor-ticket key");

    let message = b"the appliance's word, as an operator's monetize would read it";
    let signature = key.sign(message);

    let mut raw = [0u8; 32];
    for (slot, pair) in raw
        .iter_mut()
        .zip(key.public_key_hex().as_bytes().chunks(2))
    {
        *slot = u8::from_str_radix(std::str::from_utf8(pair).expect("ascii"), 16)
            .expect("hex digit pair");
    }
    let verifying = VerifyingKey::from_bytes(&raw).expect("a verifying key");
    verifying
        .verify(message, &Signature::from_bytes(&signature))
        .expect("what the appliance signed must verify under what the appliance advertises");

    // And the negative half, so the assertion above is not satisfied by a
    // verifier that accepts everything.
    assert!(
        verifying
            .verify(b"other bytes", &Signature::from_bytes(&signature))
            .is_err(),
        "the verifier accepted a signature over other bytes"
    );
}

/// **The REAL control plane answers both halves of the seam.**
///
/// `MemoryControlPlane` in `gunnar-grpc/tests/billing.rs` proves the service
/// enforces the scope; it proves nothing about the appliance that actually
/// ships, whose `ControlPlane` is this one. The default in the seam is
/// `Unimplemented` by name — the right answer for a plane with no secret store
/// — so without this test a metered appliance could serve `Billing` and refuse
/// every call, with every test in the tree green.
///
/// Both halves are checked against each other rather than separately: what the
/// plane SIGNS must verify under what the plane ADVERTISES, because two
/// methods reading two different keys is the one failure that no single-method
/// test can see.
#[test]
fn the_real_control_plane_signs_and_advertises_the_same_key() {
    use ed25519_dalek::{Signature, Verifier, VerifyingKey};

    let dir = tempfile::tempdir().expect("tempdir");
    let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
    let plane = CoreControlPlane::new(Arc::clone(&stores));

    let hex = plane
        .actor_ticket_public_key_hex()
        .expect("the shipping appliance must advertise its actor-ticket key");
    assert_eq!(hex.len(), 64);

    let message = b"what an appliance signs is what an appliance advertises";
    let signature = plane
        .actor_ticket_sign(message)
        .expect("the shipping appliance must sign");

    let mut raw = [0u8; 32];
    for (slot, pair) in raw.iter_mut().zip(hex.as_bytes().chunks(2)) {
        *slot = u8::from_str_radix(std::str::from_utf8(pair).expect("ascii"), 16)
            .expect("hex digit pair");
    }
    VerifyingKey::from_bytes(&raw)
        .expect("the advertised key parses")
        .verify(message, &Signature::from_bytes(&signature))
        .expect(
            "the appliance signed under a key it does not advertise. Every ticket it mints is \
             unverifiable and nothing on this box would say so",
        );

    // The key is the STORE's, not the plane's: a second plane over the same
    // root advertises the same key, which is what survives a restart.
    let second = CoreControlPlane::new(stores);
    assert_eq!(
        second.actor_ticket_public_key_hex().expect("advertised"),
        hex
    );
}