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

nordisk/gunnar

crates/gunnar-cli/tests/issuer_cli.rs

#![cfg(feature = "server")]

//! **`gunnar issuer show` reads through a handle that cannot sign**
//! — identity from a certificate.
//!
//! # Why this file exists at all
//!
//! Moving the control plane to redb bought exactly one thing: the catalog, the
//! identity table and the issuer *certificate* are readable without opening a
//! repository. `gunnar issuer show` is a control-plane read, and the handle it
//! reaches for is [`ServerStores::pki_readonly`], which refuses every
//! secret-store call **by construction**. One edit pointing `show` at
//! [`ServerStores::pki`] instead turns a read that cannot sign into one that
//! can, and nothing else in the suite would say so.
//!
//! # 🔴 What this file USED to claim, and why it stopped
//!
//! Until 2026-08-30 the second half of this test was a *dependency* claim: it
//! asserted `stores.pki()` **fails** here with a message naming the missing
//! storage engine, on the reasoning that the sealed secret store lived in the
//! control *repository* and a repository needs an engine. Both halves of that
//! premise are gone:
//!
//! * the secrets are rows in `control.redb` (`gunnar_core::secrets`), so
//!   `pki()` opens no repository and needs no engine — `stores.rs` says so in
//!   its own doc: *"since the secrets moved into `control.redb`, 'without an
//!   engine' describes both, and the distinction this method carries is a
//!   capability one rather than a dependency one"*;
//! * and this test crate is not engine-free either. `gunnar-cli`'s `server`
//!   feature **names** `gunnar-server/engine-znippy`, and `gunnar-server`
//!   refuses to compile with no engine at all, so an engine-free build of this
//!   binary cannot be produced to observe the failure from.
//!
//! So the old assertion was a wrong premise on both sides, and it failed with
//! its own `.expect` message — *"a build with no storage engine cannot open the
//! sealed secret store"* — which read like a product defect and was not one.
//!
//! **This is a deliberate weakening on one axis and a strengthening on the
//! other.** What is no longer asserted here: that the read needs no engine.
//! Nothing in this crate can assert it. What is asserted instead, and could not
//! be before: that the two handles differ **on the same store in the same
//! process** — the writable one mints, the read-only one refuses, and the
//! read-only one then reads back what the writable one wrote. A `pki_readonly`
//! that was quietly `pki` passes neither of the last two.
//!
//! That the read-only handle *refuses* rather than answering "there is no
//! issuer" is enforced in `gunnar_core::Pki::read_only` and proved at the source
//! by `gunnar-core/tests/issuer.rs`; what is asserted **here** is that the CLI's
//! own accessors are those two handles.

use gunnar_cli::server::ServerStores;

/// A `--data-root` with nothing in it, exactly as a first boot has.
fn stores() -> (tempfile::TempDir, ServerStores) {
    let dir = tempfile::tempdir().expect("tempdir");
    let stores = ServerStores::open(dir.path()).expect("opening the server's stores");
    (dir, stores)
}

/// **The read-only handle reads, and it cannot sign — measured against the
/// writable handle on the same store.**
///
/// All of it in one test on purpose. "The read-only handle refused to mint"
/// would also be true of a handle over a broken or empty store, and "the store
/// holds an issuer" would be true of a `pki_readonly` that was secretly `pki`.
/// The pairing is what separates the two.
#[test]
fn the_issuer_is_read_through_a_handle_that_cannot_sign() {
    let (_dir, stores) = stores();

    let reader = stores
        .pki_readonly()
        .expect("reading the issuer must not need a repository");
    assert!(
        reader.issuer().expect("reading the issuer").is_none(),
        "a fresh --data-root has no issuer, and that is `None` rather than a fault"
    );
    assert!(
        reader
            .trusted_cas()
            .expect("reading the CA list")
            .is_empty(),
        "and no trusted CAs"
    );

    // ── the capability half: this handle may not write ─────────────────────
    reader
        .create("nope", gunnar_core::DEFAULT_ISSUER_DAYS)
        .expect_err("a read-only handle must not be able to mint an issuer");
    assert!(
        stores
            .pki_readonly()
            .expect("re-open")
            .issuer()
            .expect("reading")
            .is_none(),
        "and the refused create must have committed nothing"
    );

    // ── the paired success, and it is what makes the refusal mean something ─
    //
    // The SAME store, the SAME process, one handle apart. If minting failed
    // here too, the refusal above would be about the store rather than about
    // the handle, and this file would prove nothing about `issuer show`
    // reaching for the right accessor.
    let minted = stores
        .pki()
        .expect("the writable handle opens on the same store")
        .create("gunnar-test-issuer", gunnar_core::DEFAULT_ISSUER_DAYS)
        .expect("the writable handle mints");

    // ── applied output: the read-only handle reads what the other wrote ────
    let seen = stores
        .pki_readonly()
        .expect("re-open the read-only handle")
        .issuer()
        .expect("reading the issuer")
        .expect("the issuer the writable handle just minted must be readable");
    assert_eq!(
        seen.fingerprint, minted.fingerprint,
        "the read-only handle is not reading the table the writable one wrote to, so the two \
         are not two views of one store"
    );
}