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

@ -1035,6 +1035,134 @@ fn init_owned_node_records_signed_owner_device_and_node() {
}
}
#[test]
fn node_enrollment_request_import_and_approval_records_signed_ops() {
if Command::new("ssh-keygen").arg("-?").output().is_err() {
return;
}
let owner_home = tempfile::tempdir().expect("owner tempdir");
let requester_home = tempfile::tempdir().expect("requester tempdir");
let owner_paths = geth_config::GethPaths::from_home(owner_home.path());
let requester_paths = geth_config::GethPaths::from_home(requester_home.path());
let admin_key_path = owner_home.path().join("owner_ed25519");
let status = Command::new("ssh-keygen")
.arg("-q")
.arg("-t")
.arg("ed25519")
.arg("-N")
.arg("")
.arg("-f")
.arg(&admin_key_path)
.status()
.expect("generate owner ssh key");
assert!(status.success());
let owner = geth_node::init_owned_node(
&owner_paths,
geth_node::InitOwnerOptions {
admin_key_path: Some(admin_key_path.with_extension("pub")),
signing_key_path: Some(admin_key_path.clone()),
owner_name: "Eric".to_owned(),
node_name: "owner-laptop".to_owned(),
capabilities: Vec::new(),
},
)
.expect("init owner");
let requester = geth_node::init_node(&requester_paths).expect("init requester");
let request_path = requester_home.path().join("enrollment.json");
let response = geth_node::handle_request(
&requester,
geth_control::ControlRequest::NodeEnrollRequest {
node_name: "workstation".to_owned(),
capabilities: vec!["resource:ssh-proxy:local=ssh_proxy.connect".to_owned()],
reason: Some("new test machine".to_owned()),
out: Some(request_path.clone()),
},
)
.expect("create enrollment request");
let request_id = match response {
geth_control::ControlResponse::NodeEnrollmentRequested { request, .. } => {
assert_eq!(request.requested_node_name, "workstation");
assert!(request.provenance.is_some());
request.id.to_string()
}
other => panic!("unexpected response: {other:?}"),
};
assert!(request_path.exists());
let imported = geth_node::handle_request(
&owner,
geth_control::ControlRequest::NodeEnrollImport { path: request_path },
)
.expect("import request");
match imported {
geth_control::ControlResponse::NodeEnrollmentImported { request, .. } => {
assert_eq!(request.id.as_str(), request_id);
assert_eq!(request.status.to_string(), "pending");
}
other => panic!("unexpected response: {other:?}"),
}
let approved = geth_node::handle_request(
&owner,
geth_control::ControlRequest::NodeEnrollApprove {
request_id: request_id.clone(),
signing_key_path: admin_key_path,
admin_key_path: None,
node_name: None,
capabilities: vec!["resource:cas:local=cas.fetch".to_owned()],
},
)
.expect("approve request");
match approved {
geth_control::ControlResponse::NodeEnrollmentApproved {
keychain_ops,
keychain_signatures,
auth_ops,
auth_signatures,
..
} => {
assert!(keychain_ops.len() >= 3);
assert_eq!(keychain_signatures.len(), keychain_ops.len());
assert_eq!(auth_ops.len(), 2);
assert_eq!(auth_signatures.len(), 2);
}
other => panic!("unexpected response: {other:?}"),
}
match geth_node::handle_request(&owner, geth_control::ControlRequest::NodeList)
.expect("node list")
{
geth_control::ControlResponse::NodeList { nodes, .. } => {
assert!(nodes.iter().any(|node| node.name == "workstation"));
}
other => panic!("unexpected response: {other:?}"),
}
match geth_node::handle_request(
&owner,
geth_control::ControlRequest::AuthExplain {
subject: requester.node_id.clone(),
resource: "resource:ssh-proxy:local".to_owned(),
capability: "ssh_proxy.connect".to_owned(),
},
)
.expect("explain enrollment grant")
{
geth_control::ControlResponse::AuthExplain(explanation) => assert!(explanation.allowed),
other => panic!("unexpected response: {other:?}"),
}
let store = geth_store::Store::open(&owner_paths.metadata_db()).expect("open store");
assert_eq!(
store
.list_auth_signatures()
.expect("list auth signatures")
.len(),
2
);
}
#[test]
fn db_add_and_status_register_local_db_metadata() {
let home = tempfile::tempdir().expect("tempdir");