Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/mesh-llm-commands/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ nix = { version = "0.29.0", default-features = false, features = ["signal"] }
mesh-llm-build-info.workspace = true
mesh-llm-cli = { path = "../mesh-llm-cli", version = "0.72.1" }
mesh-llm-config = { path = "../mesh-llm-config", version = "0.72.1" }
mesh-llm-events = { path = "../mesh-llm-events", version = "0.72.1" }
mesh-llm-native-runtime = { path = "../mesh-llm-native-runtime", version = "0.72.1" }
mesh-llm-plugin-manager = { path = "../mesh-llm-plugin-manager", version = "0.72.1" }
mesh-llm-identity = { path = "../mesh-llm-identity", version = "0.72.1", features = ["host-io"] }
Expand Down
87 changes: 82 additions & 5 deletions crates/mesh-llm-commands/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::result::Result as StdResult;
use anyhow::{Context, Result, bail};
use iroh::{EndpointId, SecretKey};
use mesh_llm_cli::{AuthCommand, TrustCommand};
use mesh_llm_events::audit::{audit_events, emit_audit};
use mesh_llm_identity::{
KEYCHAIN_SERVICE, OwnerKeychainLoadError, OwnerKeypair, SignedNodeOwnership, TrustPolicy,
TrustStore, default_keystore_path, default_node_key_path, default_node_ownership_path,
Expand Down Expand Up @@ -332,6 +333,14 @@ pub(crate) fn run_init(
};
let encrypted = !matches!(source, PassphraseSource::None);

let _ = emit_audit(audit_events::admin_action(
Some(owner_id.clone()),
"auth_init",
Some("owner_keystore"),
true,
None,
));

eprintln!();
eprintln!("Owner keystore created.");
eprintln!("Owner ID: {owner_id}");
Expand Down Expand Up @@ -533,6 +542,14 @@ pub(crate) fn run_sign_node(
)?;
save_node_ownership(&output_path, &ownership)?;

let _ = emit_audit(audit_events::admin_action(
Some(owner.owner_id().to_string()),
"auth_sign_node",
Some(&ownership.claim.node_endpoint_id),
true,
None,
));

eprintln!(
"Signed node certificate written to {}",
output_path.display()
Expand Down Expand Up @@ -664,6 +681,13 @@ pub(crate) const RUN_ROTATE_NODE: RunRotateNodeFn =

let owner_key_path = resolve_owner_key_path(owner_key)?;
if !owner_key_path.exists() {
let _ = emit_audit(audit_events::admin_action(
Some("system".to_string()),
"auth_rotate_node",
Some(&new_node_id),
true,
None,
));
eprintln!("No owner keystore found at {}", owner_key_path.display());
eprintln!(
"Run `mesh-llm auth init` or `mesh-llm auth sign-node` later to attest this node."
Expand All @@ -680,6 +704,15 @@ pub(crate) const RUN_ROTATE_NODE: RunRotateNodeFn =
hostname_hint,
)?;
save_node_ownership(&certificate_path, &ownership)?;

let _ = emit_audit(audit_events::admin_action(
Some(owner.owner_id().to_string()),
"auth_rotate_node",
Some(&new_node_id),
true,
None,
));
Comment thread
ndizazzo marked this conversation as resolved.

eprintln!("New node certificate: {}", certificate_path.display());
eprintln!("New cert ID: {}", ownership.claim.cert_id);

Expand All @@ -699,6 +732,14 @@ pub(crate) fn run_revoke_owner(
trust_store.revoke_owner(owner_id.clone(), reason);
save_trust_store(&trust_store_path, &trust_store)?;

let _ = emit_audit(audit_events::admin_action(
Some("system".to_string()),
"auth_revoke_owner",
Some(&owner_id),
true,
None,
));

eprintln!("Revoked owner {owner_id} in {}", trust_store_path.display());
Ok(())
}
Expand All @@ -716,17 +757,45 @@ pub(crate) fn run_revoke_node(
let trust_store_path = resolve_trust_store_path(trust_store)?;
let mut trust_store = load_effective_trust_store(&trust_store_path)?;

if let Some(cert_id) = cert_id {
// Validate every requested identifier before mutating the in-memory store.
// Mixed cert/node revocation is all-or-nothing.
let normalized_node_id = node_id
.as_deref()
.map(parse_node_id_hex)
.transpose()?
.map(hex::encode);

if let Some(cert_id) = cert_id.as_ref() {
trust_store.revoke_node_cert(cert_id.clone(), reason.clone());
eprintln!("Revoked cert ID {cert_id}");
}
if let Some(node_id) = node_id {
let normalized = hex::encode(parse_node_id_hex(&node_id)?);
if let Some(normalized) = normalized_node_id.as_ref() {
trust_store.revoke_node_id(normalized.clone(), reason);
eprintln!("Revoked node ID {normalized}");
}

// Persistence is the commit point. Emit success output and audit records
// only after every requested revocation is durable.
save_trust_store(&trust_store_path, &trust_store)?;

if let Some(cert_id) = cert_id.as_ref() {
let _ = emit_audit(audit_events::admin_action(
Some("system".to_string()),
"auth_revoke_node_cert",
Some(cert_id),
true,
None,
));
eprintln!("Revoked cert ID {cert_id}");
}
if let Some(normalized) = normalized_node_id.as_ref() {
let _ = emit_audit(audit_events::admin_action(
Some("system".to_string()),
"auth_revoke_node_id",
Some(normalized),
true,
None,
));
eprintln!("Revoked node ID {normalized}");
}
eprintln!("Updated trust store {}", trust_store_path.display());
Ok(())
}
Expand Down Expand Up @@ -766,6 +835,14 @@ pub(crate) fn run_rotate_owner(
true,
)?;

let _ = emit_audit(audit_events::admin_action(
Some(new_keypair.owner_id().to_string()),
"auth_rotate_owner",
Some("owner_keystore"),
true,
None,
));

eprintln!("Rotated owner keystore at {}", owner_key_path.display());
eprintln!("New owner ID: {}", new_keypair.owner_id());
if let Some(backup_path) = backup_path {
Expand Down
44 changes: 44 additions & 0 deletions crates/mesh-llm-commands/src/auth/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use super::*;
use serial_test::serial;

fn temporary_auth_test_dir(label: &str) -> std::path::PathBuf {
let path =
std::env::temp_dir().join(format!("mesh-llm-auth-{label}-{}", rand::random::<u64>()));
std::fs::create_dir_all(&path).unwrap();
path
}

#[test]
fn defaults_to_keychain_for_new_keystore_when_available() {
assert!(should_default_to_keychain(false, false, true));
Expand Down Expand Up @@ -135,3 +142,40 @@ fn init_defaults_to_keychain_then_load_round_trip() {
mesh_llm_identity::keychain_delete(KEYCHAIN_SERVICE, &account).ok();
std::fs::remove_dir_all(&dir).ok();
}

#[test]
fn revoke_node_validates_all_inputs_before_persisting() {
let dir = temporary_auth_test_dir("revoke-validation");
let trust_store_path = dir.join("trust-store.json");
save_trust_store(&trust_store_path, &TrustStore::default()).unwrap();
let before = std::fs::read(&trust_store_path).unwrap();

let result = run_revoke_node(
Some("valid-cert".to_owned()),
Some("not-a-node-id".to_owned()),
Some("compromised".to_owned()),
Some(trust_store_path.clone()),
);

assert!(result.is_err());
assert_eq!(std::fs::read(&trust_store_path).unwrap(), before);
std::fs::remove_dir_all(dir).ok();
}

#[test]
fn revoke_node_save_failure_leaves_blocking_file_unchanged() {
let dir = temporary_auth_test_dir("revoke-save-failure");
let blocking_file = dir.join("blocker");
std::fs::write(&blocking_file, b"not a directory").unwrap();

let result = run_revoke_node(
Some("valid-cert".to_owned()),
None,
Some("compromised".to_owned()),
Some(blocking_file.join("trust-store.json")),
);

assert!(result.is_err());
assert_eq!(std::fs::read(&blocking_file).unwrap(), b"not a directory");
std::fs::remove_dir_all(dir).ok();
}
1 change: 1 addition & 0 deletions crates/mesh-llm-commands/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod benchmark;
pub mod config;
pub mod gpus;
pub mod model_package;
pub mod operational_logging;
pub mod plugin;
pub mod runtime_native;
pub mod setup;
Expand Down
Loading
Loading