From 788522404dd83d93d0a05d82949b5f7be24d5025 Mon Sep 17 00:00:00 2001 From: Eric Wendland Date: Tue, 19 May 2026 16:05:57 +0200 Subject: [PATCH] Report keychain signature status --- README.md | 5 +++-- crates/geth-cli/src/lib.rs | 1 + crates/geth-control/src/lib.rs | 1 + crates/geth-node/src/lib.rs | 1 + crates/geth/tests/bootstrap.rs | 10 ++++++++++ docs/architecture.md | 5 +++-- docs/roadmap.md | 1 + 7 files changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 11bf760..3716059 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,9 @@ Most non-daemon commands talk to the daemon through a local Unix socket at the keychain initialization/admin-key operations and signs their canonical payloads through `ssh-keygen -Y sign` using the `geth.keychain.v1@geth.local` namespace. This is the bootstrap path for -admin/YubiKey-rooted trust; signature verification for replicated keychain ops is -still future work. +admin/YubiKey-rooted trust. `geth keychain status` reports the number of stored +keychain signatures; signature verification for replicated keychain ops is still +future work. The daemon can also install itself as a user service: diff --git a/crates/geth-cli/src/lib.rs b/crates/geth-cli/src/lib.rs index 28e2693..35de9ef 100644 --- a/crates/geth-cli/src/lib.rs +++ b/crates/geth-cli/src/lib.rs @@ -1102,6 +1102,7 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> { ControlResponse::KeychainStatus(status) => { println!("initialized: {}", status.initialized); println!("admin_keys: {}", status.admin_keys); + println!("signatures: {}", status.signatures); println!("users: {}", status.users); println!("devices: {}", status.devices); println!("nodes: {}", status.nodes); diff --git a/crates/geth-control/src/lib.rs b/crates/geth-control/src/lib.rs index 2feba66..e691015 100644 --- a/crates/geth-control/src/lib.rs +++ b/crates/geth-control/src/lib.rs @@ -592,6 +592,7 @@ pub struct NodeIdResponse { pub struct KeychainStatusResponse { pub initialized: bool, pub admin_keys: usize, + pub signatures: usize, pub users: usize, pub devices: usize, pub nodes: usize, diff --git a/crates/geth-node/src/lib.rs b/crates/geth-node/src/lib.rs index cc02a1d..500d25e 100644 --- a/crates/geth-node/src/lib.rs +++ b/crates/geth-node/src/lib.rs @@ -2914,6 +2914,7 @@ pub fn handle_request( Ok(ControlResponse::KeychainStatus(KeychainStatusResponse { initialized: view.initialized, admin_keys: view.admin_keys.len(), + signatures: store.list_keychain_signatures()?.len(), users: view.users.len(), devices: view.devices.len(), nodes: view.nodes.len(), diff --git a/crates/geth/tests/bootstrap.rs b/crates/geth/tests/bootstrap.rs index 948c8cf..995d8e1 100644 --- a/crates/geth/tests/bootstrap.rs +++ b/crates/geth/tests/bootstrap.rs @@ -709,6 +709,7 @@ fn keychain_init_and_status_use_local_keychain_log() { geth_control::ControlResponse::KeychainStatus(status) => { assert!(status.initialized); assert_eq!(status.admin_keys, 1); + assert_eq!(status.signatures, 0); assert_eq!(status.users, 0); } other => panic!("unexpected response: {other:?}"), @@ -762,6 +763,15 @@ fn keychain_init_can_record_openssh_signatures() { .list_keychain_signatures() .expect("list signatures"); assert_eq!(signatures.len(), 2); + + let response = geth_node::handle_request(&node, geth_control::ControlRequest::KeychainStatus) + .expect("keychain status"); + match response { + geth_control::ControlResponse::KeychainStatus(status) => { + assert_eq!(status.signatures, 2); + } + other => panic!("unexpected response: {other:?}"), + } } #[test] diff --git a/docs/architecture.md b/docs/architecture.md index d9e60c7..18bf4e5 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -238,8 +238,9 @@ daemon persists local keychain init/admin-key operations and `keychain status` reports the reduced local view. `keychain init --signing-key ` writes the canonical keychain signing payloads, runs `ssh-keygen -Y sign` with the explicit `geth.keychain.v1@geth.local` namespace, and stores the resulting OpenSSH -signatures in local SQLite. Verification and rejection of unsigned replicated -keychain operations are still future work. +signatures in local SQLite. `keychain status` reports the stored signature +count. Verification and rejection of unsigned replicated keychain operations are +still future work. The authorization plane is `geth-auth`: resource-local signed operation logs, grants, revocations, groups, and `auth explain`. Auth operations reduce into a diff --git a/docs/roadmap.md b/docs/roadmap.md index 5598ef3..270933a 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -174,6 +174,7 @@ resource-scoped capability decisions. - `[x]` OpenSSH keychain signatures use the explicit `geth.keychain.v1@geth.local` namespace. - `[x]` Keychain OpenSSH signatures are stored in local SQLite. + - `[x]` `geth keychain status` reports the stored keychain signature count. - `[x]` Missing `ssh-keygen` or unavailable hardware keys produce clear errors during signing. - `[x]` Tests cover signed keychain init with a generated local OpenSSH key