2026-05-16 16:32:03 +02:00
|
|
|
use geth_auth::{AuthExplanation, AuthOp};
|
2026-05-15 15:08:20 +02:00
|
|
|
use geth_resource::ResourceDescriptor;
|
2026-05-16 00:17:08 +02:00
|
|
|
use geth_ssh_identity::{
|
|
|
|
|
SshCertApproval, SshCertRequest, SshCertificateRecord, SshRevocationEntry,
|
|
|
|
|
};
|
2026-05-15 15:08:20 +02:00
|
|
|
use geth_types::BlobHash;
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
|
|
|
|
pub enum ControlRequest {
|
|
|
|
|
Status,
|
|
|
|
|
NodeId,
|
|
|
|
|
ResourceList,
|
|
|
|
|
ResourceCreate {
|
|
|
|
|
kind: String,
|
|
|
|
|
name: String,
|
|
|
|
|
},
|
|
|
|
|
CasAdd {
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
},
|
|
|
|
|
CasGet {
|
|
|
|
|
hash: BlobHash,
|
|
|
|
|
out: PathBuf,
|
|
|
|
|
},
|
|
|
|
|
CasHash {
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
},
|
|
|
|
|
CasHas {
|
|
|
|
|
hash: BlobHash,
|
|
|
|
|
},
|
|
|
|
|
CasList,
|
|
|
|
|
KeychainStatus,
|
|
|
|
|
AuthExplain {
|
|
|
|
|
subject: String,
|
|
|
|
|
resource: String,
|
|
|
|
|
capability: String,
|
|
|
|
|
},
|
2026-05-16 16:32:03 +02:00
|
|
|
AuthGrant {
|
|
|
|
|
subject: String,
|
|
|
|
|
resource: String,
|
|
|
|
|
capability: String,
|
|
|
|
|
grant_id: Option<String>,
|
|
|
|
|
},
|
|
|
|
|
AuthRevoke {
|
|
|
|
|
resource: String,
|
|
|
|
|
grant_id: String,
|
|
|
|
|
},
|
2026-05-16 00:17:08 +02:00
|
|
|
SshCertRequest {
|
|
|
|
|
public_key_path: PathBuf,
|
|
|
|
|
cert_kind: String,
|
|
|
|
|
principals: Vec<String>,
|
|
|
|
|
requested_validity: Option<String>,
|
|
|
|
|
renewal_of: Option<String>,
|
|
|
|
|
reason: Option<String>,
|
|
|
|
|
},
|
|
|
|
|
SshCertRequests,
|
|
|
|
|
SshCertApprove {
|
|
|
|
|
request_id: String,
|
|
|
|
|
ca_key_path: PathBuf,
|
|
|
|
|
valid_for: Option<String>,
|
|
|
|
|
serial: Option<u64>,
|
|
|
|
|
out: Option<PathBuf>,
|
|
|
|
|
},
|
|
|
|
|
SshCertImport {
|
|
|
|
|
request_id: String,
|
|
|
|
|
cert_path: PathBuf,
|
|
|
|
|
},
|
|
|
|
|
SshCertList,
|
|
|
|
|
SshRevocationAdd {
|
|
|
|
|
kind: String,
|
|
|
|
|
target: String,
|
|
|
|
|
reason: Option<String>,
|
|
|
|
|
},
|
|
|
|
|
SshRevocationList,
|
|
|
|
|
SshRevocationExport {
|
|
|
|
|
out: PathBuf,
|
|
|
|
|
},
|
2026-05-15 15:08:20 +02:00
|
|
|
ModuleStub {
|
|
|
|
|
module: String,
|
|
|
|
|
command: String,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
|
|
|
|
pub enum ControlResponse {
|
|
|
|
|
Status(StatusResponse),
|
|
|
|
|
NodeId(NodeIdResponse),
|
|
|
|
|
ResourceList {
|
|
|
|
|
resources: Vec<ResourceDescriptor>,
|
|
|
|
|
},
|
|
|
|
|
ResourceCreated {
|
|
|
|
|
resource: ResourceDescriptor,
|
|
|
|
|
},
|
|
|
|
|
CasAdded {
|
|
|
|
|
hash: BlobHash,
|
|
|
|
|
size_bytes: u64,
|
|
|
|
|
},
|
|
|
|
|
CasGot {
|
|
|
|
|
hash: BlobHash,
|
|
|
|
|
out: PathBuf,
|
|
|
|
|
size_bytes: u64,
|
|
|
|
|
},
|
|
|
|
|
CasHash {
|
|
|
|
|
hash: BlobHash,
|
|
|
|
|
},
|
|
|
|
|
CasHas {
|
|
|
|
|
hash: BlobHash,
|
|
|
|
|
present: bool,
|
|
|
|
|
},
|
|
|
|
|
CasList {
|
|
|
|
|
blobs: Vec<CasBlob>,
|
|
|
|
|
},
|
|
|
|
|
KeychainStatus(KeychainStatusResponse),
|
|
|
|
|
AuthExplain(AuthExplanation),
|
2026-05-16 16:32:03 +02:00
|
|
|
AuthOpRecorded {
|
|
|
|
|
op: AuthOp,
|
|
|
|
|
},
|
2026-05-16 00:17:08 +02:00
|
|
|
SshCertRequested {
|
|
|
|
|
request: SshCertRequest,
|
|
|
|
|
},
|
|
|
|
|
SshCertRequests {
|
|
|
|
|
requests: Vec<SshCertRequest>,
|
|
|
|
|
},
|
|
|
|
|
SshCertApproved {
|
|
|
|
|
approval: SshCertApproval,
|
|
|
|
|
},
|
|
|
|
|
SshCertImported {
|
|
|
|
|
certificate: SshCertificateRecord,
|
|
|
|
|
},
|
|
|
|
|
SshCertList {
|
|
|
|
|
requests: Vec<SshCertRequest>,
|
|
|
|
|
certificates: Vec<SshCertificateRecord>,
|
|
|
|
|
},
|
|
|
|
|
SshRevocationAdded {
|
|
|
|
|
revocation: SshRevocationEntry,
|
|
|
|
|
},
|
|
|
|
|
SshRevocationList {
|
|
|
|
|
revocations: Vec<SshRevocationEntry>,
|
|
|
|
|
},
|
|
|
|
|
SshRevocationExported {
|
|
|
|
|
out: PathBuf,
|
|
|
|
|
count: usize,
|
|
|
|
|
},
|
2026-05-15 15:08:20 +02:00
|
|
|
NotImplemented {
|
|
|
|
|
module: String,
|
|
|
|
|
command: String,
|
|
|
|
|
},
|
|
|
|
|
Error {
|
|
|
|
|
message: String,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub struct StatusResponse {
|
|
|
|
|
pub home: PathBuf,
|
|
|
|
|
pub socket: PathBuf,
|
|
|
|
|
pub agent_id: String,
|
|
|
|
|
pub node_id: String,
|
2026-05-16 01:54:00 +02:00
|
|
|
pub iroh_enabled: bool,
|
|
|
|
|
pub endpoint_id: Option<String>,
|
2026-05-16 03:17:45 +02:00
|
|
|
pub iroh_relay_mode: String,
|
2026-05-16 14:33:45 +02:00
|
|
|
pub iroh_local_discovery: bool,
|
2026-05-15 15:08:20 +02:00
|
|
|
pub iroh: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub struct NodeIdResponse {
|
|
|
|
|
pub agent_id: String,
|
|
|
|
|
pub node_id: String,
|
|
|
|
|
pub endpoint_id: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub struct KeychainStatusResponse {
|
|
|
|
|
pub initialized: bool,
|
|
|
|
|
pub admin_keys: usize,
|
|
|
|
|
pub users: usize,
|
|
|
|
|
pub devices: usize,
|
|
|
|
|
pub nodes: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub struct CasBlob {
|
|
|
|
|
pub hash: BlobHash,
|
|
|
|
|
pub size_bytes: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
|
pub enum ControlError {
|
|
|
|
|
#[error("json error: {0}")]
|
|
|
|
|
Json(#[from] serde_json::Error),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn encode_request(request: &ControlRequest) -> Result<String, ControlError> {
|
|
|
|
|
let mut line = serde_json::to_string(request)?;
|
|
|
|
|
line.push('\n');
|
|
|
|
|
Ok(line)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn decode_request(line: &str) -> Result<ControlRequest, ControlError> {
|
|
|
|
|
serde_json::from_str(line).map_err(ControlError::from)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn encode_response(response: &ControlResponse) -> Result<String, ControlError> {
|
|
|
|
|
let mut line = serde_json::to_string(response)?;
|
|
|
|
|
line.push('\n');
|
|
|
|
|
Ok(line)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn decode_response(line: &str) -> Result<ControlResponse, ControlError> {
|
|
|
|
|
serde_json::from_str(line).map_err(ControlError::from)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn control_request_response_serialization_roundtrip() {
|
|
|
|
|
let request = ControlRequest::CasHas {
|
|
|
|
|
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
|
|
|
|
request
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let response = ControlResponse::CasHas {
|
|
|
|
|
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
|
|
|
|
|
present: true,
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
|
|
|
|
|
response
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|