- Add hardware fingerprinting with cross-platform support - Implement secure device registration flow with X.509 certificates - Add WebSocket real-time communication for device status - Create comprehensive device management dashboard - Establish zero-trust security architecture with multi-layer protection - Add database migrations for device registration entities - Implement Rust edge client with hardware identification - Add certificate management and automated provisioning system 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
403 lines
14 KiB
Rust
403 lines
14 KiB
Rust
use clap::{Parser, Subcommand};
|
|
use anyhow::Result;
|
|
|
|
mod hardware;
|
|
mod config;
|
|
mod api;
|
|
mod events;
|
|
mod app;
|
|
mod camera;
|
|
mod detection;
|
|
mod storage;
|
|
mod communication;
|
|
mod hardware_fingerprint;
|
|
mod device_registration;
|
|
mod websocket_client;
|
|
|
|
use hardware::get_hardware_id;
|
|
use config::{Config, ConfigManager};
|
|
use api::ApiClient;
|
|
use app::Application;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "meteor-edge-client")]
|
|
#[command(about = "Meteor Edge Client - Running on edge hardware")]
|
|
#[command(version = "0.1.0")]
|
|
struct Cli {
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// Show version information
|
|
Version,
|
|
/// Register device with JWT token
|
|
Register {
|
|
/// JWT token for device registration
|
|
token: String,
|
|
/// Backend API URL (optional, defaults to http://localhost:3000)
|
|
#[arg(long, default_value = "http://localhost:3000")]
|
|
api_url: String,
|
|
},
|
|
/// Interactive device registration
|
|
RegisterDevice {
|
|
/// Backend API URL
|
|
#[arg(long, default_value = "http://localhost:3000")]
|
|
api_url: String,
|
|
/// Device name (optional)
|
|
#[arg(long)]
|
|
device_name: Option<String>,
|
|
/// Device location (latitude,longitude)
|
|
#[arg(long)]
|
|
location: Option<String>,
|
|
},
|
|
/// Test hardware fingerprinting
|
|
TestFingerprint,
|
|
/// Show device status and configuration
|
|
Status,
|
|
/// Check backend connectivity
|
|
Health {
|
|
/// Backend API URL (optional, defaults to http://localhost:3000)
|
|
#[arg(long, default_value = "http://localhost:3000")]
|
|
api_url: String,
|
|
},
|
|
/// Run the edge client application
|
|
Run,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
|
|
match &cli.command {
|
|
Commands::Version => {
|
|
println!("meteor-edge-client v{}", env!("CARGO_PKG_VERSION"));
|
|
}
|
|
Commands::Register { token, api_url } => {
|
|
if let Err(e) = register_device(token.clone(), api_url.clone()).await {
|
|
eprintln!("❌ Registration failed: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
Commands::RegisterDevice { api_url, device_name, location } => {
|
|
if let Err(e) = register_device_interactive(api_url.clone(), device_name.clone(), location.clone()).await {
|
|
eprintln!("❌ Device registration failed: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
Commands::TestFingerprint => {
|
|
if let Err(e) = test_hardware_fingerprint().await {
|
|
eprintln!("❌ Fingerprint test failed: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
Commands::Status => {
|
|
show_status().await?;
|
|
}
|
|
Commands::Health { api_url } => {
|
|
if let Err(e) = check_health(api_url.clone()).await {
|
|
eprintln!("❌ Health check failed: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
Commands::Run => {
|
|
if let Err(e) = run_application().await {
|
|
eprintln!("❌ Application failed: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Registers the device with the backend using the provided JWT token
|
|
async fn register_device(jwt_token: String, api_url: String) -> Result<()> {
|
|
println!("🚀 Starting device registration process...");
|
|
|
|
let config_manager = ConfigManager::new();
|
|
|
|
// Check if device is already registered
|
|
if config_manager.config_exists() {
|
|
match config_manager.load_config() {
|
|
Ok(config) if config.registered => {
|
|
println!("✅ Device is already registered!");
|
|
println!(" Hardware ID: {}", config.hardware_id);
|
|
if let Some(user_id) = &config.user_profile_id {
|
|
println!(" Device ID: {}", config.device_id);
|
|
println!(" User Profile ID: {}", user_id);
|
|
}
|
|
if let Some(registered_at) = &config.registered_at {
|
|
println!(" Registered at: {}", registered_at);
|
|
}
|
|
println!(" Config file: {:?}", config_manager.get_config_path());
|
|
return Ok(());
|
|
}
|
|
_ => {
|
|
println!("📝 Found existing config file, but device not fully registered. Continuing...");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get hardware ID
|
|
println!("🔍 Reading hardware identifier...");
|
|
let hardware_id = get_hardware_id()?;
|
|
println!(" Hardware ID: {}", hardware_id);
|
|
|
|
// Create API client
|
|
let api_client = ApiClient::new(api_url);
|
|
|
|
// Verify backend connectivity first
|
|
println!("🏥 Checking backend connectivity...");
|
|
api_client.health_check().await?;
|
|
|
|
// Attempt registration
|
|
println!("📡 Registering device with backend...");
|
|
let registration_response = api_client
|
|
.register_device(hardware_id.clone(), jwt_token.clone())
|
|
.await?;
|
|
|
|
// Save configuration
|
|
println!("💾 Saving registration configuration...");
|
|
let mut config = Config::new(hardware_id);
|
|
config.mark_registered(
|
|
registration_response.device.user_profile_id,
|
|
registration_response.device.id,
|
|
jwt_token,
|
|
);
|
|
|
|
config_manager.save_config(&config)?;
|
|
|
|
println!("🎉 Device registration completed successfully!");
|
|
println!(" Device ID: {}", config.device_id);
|
|
println!(" Config saved to: {:?}", config_manager.get_config_path());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Shows the current device status and configuration
|
|
async fn show_status() -> Result<()> {
|
|
println!("📊 Meteor Edge Client Status");
|
|
println!("============================");
|
|
|
|
// Show hardware information
|
|
match get_hardware_id() {
|
|
Ok(hardware_id) => {
|
|
println!("🔧 Hardware ID: {}", hardware_id);
|
|
}
|
|
Err(e) => {
|
|
println!("❌ Could not read hardware ID: {}", e);
|
|
}
|
|
}
|
|
|
|
// Show configuration status
|
|
let config_manager = ConfigManager::new();
|
|
println!("📁 Config file: {:?}", config_manager.get_config_path());
|
|
|
|
if config_manager.config_exists() {
|
|
match config_manager.load_config() {
|
|
Ok(config) => {
|
|
if config.registered {
|
|
println!("✅ Registration Status: REGISTERED");
|
|
println!(" Device ID: {}", config.device_id);
|
|
if let Some(user_id) = &config.user_profile_id {
|
|
println!(" User Profile ID: {}", user_id);
|
|
}
|
|
if let Some(registered_at) = &config.registered_at {
|
|
println!(" Registered at: {}", registered_at);
|
|
}
|
|
} else {
|
|
println!("⚠️ Registration Status: NOT REGISTERED");
|
|
println!(" Use 'register <token>' command to register this device");
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("❌ Could not load config: {}", e);
|
|
}
|
|
}
|
|
} else {
|
|
println!("⚠️ Registration Status: NOT REGISTERED");
|
|
println!(" No configuration file found");
|
|
println!(" Use 'register <token>' command to register this device");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Checks backend health and connectivity
|
|
async fn check_health(api_url: String) -> Result<()> {
|
|
println!("🏥 Checking backend health at: {}", api_url);
|
|
|
|
let api_client = ApiClient::new(api_url);
|
|
api_client.health_check().await?;
|
|
|
|
println!("✅ Backend is healthy and reachable!");
|
|
Ok(())
|
|
}
|
|
|
|
/// Run the main application
|
|
async fn run_application() -> Result<()> {
|
|
// Load configuration first
|
|
let config_manager = ConfigManager::new();
|
|
let config = if config_manager.config_exists() {
|
|
config_manager.load_config()?
|
|
} else {
|
|
eprintln!("❌ Device not registered. Use 'register <token>' command first.");
|
|
std::process::exit(1);
|
|
};
|
|
|
|
if !config.registered {
|
|
eprintln!("❌ Device not registered. Use 'register <token>' command first.");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
println!("🎯 Initializing Meteor Edge Client...");
|
|
|
|
// Create the application
|
|
let mut app = Application::new(1000);
|
|
|
|
println!("📊 Application Statistics:");
|
|
println!(" Event Bus Capacity: 1000");
|
|
println!(" Initial Subscribers: {}", app.subscriber_count());
|
|
|
|
// Run the application
|
|
app.run().await
|
|
}
|
|
|
|
/// Interactive device registration process
|
|
async fn register_device_interactive(api_url: String, device_name: Option<String>, location: Option<String>) -> Result<()> {
|
|
use device_registration::{DeviceRegistrationClient, RegistrationConfig, Location};
|
|
|
|
println!("🚀 Starting interactive device registration...");
|
|
|
|
// Parse location if provided
|
|
let parsed_location = if let Some(loc_str) = location {
|
|
let coords: Vec<&str> = loc_str.split(',').collect();
|
|
if coords.len() >= 2 {
|
|
if let (Ok(lat), Ok(lon)) = (coords[0].trim().parse::<f64>(), coords[1].trim().parse::<f64>()) {
|
|
Some(Location {
|
|
latitude: lat,
|
|
longitude: lon,
|
|
altitude: None,
|
|
accuracy: None
|
|
})
|
|
} else {
|
|
eprintln!("⚠️ Invalid location format. Use: latitude,longitude");
|
|
None
|
|
}
|
|
} else {
|
|
eprintln!("⚠️ Invalid location format. Use: latitude,longitude");
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Create registration configuration
|
|
let mut config = RegistrationConfig::default();
|
|
config.api_url = api_url;
|
|
config.device_name = device_name;
|
|
config.location = parsed_location;
|
|
|
|
// Create registration client
|
|
let mut client = DeviceRegistrationClient::new(config);
|
|
|
|
// Add state change callback
|
|
client.on_state_change(|state| {
|
|
println!("📍 Registration State: {:?}", state);
|
|
});
|
|
|
|
// Start registration process
|
|
match client.start_registration().await {
|
|
Ok(()) => {
|
|
println!("🎉 Device registration completed successfully!");
|
|
|
|
if let Some(credentials) = client.credentials() {
|
|
println!(" Device ID: {}", credentials.device_id);
|
|
println!(" Token: {}...", &credentials.device_token[..20]);
|
|
println!(" Certificate generated and stored");
|
|
|
|
// Save credentials to config file
|
|
let config_data = client.export_config()?;
|
|
let config_path = dirs::config_dir()
|
|
.unwrap_or_else(|| std::path::PathBuf::from("."))
|
|
.join("meteor-edge-client")
|
|
.join("registration.json");
|
|
|
|
std::fs::create_dir_all(config_path.parent().unwrap())?;
|
|
std::fs::write(&config_path, serde_json::to_string_pretty(&config_data)?)?;
|
|
|
|
println!(" Configuration saved to: {:?}", config_path);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!("❌ Registration failed: {}", e);
|
|
return Err(e);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Tests hardware fingerprinting functionality
|
|
async fn test_hardware_fingerprint() -> Result<()> {
|
|
use hardware_fingerprint::HardwareFingerprintService;
|
|
|
|
println!("🔍 Testing hardware fingerprinting...");
|
|
|
|
let mut service = HardwareFingerprintService::new();
|
|
let fingerprint = service.generate_fingerprint().await?;
|
|
|
|
println!("✅ Hardware fingerprint generated successfully!");
|
|
println!("");
|
|
println!("Hardware Information:");
|
|
println!(" CPU ID: {}", fingerprint.cpu_id);
|
|
println!(" Board Serial: {}", fingerprint.board_serial);
|
|
println!(" MAC Addresses: {}", fingerprint.mac_addresses.join(", "));
|
|
println!(" Disk UUID: {}", fingerprint.disk_uuid);
|
|
if let Some(tmp) = &fingerprint.tmp_attestation {
|
|
println!(" TPM Attestation: {}...", &tmp[..20]);
|
|
} else {
|
|
println!(" TPM Attestation: Not available");
|
|
}
|
|
println!(" Computed Hash: {}", fingerprint.computed_hash);
|
|
|
|
println!("");
|
|
println!("System Information:");
|
|
println!(" Hostname: {}", fingerprint.system_info.hostname);
|
|
println!(" OS: {} {}", fingerprint.system_info.os_name, fingerprint.system_info.os_version);
|
|
println!(" Kernel: {}", fingerprint.system_info.kernel_version);
|
|
println!(" Architecture: {}", fingerprint.system_info.architecture);
|
|
println!(" Memory: {} MB total, {} MB available",
|
|
fingerprint.system_info.total_memory / 1024 / 1024,
|
|
fingerprint.system_info.available_memory / 1024 / 1024);
|
|
println!(" CPU: {} cores, {}",
|
|
fingerprint.system_info.cpu_count,
|
|
fingerprint.system_info.cpu_brand);
|
|
println!(" Disks: {} mounted", fingerprint.system_info.disk_info.len());
|
|
|
|
// Test fingerprint validation
|
|
println!("");
|
|
println!("🔐 Testing fingerprint validation...");
|
|
let is_valid = service.validate_fingerprint(&fingerprint)?;
|
|
if is_valid {
|
|
println!("✅ Fingerprint validation: PASSED");
|
|
} else {
|
|
println!("❌ Fingerprint validation: FAILED");
|
|
}
|
|
|
|
// Test consistency
|
|
println!("");
|
|
println!("🔄 Testing fingerprint consistency...");
|
|
let fingerprint2 = service.generate_fingerprint().await?;
|
|
if fingerprint.computed_hash == fingerprint2.computed_hash {
|
|
println!("✅ Fingerprint consistency: PASSED");
|
|
} else {
|
|
println!("❌ Fingerprint consistency: FAILED");
|
|
println!(" First: {}", fingerprint.computed_hash);
|
|
println!(" Second: {}", fingerprint2.computed_hash);
|
|
}
|
|
|
|
Ok(())
|
|
} |