chore: sync repository to current working state
Some checks failed
Build and Test / Build Server (Linux) (push) Has been cancelled
Build and Test / Build Agent (Windows) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Build Summary (push) Has been cancelled
Run Tests / Test Server (push) Has been cancelled
Run Tests / Test Agent (push) Has been cancelled
Run Tests / Code Coverage (push) Has been cancelled
Run Tests / Lint and Format Check (push) Has been cancelled

Brings azcomputerguru/guru-connect up to the authoritative working copy that
had been maintained in the claudetools monorepo: Phase 1 security and
infrastructure (middleware, metrics, utils, token blacklist, deployment
scripts, security audits) plus the native-remote-control integration spec.
Preserves the repo .gitignore, .cargo, and server/static/downloads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 06:15:29 -07:00
parent 5b7cf5fb07
commit e3e95f8fa7
73 changed files with 15608 additions and 5757 deletions

View File

@@ -6,11 +6,12 @@
use axum::{
extract::{
ws::{Message, WebSocket, WebSocketUpgrade},
Query, State,
Query, State, ConnectInfo,
},
response::IntoResponse,
http::StatusCode,
};
use std::net::SocketAddr;
use futures_util::{SinkExt, StreamExt};
use prost::Message as ProstMessage;
use serde::Deserialize;
@@ -54,19 +55,38 @@ fn default_viewer_name() -> String {
pub async fn agent_ws_handler(
ws: WebSocketUpgrade,
State(state): State<AppState>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Query(params): Query<AgentParams>,
) -> Result<impl IntoResponse, StatusCode> {
let agent_id = params.agent_id.clone();
let agent_name = params.hostname.clone().or(params.agent_name.clone()).unwrap_or_else(|| agent_id.clone());
let support_code = params.support_code.clone();
let api_key = params.api_key.clone();
let client_ip = addr.ip();
// SECURITY: Agent must provide either a support code OR an API key
// Support code = ad-hoc support session (technician generated code)
// API key = persistent managed agent
if support_code.is_none() && api_key.is_none() {
warn!("Agent connection rejected: {} - no support code or API key", agent_id);
warn!("Agent connection rejected: {} from {} - no support code or API key", agent_id, client_ip);
// Log failed connection attempt to database
if let Some(ref db) = state.db {
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(), // Temporary UUID for failed attempt
db::events::EventTypes::CONNECTION_REJECTED_NO_AUTH,
None,
Some(&agent_id),
Some(serde_json::json!({
"reason": "no_auth_method",
"agent_id": agent_id
})),
Some(client_ip),
).await;
}
return Err(StatusCode::UNAUTHORIZED);
}
@@ -75,15 +95,57 @@ pub async fn agent_ws_handler(
// Check if it's a valid, pending support code
let code_info = state.support_codes.get_status(code).await;
if code_info.is_none() {
warn!("Agent connection rejected: {} - invalid support code {}", agent_id, code);
warn!("Agent connection rejected: {} from {} - invalid support code {}", agent_id, client_ip, code);
// Log failed connection attempt
if let Some(ref db) = state.db {
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(),
db::events::EventTypes::CONNECTION_REJECTED_INVALID_CODE,
None,
Some(&agent_id),
Some(serde_json::json!({
"reason": "invalid_code",
"support_code": code,
"agent_id": agent_id
})),
Some(client_ip),
).await;
}
return Err(StatusCode::UNAUTHORIZED);
}
let status = code_info.unwrap();
if status != "pending" && status != "connected" {
warn!("Agent connection rejected: {} - support code {} has status {}", agent_id, code, status);
warn!("Agent connection rejected: {} from {} - support code {} has status {}", agent_id, client_ip, code, status);
// Log failed connection attempt (expired/cancelled code)
if let Some(ref db) = state.db {
let event_type = if status == "cancelled" {
db::events::EventTypes::CONNECTION_REJECTED_CANCELLED_CODE
} else {
db::events::EventTypes::CONNECTION_REJECTED_EXPIRED_CODE
};
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(),
event_type,
None,
Some(&agent_id),
Some(serde_json::json!({
"reason": status,
"support_code": code,
"agent_id": agent_id
})),
Some(client_ip),
).await;
}
return Err(StatusCode::UNAUTHORIZED);
}
info!("Agent {} authenticated via support code {}", agent_id, code);
info!("Agent {} from {} authenticated via support code {}", agent_id, client_ip, code);
}
// Validate API key if provided (for persistent agents)
@@ -91,17 +153,34 @@ pub async fn agent_ws_handler(
// For now, we'll accept API keys that match the JWT secret or a configured agent key
// In production, this should validate against a database of registered agents
if !validate_agent_api_key(&state, key).await {
warn!("Agent connection rejected: {} - invalid API key", agent_id);
warn!("Agent connection rejected: {} from {} - invalid API key", agent_id, client_ip);
// Log failed connection attempt
if let Some(ref db) = state.db {
let _ = db::events::log_event(
db.pool(),
Uuid::new_v4(),
db::events::EventTypes::CONNECTION_REJECTED_INVALID_API_KEY,
None,
Some(&agent_id),
Some(serde_json::json!({
"reason": "invalid_api_key",
"agent_id": agent_id
})),
Some(client_ip),
).await;
}
return Err(StatusCode::UNAUTHORIZED);
}
info!("Agent {} authenticated via API key", agent_id);
info!("Agent {} from {} authenticated via API key", agent_id, client_ip);
}
let sessions = state.sessions.clone();
let support_codes = state.support_codes.clone();
let db = state.db.clone();
Ok(ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, support_codes, db, agent_id, agent_name, support_code)))
Ok(ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, support_codes, db, agent_id, agent_name, support_code, Some(client_ip))))
}
/// Validate an agent API key
@@ -126,28 +205,31 @@ async fn validate_agent_api_key(state: &AppState, api_key: &str) -> bool {
pub async fn viewer_ws_handler(
ws: WebSocketUpgrade,
State(state): State<AppState>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
Query(params): Query<ViewerParams>,
) -> Result<impl IntoResponse, StatusCode> {
let client_ip = addr.ip();
// Require JWT token for viewers
let token = params.token.ok_or_else(|| {
warn!("Viewer connection rejected: missing token");
warn!("Viewer connection rejected from {}: missing token", client_ip);
StatusCode::UNAUTHORIZED
})?;
// Validate the token
let claims = state.jwt_config.validate_token(&token).map_err(|e| {
warn!("Viewer connection rejected: invalid token: {}", e);
warn!("Viewer connection rejected from {}: invalid token: {}", client_ip, e);
StatusCode::UNAUTHORIZED
})?;
info!("Viewer {} authenticated via JWT", claims.username);
info!("Viewer {} authenticated via JWT from {}", claims.username, client_ip);
let session_id = params.session_id;
let viewer_name = params.viewer_name;
let sessions = state.sessions.clone();
let db = state.db.clone();
Ok(ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, db, session_id, viewer_name)))
Ok(ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, db, session_id, viewer_name, Some(client_ip))))
}
/// Handle an agent WebSocket connection
@@ -159,8 +241,9 @@ async fn handle_agent_connection(
agent_id: String,
agent_name: String,
support_code: Option<String>,
client_ip: Option<std::net::IpAddr>,
) {
info!("Agent connected: {} ({})", agent_name, agent_id);
info!("Agent connected: {} ({}) from {:?}", agent_name, agent_id, client_ip);
let (mut ws_sender, mut ws_receiver) = socket.split();
@@ -209,7 +292,7 @@ async fn handle_agent_connection(
db.pool(),
session_id,
db::events::EventTypes::SESSION_STARTED,
None, None, None, None,
None, None, None, client_ip,
).await;
Some(machine.id)
@@ -406,7 +489,7 @@ async fn handle_agent_connection(
db.pool(),
session_id,
db::events::EventTypes::SESSION_ENDED,
None, None, None, None,
None, None, None, client_ip,
).await;
}
@@ -434,6 +517,7 @@ async fn handle_viewer_connection(
db: Option<Database>,
session_id_str: String,
viewer_name: String,
client_ip: Option<std::net::IpAddr>,
) {
// Parse session ID
let session_id = match uuid::Uuid::parse_str(&session_id_str) {
@@ -456,7 +540,7 @@ async fn handle_viewer_connection(
}
};
info!("Viewer {} ({}) joined session: {}", viewer_name, viewer_id, session_id);
info!("Viewer {} ({}) joined session: {} from {:?}", viewer_name, viewer_id, session_id, client_ip);
// Database: log viewer joined event
if let Some(ref db) = db {
@@ -466,7 +550,7 @@ async fn handle_viewer_connection(
db::events::EventTypes::VIEWER_JOINED,
Some(&viewer_id),
Some(&viewer_name),
None, None,
None, client_ip,
).await;
}
@@ -536,7 +620,7 @@ async fn handle_viewer_connection(
db::events::EventTypes::VIEWER_LEFT,
Some(&viewer_id_cleanup),
Some(&viewer_name_cleanup),
None, None,
None, client_ip,
).await;
}