crates/gunnar-cli/tests/add_key_attested.rs
#![cfg(all(feature = "server", feature = "control"))]
//! **`Access.AddKey` the target principal attests — on the REAL plane**
//! (2026-09-12).
//!
//! `gunnar-core`'s `key_attestation` unit tests prove the arithmetic and
//! `gunnar-grpc`'s services test proves the shape on the double. This drives
//! the shipped `CoreControlPlane` with a real identity table and real keys:
//! the attestation the CLI would print is what the server verifies, and every
//! refusal is asserted on the key table — a refusal that wrote the key anyway
//! looks identical to the caller.
//!
//! Red first, in this order: an admin's bare word on a keyed principal
//! (FAILED_PRECONDITION); an attestation by the admin's own key
//! (INVALID_ARGUMENT); an attestation the target made for a *different* key
//! (INVALID_ARGUMENT); a forged one (INVALID_ARGUMENT). Then green: the
//! target's honest attestation, and the target adding to themselves with no
//! attestation at all.
mod common;
use std::sync::Arc;
use common::Harness;
use gunnar_cli::server::CoreControlPlane;
use gunnar_core::key_attestation::{sign_attestation, KeyAttestation};
use gunnar_core::{
mint_ed25519, ssh_key, Fingerprint, Grant, HashKind, Level, Principal, PrincipalKind, Scope,
};
use gunnar_grpc::{AuthMode, Caller, ControlPlane, EventBus, ServerConfig, ServiceContext};
use gunnar_proto::access_server::Access as _;
use gunnar_proto::AddKeyRequest;
use tonic::{Code, Request};
fn key() -> ssh_key::PrivateKey {
ssh_key::PrivateKey::from_openssh(mint_ed25519().expect("mint")).expect("parse")
}
fn openssh(key: &ssh_key::PrivateKey) -> String {
key.public_key().to_openssh().expect("openssh")
}
fn fingerprint(key: &ssh_key::PrivateKey) -> String {
key.public_key()
.fingerprint(ssh_key::HashAlg::Sha256)
.to_string()
}
fn now<F: std::future::Future>(future: F) -> F::Output {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("runtime")
.block_on(future)
}
fn as_caller<T>(message: T, principal: &str) -> Request<T> {
let mut request = Request::new(message);
request.extensions_mut().insert(Caller {
principal_id: principal.to_owned(),
fingerprint: String::new(),
machine: false,
authenticated: true,
});
request
}
fn access(plane: &Arc<CoreControlPlane>) -> gunnar_grpc::AccessService {
let control: Arc<dyn ControlPlane> = plane.clone();
gunnar_grpc::AccessService::new(ServiceContext::new(
control,
Arc::new(EventBus::new(16)),
ServerConfig {
auth: AuthMode::Required,
..ServerConfig::default()
},
))
}
/// The wire form of an attestation, re-encoded by hand rather than through the
/// server's converter, so what the RPC is handed is what a client sends.
fn wire(signed: &gunnar_core::SignedKeyAttestation) -> gunnar_proto::SignedKeyAttestation {
gunnar_proto::SignedKeyAttestation {
attestation: Some(gunnar_proto::KeyAttestation {
principal_id: signed.attestation.principal_id.clone(),
fingerprint: signed.attestation.fingerprint.clone(),
at_unix_ms: signed.attestation.at_unix_ms,
}),
key_fingerprint: signed.key_fingerprint.clone(),
signature: signed.signature.clone(),
}
}
fn attest(
by: &ssh_key::PrivateKey,
principal: &str,
new: &str,
) -> gunnar_proto::SignedKeyAttestation {
wire(
&sign_attestation(
by,
KeyAttestation {
principal_id: principal.into(),
fingerprint: new.into(),
at_unix_ms: 1_700_000_000_000,
},
)
.expect("sign"),
)
}
fn add(
principal: &str,
key: &ssh_key::PrivateKey,
attestation: Option<gunnar_proto::SignedKeyAttestation>,
) -> AddKeyRequest {
AddKeyRequest {
principal_id: principal.into(),
openssh_public_key: openssh(key),
comment: String::new(),
expires_at_unix_ms: None,
attestation,
}
}
#[test]
fn an_admin_cannot_mint_access_as_another_person_without_their_word() {
let harness = Harness::start("team", "proj", HashKind::Sha1);
let auth = harness.stores.auth();
let roots = key();
let alices = key();
for id in ["root", "alice"] {
auth.add_principal(Principal::new(id, id, PrincipalKind::User).unwrap())
.expect("principal");
}
auth.register_key("root", &openssh(&roots), None)
.expect("root's key");
auth.grant(Grant {
principal: "root".into(),
scope: Scope::Server,
level: Level::Admin,
})
.expect("root is admin");
let plane = Arc::new(CoreControlPlane::new(Arc::clone(&harness.stores)));
let access = access(&plane);
let keys_of = |id: &str| auth.keys_of(id).expect("keys").len();
// alice's FIRST key: nobody to ask, the admin's word stands.
now(access.add_key(as_caller(add("alice", &alices, None), "root")))
.expect("a first key needs no attestation");
assert_eq!(keys_of("alice"), 1);
// RED 1: the admin's bare word on a keyed principal.
let second = key();
let status = now(access.add_key(as_caller(add("alice", &second, None), "root")))
.expect_err("a second key on the admin's word alone must be refused");
assert_eq!(status.code(), Code::FailedPrecondition, "{status}");
assert!(
status.message().contains("attest-key"),
"the refusal must say how to satisfy it: {}",
status.message()
);
assert_eq!(keys_of("alice"), 1, "the refusal must have written nothing");
// RED 2: attested by the ADMIN's key. Only the target can attest.
let by_root = attest(&roots, "alice", &fingerprint(&second));
let status = now(access.add_key(as_caller(add("alice", &second, Some(by_root)), "root")))
.expect_err("the admin attesting for alice is not alice attesting");
assert_eq!(status.code(), Code::InvalidArgument, "{status}");
assert_eq!(keys_of("alice"), 1);
// RED 3: alice attested a DIFFERENT key than the one being added.
let other = key();
let for_other = attest(&alices, "alice", &fingerprint(&other));
let status = now(access.add_key(as_caller(add("alice", &second, Some(for_other)), "root")))
.expect_err("an attestation for one key may not be spent on another");
assert_eq!(status.code(), Code::InvalidArgument, "{status}");
assert_eq!(keys_of("alice"), 1);
// RED 4: forged — alice's fingerprint on root's signature.
let mut forged = attest(&roots, "alice", &fingerprint(&second));
forged.key_fingerprint = fingerprint(&alices);
let status = now(access.add_key(as_caller(add("alice", &second, Some(forged)), "root")))
.expect_err("a signature that does not verify must be refused");
assert_eq!(status.code(), Code::InvalidArgument, "{status}");
assert_eq!(keys_of("alice"), 1);
// GREEN: alice's honest attestation, made with the key she holds.
let honest = attest(&alices, "alice", &fingerprint(&second));
let added = now(access.add_key(as_caller(add("alice", &second, Some(honest)), "root")))
.expect("the target's own attestation is accepted")
.into_inner()
.key
.unwrap();
assert_eq!(added.principal_id, "alice");
// Applied output: the key authenticates as alice on the table the SSH
// listener reads.
let fp = Fingerprint::parse(&fingerprint(&second)).unwrap();
assert_eq!(
auth.principal_by_fingerprint(&fp)
.expect("read")
.map(|p| p.id),
Some("alice".to_owned())
);
assert_eq!(keys_of("alice"), 2);
// GREEN: the target adding to themselves needs no attestation — the call
// is already their signed word. (alice needs Admin to reach AddKey at all;
// that rule is unchanged.)
auth.grant(Grant {
principal: "alice".into(),
scope: Scope::Server,
level: Level::Admin,
})
.expect("alice is admin now");
let third = key();
now(access.add_key(as_caller(add("alice", &third, None), "alice")))
.expect("a principal adding to themselves is the attestation");
assert_eq!(keys_of("alice"), 3);
}