Add smooth node enrollment flow

This commit is contained in:
Eric Wendland 2026-05-21 18:01:38 +02:00
commit 27a79768e4
12 changed files with 1662 additions and 22 deletions

View file

@ -1,9 +1,9 @@
use geth_auth::{AuthExplanation, AuthOp};
use geth_auth::{AuthExplanation, AuthOp, AuthOpSignature};
use geth_cas::{FileConflict, FileRoot, FileRootScan};
use geth_db::{CrSqliteChangeBatch, DbResource};
use geth_discovery::{DiscoveredPeer, PeerCard};
use geth_document::{DocumentResource, DocumentState};
use geth_keychain::{KeychainOp, KeychainOpSignature, NodeRecord};
use geth_keychain::{KeychainOp, KeychainOpSignature, NodeEnrollmentRequest, NodeRecord};
use geth_kv::{KvEntry, KvResource, KvSyncEntry};
use geth_pipe::{PipeConnection, PipeListener, PipeMessage};
use geth_pubsub::PubsubMessage;
@ -137,6 +137,33 @@ pub enum ControlRequest {
resource: String,
grant_id: String,
},
NodeEnrollRequest {
node_name: String,
capabilities: Vec<String>,
reason: Option<String>,
out: Option<PathBuf>,
},
NodeEnrollSubmit {
owner_node: String,
request_id: Option<String>,
path: Option<PathBuf>,
},
NodeEnrollImport {
path: PathBuf,
},
NodeEnrollList {
status: Option<String>,
},
NodeEnrollApprove {
request_id: String,
signing_key_path: PathBuf,
admin_key_path: Option<PathBuf>,
node_name: Option<String>,
capabilities: Vec<String>,
},
NodeEnrollSync {
owner_node: String,
},
KeychainInit {
admin_key_path: Option<PathBuf>,
signing_key_path: Option<PathBuf>,
@ -145,6 +172,9 @@ pub enum ControlRequest {
KeychainSync {
node: String,
},
AuthSync {
node: String,
},
SecretStatus,
SecretCreate {
resource: String,
@ -530,6 +560,15 @@ pub enum ControlResponse {
invalid_ops_rejected: usize,
note: String,
},
AuthSynced {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
ops_imported: usize,
signatures_imported: usize,
invalid_ops_rejected: usize,
note: String,
},
SecretStatus {
secrets: Vec<ResourceMasterSecret>,
},
@ -574,6 +613,43 @@ pub enum ControlResponse {
},
NodeGrantUpdated {
op: AuthOp,
signatures: Vec<AuthOpSignature>,
note: String,
},
NodeEnrollmentRequested {
request: NodeEnrollmentRequest,
out: Option<PathBuf>,
note: String,
},
NodeEnrollmentSubmitted {
request_id: String,
owner_node_id: String,
accepted: bool,
note: String,
},
NodeEnrollmentImported {
request: NodeEnrollmentRequest,
note: String,
},
NodeEnrollmentList {
requests: Vec<NodeEnrollmentRequest>,
note: String,
},
NodeEnrollmentApproved {
request: NodeEnrollmentRequest,
keychain_ops: Vec<KeychainOp>,
keychain_signatures: Vec<KeychainOpSignature>,
auth_ops: Vec<AuthOp>,
auth_signatures: Vec<AuthOpSignature>,
note: String,
},
NodeEnrollmentSynced {
owner_node: String,
keychain_ops_imported: usize,
keychain_signatures_imported: usize,
auth_ops_imported: usize,
auth_signatures_imported: usize,
invalid_ops_rejected: usize,
note: String,
},
SshCertRequested {
@ -860,6 +936,15 @@ pub enum PeerControlRequest {
peer_card: PeerCard,
nonce: String,
},
AuthSync {
peer_card: PeerCard,
nonce: String,
},
NodeEnrollmentSubmit {
peer_card: PeerCard,
request: NodeEnrollmentRequest,
nonce: String,
},
CasFetch {
peer_card: PeerCard,
hash: BlobHash,
@ -988,6 +1073,26 @@ pub enum PeerControlResponse {
nonce: String,
note: String,
},
AuthSynced {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
ops: Vec<AuthOp>,
signatures: Vec<AuthOpSignature>,
nonce: String,
note: String,
},
NodeEnrollmentSubmitted {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
request_id: String,
accepted: bool,
nonce: String,
note: String,
},
CasFetched {
node_id: String,
agent_id: String,
@ -1318,6 +1423,25 @@ mod tests {
request
);
let request = ControlRequest::NodeEnrollRequest {
node_name: "workstation".to_owned(),
capabilities: vec!["resource:ssh-proxy:local=ssh_proxy.connect".to_owned()],
reason: Some("new machine".to_owned()),
out: Some(PathBuf::from("enrollment.json")),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let request = ControlRequest::NodeEnrollSync {
owner_node: "owner-laptop".to_owned(),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let request = ControlRequest::KeychainSync {
node: "work-laptop".to_owned(),
};
@ -1371,6 +1495,34 @@ mod tests {
response
);
let response = ControlResponse::NodeEnrollmentSynced {
owner_node: "owner-laptop".to_owned(),
keychain_ops_imported: 3,
keychain_signatures_imported: 3,
auth_ops_imported: 1,
auth_signatures_imported: 1,
invalid_ops_rejected: 0,
note: "synced".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::AuthSynced {
peer_node_id: "node:peer".to_owned(),
peer_agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
ops_imported: 1,
signatures_imported: 1,
invalid_ops_rejected: 0,
note: "trusted admin signatures only".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let request = ControlRequest::SecretBearerVerify {
secret: "bearer:test".to_owned(),
resource: "resource:cas:local".to_owned(),