v2.0.0: adaptive eBPF firewall with AI honeypot and P2P threat mesh

This commit is contained in:
Vladyslav Soliannikov 2026-04-07 22:28:11 +00:00
commit 37c6bbf5a1
133 changed files with 28073 additions and 0 deletions

451
hivemind-api/tests/load_test.rs Executable file
View file

@ -0,0 +1,451 @@
//! Load Test & Licensing Lockdown — Stress simulation for HiveMind Enterprise API.
//!
//! Spawns a real hyper server, seeds it with IoCs, then hammers it with
//! concurrent clients measuring response latency and verifying tier-based
//! access control enforcement.
use common::hivemind::{ApiTier, IoC};
use hivemind_api::licensing::LicenseManager;
use hivemind_api::server::{self, HivemindCounters};
use hivemind_api::store::ThreatFeedStore;
use std::net::SocketAddr;
use std::time::Instant;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/// Make a raw HTTP/1.1 GET request and return (status_code, body).
async fn http_get(addr: SocketAddr, path: &str, bearer: Option<&str>) -> (u16, String) {
let mut stream = TcpStream::connect(addr)
.await
.expect("TCP connect failed");
let mut request = format!(
"GET {path} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n"
);
if let Some(token) = bearer {
request.push_str(&format!("Authorization: Bearer {token}\r\n"));
}
request.push_str("\r\n");
stream
.write_all(request.as_bytes())
.await
.expect("write request failed");
let mut buf = Vec::with_capacity(8192);
stream
.read_to_end(&mut buf)
.await
.expect("read response failed");
let raw = String::from_utf8_lossy(&buf);
// Parse status code from "HTTP/1.1 NNN ..."
let status = raw
.get(9..12)
.and_then(|s| s.parse::<u16>().ok())
.unwrap_or(0);
// Split at the blank line separating headers from body
let body = raw
.find("\r\n\r\n")
.map(|i| raw[i + 4..].to_string())
.unwrap_or_default();
(status, body)
}
/// Seed the store with `count` synthetic IoCs at 1-second intervals.
fn seed_store(store: &mut ThreatFeedStore, count: usize) {
let base_time = 1_700_000_000u64;
for i in 0..count {
let ioc = IoC {
ioc_type: (i % 5) as u8,
severity: ((i % 5) as u8).min(4),
ip: 0xC6120000 + i as u32, // 198.18.x.x range
ja4: if i % 3 == 0 {
Some("t13d1516h2_8daaf6152771_e5627efa2ab1".into())
} else {
None
},
entropy_score: Some(5000 + (i as u32 * 100)),
description: format!("Synthetic threat indicator #{i}"),
first_seen: base_time + i as u64,
confirmations: 3,
zkp_proof: Vec::new(),
};
store.insert(ioc, base_time + i as u64 + 60);
}
}
/// Find a free TCP port by binding to :0 and reading the assigned port.
async fn free_port() -> u16 {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind :0 failed");
listener
.local_addr()
.expect("local_addr failed")
.port()
}
// ===========================================================================
// Scenario A — API Hammering: 100 concurrent clients
// ===========================================================================
#[tokio::test(flavor = "current_thread")]
async fn scenario_a_api_hammering() {
// Setup
let store = ThreatFeedStore::shared();
let licensing = LicenseManager::shared();
// Seed 50 IoCs
{
let mut s = store.write().expect("lock");
seed_store(&mut s, 50);
}
// Register an Enterprise-tier key
let api_key = "test-enterprise-key-12345678";
{
let mut lm = licensing.write().expect("lock");
lm.register_key(api_key, ApiTier::Enterprise);
}
// Start server on a random port
let port = free_port().await;
let addr: SocketAddr = ([127, 0, 0, 1], port).into();
let server_store = store.clone();
let server_lic = licensing.clone();
let counters = std::sync::Arc::new(HivemindCounters::default());
let server_handle = tokio::task::spawn(async move {
let _ = server::run(addr, server_store, server_lic, counters).await;
});
// Give the server a moment to bind — try connecting in a quick retry loop
let mut connected = false;
for _ in 0..50 {
if TcpStream::connect(addr).await.is_ok() {
connected = true;
break;
}
tokio::task::yield_now().await;
}
assert!(connected, "Server did not start within retry window");
// Define the endpoints to hit
let endpoints = [
"/api/v1/feed",
"/api/v1/feed/stix",
"/api/v1/feed/splunk",
"/api/v1/stats",
];
// Spawn 100 concurrent client tasks
let client_count = 100;
let mut handles = Vec::with_capacity(client_count);
let start = Instant::now();
for i in 0..client_count {
let endpoint = endpoints[i % endpoints.len()];
let key = api_key.to_string();
handles.push(tokio::task::spawn(async move {
let t = Instant::now();
let (status, body) = http_get(addr, endpoint, Some(&key)).await;
let latency = t.elapsed();
(i, status, body.len(), latency)
}));
}
// Collect results
let mut total_latency = std::time::Duration::ZERO;
let mut max_latency = std::time::Duration::ZERO;
let mut error_count = 0;
for handle in handles {
let (idx, status, body_len, latency) = handle.await.expect("task panicked");
if status != 200 {
error_count += 1;
eprintln!(
"[HAMMER] Client {idx}: HTTP {status} (body {body_len}B) in {latency:.2?}"
);
}
total_latency += latency;
if latency > max_latency {
max_latency = latency;
}
}
let total_elapsed = start.elapsed();
let avg_latency = total_latency / client_count as u32;
eprintln!(
"[HAMMER] {client_count} clients completed in {total_elapsed:.2?}"
);
eprintln!(
"[HAMMER] Avg latency: {avg_latency:.2?}, Max: {max_latency:.2?}, Errors: {error_count}"
);
assert_eq!(
error_count, 0,
"All authenticated requests should succeed (HTTP 200)"
);
// Abort the server
server_handle.abort();
}
// ===========================================================================
// Scenario B — Licensing Lockdown: tier-based access denial
// ===========================================================================
#[tokio::test(flavor = "current_thread")]
async fn scenario_b_licensing_lockdown() {
let store = ThreatFeedStore::shared();
let licensing = LicenseManager::shared();
// Seed 10 IoCs
{
let mut s = store.write().expect("lock");
seed_store(&mut s, 10);
}
// Register keys at each tier
let free_key = "free-tier-key-aaaa";
let enterprise_key = "enterprise-tier-key-bbbb";
let ns_key = "national-security-key-cccc";
{
let mut lm = licensing.write().expect("lock");
lm.register_key(free_key, ApiTier::Free);
lm.register_key(enterprise_key, ApiTier::Enterprise);
lm.register_key(ns_key, ApiTier::NationalSecurity);
}
let port = free_port().await;
let addr: SocketAddr = ([127, 0, 0, 1], port).into();
let server_store = store.clone();
let server_lic = licensing.clone();
let counters = std::sync::Arc::new(HivemindCounters::default());
let server_handle = tokio::task::spawn(async move {
let _ = server::run(addr, server_store, server_lic, counters).await;
});
// Wait for server
for _ in 0..50 {
if TcpStream::connect(addr).await.is_ok() {
break;
}
tokio::task::yield_now().await;
}
// --- Free tier: /api/v1/feed should work ---
let (status, _) = http_get(addr, "/api/v1/feed", Some(free_key)).await;
assert_eq!(status, 200, "Free tier should access /api/v1/feed");
// --- Free tier: /api/v1/stats should work ---
let (status, _) = http_get(addr, "/api/v1/stats", Some(free_key)).await;
assert_eq!(status, 200, "Free tier should access /api/v1/stats");
// --- Free tier: SIEM endpoints BLOCKED ---
let siem_paths = [
"/api/v1/feed/splunk",
"/api/v1/feed/qradar",
"/api/v1/feed/cef",
];
for path in &siem_paths {
let (status, _) = http_get(addr, path, Some(free_key)).await;
assert_eq!(
status, 403,
"Free tier should be FORBIDDEN from {path}"
);
}
// --- Free tier: STIX/TAXII endpoints BLOCKED ---
let taxii_paths = [
"/api/v1/feed/stix",
"/taxii2/collections/",
];
for path in &taxii_paths {
let (status, _) = http_get(addr, path, Some(free_key)).await;
assert_eq!(
status, 403,
"Free tier should be FORBIDDEN from {path}"
);
}
// --- No auth: should get 401 Unauthorized ---
let (status, _) = http_get(addr, "/api/v1/feed/splunk", None).await;
assert_eq!(
status, 401,
"No auth header should yield 401 Unauthorized"
);
// --- Invalid key: should get denied ---
let (status, _) = http_get(addr, "/api/v1/feed", Some("totally-bogus-key")).await;
// /api/v1/feed allows unauthenticated access via effective_tier=Free fallback,
// but with an invalid key, the server resolves tier to None and falls through
// to Free default for /api/v1/feed
assert!(
status == 200 || status == 401,
"/api/v1/feed with invalid key: got {status}"
);
// --- Enterprise tier: SIEM endpoints ALLOWED ---
for path in &siem_paths {
let (status, body) = http_get(addr, path, Some(enterprise_key)).await;
assert_eq!(
status, 200,
"Enterprise tier should access {path}, got {status}"
);
assert!(!body.is_empty(), "{path} response body should not be empty");
}
// --- Enterprise tier: TAXII endpoints ALLOWED ---
for path in &taxii_paths {
let (status, _) = http_get(addr, path, Some(enterprise_key)).await;
assert_eq!(
status, 200,
"Enterprise tier should access {path}, got {status}"
);
}
// --- NationalSecurity tier: everything ALLOWED ---
let all_paths = [
"/api/v1/feed",
"/api/v1/feed/stix",
"/api/v1/feed/splunk",
"/api/v1/feed/qradar",
"/api/v1/feed/cef",
"/api/v1/stats",
"/taxii2/",
"/taxii2/collections/",
];
for path in &all_paths {
let (status, _) = http_get(addr, path, Some(ns_key)).await;
assert_eq!(
status, 200,
"NationalSecurity tier should access {path}, got {status}"
);
}
// --- Unknown endpoint: 404 ---
let (status, _) = http_get(addr, "/api/v1/nonexistent", Some(enterprise_key)).await;
assert_eq!(status, 404, "Unknown endpoint should yield 404");
eprintln!("[LOCKDOWN] All tier-based access control assertions passed");
server_handle.abort();
}
// ===========================================================================
// Scenario C — Feed Content Integrity: verify response payloads
// ===========================================================================
#[tokio::test(flavor = "current_thread")]
async fn scenario_c_feed_content_integrity() {
let store = ThreatFeedStore::shared();
let licensing = LicenseManager::shared();
// Seed 5 IoCs
{
let mut s = store.write().expect("lock");
seed_store(&mut s, 5);
}
let api_key = "integrity-test-key";
{
let mut lm = licensing.write().expect("lock");
lm.register_key(api_key, ApiTier::Enterprise);
}
let port = free_port().await;
let addr: SocketAddr = ([127, 0, 0, 1], port).into();
let ss = store.clone();
let sl = licensing.clone();
let counters = std::sync::Arc::new(HivemindCounters::default());
let server_handle = tokio::task::spawn(async move {
let _ = server::run(addr, ss, sl, counters).await;
});
for _ in 0..50 {
if TcpStream::connect(addr).await.is_ok() {
break;
}
tokio::task::yield_now().await;
}
// --- JSON feed ---
let (status, body) = http_get(addr, "/api/v1/feed", Some(api_key)).await;
assert_eq!(status, 200);
let parsed: serde_json::Value = serde_json::from_str(&body)
.unwrap_or_else(|e| panic!("Invalid JSON in /api/v1/feed response: {e}"));
assert!(
parsed.get("items").is_some(),
"Feed response should contain 'items' field"
);
assert!(
parsed.get("total").is_some(),
"Feed response should contain 'total' field"
);
// --- STIX bundle ---
let (status, body) = http_get(addr, "/api/v1/feed/stix", Some(api_key)).await;
assert_eq!(status, 200);
let stix: serde_json::Value = serde_json::from_str(&body)
.unwrap_or_else(|e| panic!("Invalid STIX JSON: {e}"));
assert_eq!(
stix.get("type").and_then(|t| t.as_str()),
Some("bundle"),
"STIX response should be a bundle"
);
// --- Splunk HEC ---
let (status, body) = http_get(addr, "/api/v1/feed/splunk", Some(api_key)).await;
assert_eq!(status, 200);
let splunk: serde_json::Value = serde_json::from_str(&body)
.unwrap_or_else(|e| panic!("Invalid Splunk JSON: {e}"));
assert!(splunk.is_array(), "Splunk response should be a JSON array");
// --- QRadar LEEF (plain text) ---
let (status, body) = http_get(addr, "/api/v1/feed/qradar", Some(api_key)).await;
assert_eq!(status, 200);
assert!(
body.contains("LEEF:"),
"QRadar response should contain LEEF headers"
);
// --- CEF (plain text) ---
let (status, body) = http_get(addr, "/api/v1/feed/cef", Some(api_key)).await;
assert_eq!(status, 200);
assert!(
body.contains("CEF:"),
"CEF response should contain CEF headers"
);
// --- Stats ---
let (status, body) = http_get(addr, "/api/v1/stats", Some(api_key)).await;
assert_eq!(status, 200);
let stats: serde_json::Value = serde_json::from_str(&body)
.unwrap_or_else(|e| panic!("Invalid stats JSON: {e}"));
let total = stats
.get("total_iocs")
.and_then(|v| v.as_u64())
.unwrap_or(0);
assert_eq!(total, 5, "Stats should report 5 total IoCs");
// --- TAXII discovery (no auth needed, but we send one) ---
let (status, body) = http_get(addr, "/taxii2/", Some(api_key)).await;
assert_eq!(status, 200);
let taxii: serde_json::Value = serde_json::from_str(&body)
.unwrap_or_else(|e| panic!("Invalid TAXII JSON: {e}"));
assert!(
taxii.get("title").is_some(),
"TAXII discovery should contain title"
);
eprintln!("[INTEGRITY] All 7 endpoints return well-formed responses");
server_handle.abort();
}