Add guided node enrollment
This commit is contained in:
parent
678865bfdc
commit
ce3e029b3d
8 changed files with 490 additions and 18 deletions
|
|
@ -174,21 +174,26 @@ const GUIDE_ENROLLMENT: &str = r#"Add another node/device:
|
|||
|
||||
On the new node:
|
||||
geth init
|
||||
geth keychain init --admin-key ~/.ssh/id_ed25519_sk.pub
|
||||
geth peer import /tmp/owner.peer.json
|
||||
geth node enroll request --node-name workstation --out /tmp/workstation-enrollment.json
|
||||
geth node enroll submit owner --path /tmp/workstation-enrollment.json
|
||||
geth daemon install
|
||||
geth node enroll join /tmp/owner.peer.json \
|
||||
--admin-key /tmp/owner-admin.pub \
|
||||
--node-name workstation
|
||||
|
||||
On the owner/YubiKey machine:
|
||||
geth node enroll list
|
||||
geth node enroll approve <request-id> --signing-key ~/.ssh/id_ed25519_sk
|
||||
|
||||
Back on the new node:
|
||||
geth sync now owner
|
||||
geth node list
|
||||
geth node enroll sync <owner-node-id>
|
||||
geth keychain status
|
||||
|
||||
Enrollment approval records signed keychain/auth operations. Discovery and peer
|
||||
cards alone never grant trust or capabilities.
|
||||
cards alone never grant trust or capabilities. The join command combines peer
|
||||
import, explicit admin public-key trust bootstrap, signed request creation, and
|
||||
Iroh submission; it does not approve the request. Verify the admin public key
|
||||
through a separate trusted channel before using it. For offline transfer or
|
||||
recovery, the lower-level `request`, `submit`, and `import` commands remain
|
||||
available.
|
||||
"#;
|
||||
|
||||
const GUIDE_KEYS: &str = r#"Key terminology:
|
||||
|
|
@ -819,6 +824,23 @@ pub enum NodeCommand {
|
|||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum NodeEnrollCommand {
|
||||
/// Import an owner peer card, create a request, and submit it in one step
|
||||
#[command(
|
||||
after_help = "Example:\n geth node enroll join owner.peer.json --admin-key owner-admin.pub --node-name workstation --capability resource:kv:preferences=kv.read\n\nThe explicitly supplied admin public key bootstraps trust for later approval sync. Peer-card import supplies candidate routing metadata only. Owner approval remains a separate, admin-signed action."
|
||||
)]
|
||||
Join {
|
||||
peer_card_path: PathBuf,
|
||||
#[arg(long)]
|
||||
admin_key: PathBuf,
|
||||
#[arg(long)]
|
||||
node_name: String,
|
||||
#[arg(long = "capability")]
|
||||
capabilities: Vec<String>,
|
||||
#[arg(long)]
|
||||
reason: Option<String>,
|
||||
#[arg(long)]
|
||||
out: Option<PathBuf>,
|
||||
},
|
||||
/// Create a signed enrollment request on the new node
|
||||
#[command(
|
||||
after_help = "Examples:\n geth node enroll request --node-name workstation --out workstation.enroll.json\n geth node enroll request --node-name ci-runner --capability resource:kv:builds=kv.read"
|
||||
|
|
@ -1639,6 +1661,18 @@ fn argument_help(path: &str, id: &str) -> Option<&'static str> {
|
|||
("geth node enroll request", "capabilities") => {
|
||||
Some("Requested RESOURCE=CAPABILITY pair; repeat to request more than one")
|
||||
}
|
||||
("geth node enroll join", "peer_card_path") => {
|
||||
Some("Signed owner peer-card JSON file to import as an untrusted candidate")
|
||||
}
|
||||
("geth node enroll join", "admin_key") => {
|
||||
Some("Owner OpenSSH admin public key used as the explicit trust anchor")
|
||||
}
|
||||
("geth node enroll join", "capabilities") => {
|
||||
Some("Requested RESOURCE=CAPABILITY pair; repeat to request more than one")
|
||||
}
|
||||
("geth node enroll join" | "geth node enroll request", "out") => {
|
||||
Some("Optional path for a portable enrollment-request JSON copy")
|
||||
}
|
||||
("geth kv create" | "geth kv set" | "geth kv get" | "geth kv sync", "name") => {
|
||||
Some("Name of the KV store")
|
||||
}
|
||||
|
|
@ -2244,6 +2278,21 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
|
|||
Command::Node {
|
||||
command: NodeCommand::Enroll { command },
|
||||
} => match command {
|
||||
NodeEnrollCommand::Join {
|
||||
peer_card_path,
|
||||
admin_key,
|
||||
node_name,
|
||||
capabilities,
|
||||
reason,
|
||||
out,
|
||||
} => ControlRequest::NodeEnrollJoin {
|
||||
peer_card_path,
|
||||
admin_key_path: admin_key,
|
||||
node_name,
|
||||
capabilities,
|
||||
reason,
|
||||
out,
|
||||
},
|
||||
NodeEnrollCommand::Request {
|
||||
node_name,
|
||||
capabilities,
|
||||
|
|
@ -4397,6 +4446,28 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> {
|
|||
}
|
||||
println!("note: {note}");
|
||||
}
|
||||
ControlResponse::NodeEnrollmentJoined {
|
||||
peer,
|
||||
request,
|
||||
out,
|
||||
owner_node_id,
|
||||
admin_key_id,
|
||||
accepted,
|
||||
note,
|
||||
} => {
|
||||
println!("imported owner candidate: {}", peer.card.node_id);
|
||||
println!("trust: candidate-only");
|
||||
println!("admin trust anchor: {admin_key_id}");
|
||||
println!("node enrollment request: {}", request.id);
|
||||
println!("requested_name: {}", request.requested_node_name);
|
||||
if let Some(out) = out {
|
||||
println!("written: {}", out.display());
|
||||
}
|
||||
println!("submitted_to: {owner_node_id}");
|
||||
println!("accepted: {accepted}");
|
||||
println!("next: the owner reviews and approves this request with an admin key");
|
||||
println!("note: {note}");
|
||||
}
|
||||
ControlResponse::NodeEnrollmentSubmitted {
|
||||
request_id,
|
||||
owner_node_id,
|
||||
|
|
@ -5349,6 +5420,32 @@ mod tests {
|
|||
assert_eq!(config.sync.live_sync_interval_ms, 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guided_enrollment_join_parses_the_common_new_node_workflow() {
|
||||
let parsed = Cli::try_parse_from([
|
||||
"geth",
|
||||
"node",
|
||||
"enroll",
|
||||
"join",
|
||||
"owner.peer.json",
|
||||
"--admin-key",
|
||||
"owner-admin.pub",
|
||||
"--node-name",
|
||||
"workstation",
|
||||
"--capability",
|
||||
"resource:kv:preferences=kv.read",
|
||||
])
|
||||
.expect("parse guided enrollment");
|
||||
assert!(matches!(
|
||||
parsed.command,
|
||||
Command::Node {
|
||||
command: NodeCommand::Enroll {
|
||||
command: NodeEnrollCommand::Join { .. }
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ephemeral_daemon_rejects_an_explicit_persistent_home() {
|
||||
let parsed = Cli::try_parse_from([
|
||||
|
|
|
|||
Loading…
Reference in a new issue