Files
LOGAR/tests/test_server.py
T
me0nline 909dc640e3
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m24s
Fix Gitea issues #1 and #2, add firewall automation, forwarder telemetry, and dynamic key rotation
2026-10-02 21:39:51 +02:00

995 lines
46 KiB
Python

import os
import sys
import json
import socket
import struct
import sqlite3
import unittest
import urllib.request
import uuid
import warnings
from datetime import datetime, timezone, timedelta
warnings.filterwarnings("ignore")
# Ensure parent directory and src directory are in path to import Server
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
import Server
import pgpy
class TestServerComponent(unittest.TestCase):
def setUp(self):
self.test_db = f"test_server_state_{uuid.uuid4().hex[:8]}.db"
self.test_config = f"test_server_config_{uuid.uuid4().hex[:8]}.json"
def tearDown(self):
if os.path.exists(self.test_db):
try:
os.remove(self.test_db)
except Exception:
pass
if os.path.exists(self.test_config):
try:
os.remove(self.test_config)
except Exception:
pass
def test_first_run_config_and_keypair_generation(self):
config = Server.load_or_init_config(self.test_config)
self.assertTrue(os.path.exists(self.test_config))
self.assertIn("server_fingerprint", config)
self.assertIn("public_key", config)
self.assertIn("private_key", config)
self.assertIn("auth_token", config)
self.assertNotIn("site_name", config)
# Verify keypair
priv_key, _ = pgpy.PGPKey.from_blob(config["private_key"])
pub_key, _ = pgpy.PGPKey.from_blob(config["public_key"])
self.assertEqual(str(pub_key.fingerprint), config["server_fingerprint"])
def test_create_client_config(self):
Server.load_or_init_config(self.test_config)
client_out = "test_client_out.json"
try:
client_conf = Server.create_client_config(
server_host="10.0.0.1",
server_port=9443,
output_path=client_out,
config_path=self.test_config
)
self.assertTrue(os.path.exists(client_out))
self.assertEqual(client_conf["server_host"], "10.0.0.1")
self.assertEqual(client_conf["server_port"], 9443)
# Verify no machine name or site_name is included
self.assertNotIn("server_name", client_conf)
self.assertNotIn("name", client_conf)
self.assertNotIn("site_name", client_conf)
finally:
if os.path.exists(client_out):
os.remove(client_out)
def test_direct_hermes_anomaly_and_12h_window(self):
Server.init_db(self.test_db)
log_entry = {
"server": "app-worker-01.corp.local",
"signature": "PostgresConnWarning",
"severity": "WARNING",
"message": "Connection to database pool near capacity: 85%",
"os_type": "linux"
}
payload = {
"server": "app-worker-01.corp.local",
"logs": [log_entry]
}
# Run 1: WARNING is immediately VERIFIED for Hermes AI reasoning
res1 = Server.process_ingested_logs(payload, self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res1["status"], "success")
self.assertEqual(res1["promoted_verified"], 1)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT run_count, status FROM active_issues WHERE signature = ?", ("PostgresConnWarning",))
row = c.fetchone()
conn.close()
self.assertEqual(row[0], 1)
self.assertEqual(row[1], "VERIFIED")
# Runs 2 to 4: Increments consecutive run count while remaining VERIFIED
for run_idx in range(2, 5):
res = Server.process_ingested_logs(payload, self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res["status"], "success")
self.assertEqual(res["promoted_verified"], 0)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT run_count, status FROM active_issues WHERE signature = ?", ("PostgresConnWarning",))
row = c.fetchone()
conn.close()
self.assertEqual(row[0], 4)
self.assertEqual(row[1], "VERIFIED")
def test_error_immediate_pass(self):
Server.init_db(self.test_db)
log_entry = {
"server": "app-worker-01.corp.local",
"signature": "KernelPanicCritical",
"severity": "ERROR",
"message": "Kernel panic - not syncing: Fatal hardware error",
"os_type": "linux"
}
payload = {
"server": "app-worker-01.corp.local",
"logs": [log_entry]
}
# Run 1: ERROR must immediately promote to VERIFIED
res = Server.process_ingested_logs(payload, self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res["status"], "success")
self.assertEqual(res["promoted_verified"], 1)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT run_count, status, severity FROM active_issues WHERE signature = ?", ("KernelPanicCritical",))
row = c.fetchone()
conn.close()
self.assertIsNotNone(row)
self.assertEqual(row[0], 1)
self.assertEqual(row[1], "VERIFIED")
self.assertEqual(row[2], "ERROR")
def test_server_severity_filtering(self):
Server.init_db(self.test_db)
payload = {
"server": "app-worker-01.corp.local",
"logs": [
{"server": "app-worker-01", "signature": "SigInfo", "severity": "INFO", "message": "Info msg", "os_type": "linux"},
{"server": "app-worker-01", "signature": "SigWarn", "severity": "WARNING", "message": "Warn msg", "os_type": "linux"},
{"server": "app-worker-01", "signature": "SigErr", "severity": "ERROR", "message": "Err msg", "os_type": "linux"},
{"server": "app-worker-01", "signature": "SigDebug", "severity": "DEBUG", "message": "Debug msg", "os_type": "linux"},
{"server": "app-worker-01", "signature": "SigTrace", "severity": "TRACE", "message": "Trace msg", "os_type": "linux"}
]
}
res = Server.process_ingested_logs(payload, self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res["status"], "success")
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT signature, status FROM active_issues ORDER BY signature")
rows = dict(c.fetchall())
conn.close()
self.assertIn("SigInfo", rows)
self.assertIn("SigWarn", rows)
self.assertIn("SigErr", rows)
self.assertNotIn("SigDebug", rows)
self.assertNotIn("SigTrace", rows)
# All actionable issues (ERROR, WARNING, INFO) are promoted to VERIFIED immediately for Hermes reasoning
self.assertEqual(rows["SigErr"], "VERIFIED")
self.assertEqual(rows["SigWarn"], "VERIFIED")
self.assertEqual(rows["SigInfo"], "VERIFIED")
def test_license_schema_and_pki_generation(self):
secret = "test-secret-12345"
Server.init_db(self.test_db, enrollment_secret=secret, max_seats=5)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT max_seats, enrollment_secret FROM license_config WHERE id = 1")
row = c.fetchone()
conn.close()
self.assertIsNotNone(row)
self.assertEqual(row[0], 5)
self.assertEqual(row[1], secret)
# Test Dynamic PKI
test_cert_dir = "test_certs_pki"
try:
ca_cert, ca_key, ca_pem, ca_key_pem = Server.enrollment.generate_ca_if_needed(cert_dir=test_cert_dir)
self.assertIn("BEGIN CERTIFICATE", ca_pem)
self.assertIn("BEGIN RSA PRIVATE KEY", ca_key_pem)
srv_cert, srv_key, srv_pem, srv_key_pem = Server.enrollment.generate_server_cert_if_needed(
ca_cert, ca_key, hostnames=["127.0.0.1", "localhost"], cert_dir=test_cert_dir
)
self.assertIn("BEGIN CERTIFICATE", srv_pem)
client_cert_pem, client_key_pem = Server.enrollment.issue_client_cert("node-test-1", ca_cert, ca_key)
self.assertIn("BEGIN CERTIFICATE", client_cert_pem)
self.assertIn("BEGIN RSA PRIVATE KEY", client_key_pem)
fp = Server.enrollment.calculate_cert_fingerprint(client_cert_pem)
self.assertEqual(len(fp), 64)
finally:
import shutil
if os.path.exists(test_cert_dir):
shutil.rmtree(test_cert_dir, ignore_errors=True)
def test_enrollment_endpoint_and_seat_quota(self):
from fastapi import HTTPException
secret = "super-secret-enrollment"
max_seats = 2
Server.init_db(self.test_db, enrollment_secret=secret, max_seats=max_seats)
test_cert_dir = "test_certs_enroll"
try:
ca_cert, ca_key, ca_pem, _ = Server.enrollment.generate_ca_if_needed(cert_dir=test_cert_dir)
Server.SERVER_STATE["config"] = {"db_path": self.test_db, "cert_dir": test_cert_dir}
Server.SERVER_STATE["ca_cert"] = ca_cert
Server.SERVER_STATE["ca_key"] = ca_key
Server.SERVER_STATE["ca_cert_pem"] = ca_pem
# 1. Invalid secret should raise 403
bad_req = Server.ClientEnrollRequest(
client_id="client-1",
hostname="host-1",
os="linux",
enrollment_secret="wrong-secret"
)
with self.assertRaises(HTTPException) as cm:
Server.enroll_client(bad_req)
self.assertEqual(cm.exception.status_code, 403)
# 2. Valid enrollment for client 1
req1 = Server.ClientEnrollRequest(
client_id="client-1",
hostname="host-1",
os="linux",
enrollment_secret=secret
)
resp1 = Server.enroll_client(req1)
self.assertIn("client_cert", resp1)
self.assertIn("client_key", resp1)
self.assertTrue(resp1["ca_cert"].strip().startswith(ca_pem.strip()))
# 3. Valid enrollment for client 2
req2 = Server.ClientEnrollRequest(
client_id="client-2",
hostname="host-2",
os="windows",
enrollment_secret=secret
)
resp2 = Server.enroll_client(req2)
self.assertIn("client_cert", resp2)
# 4. Seat quota exhausted: client 3 should raise 403
req3 = Server.ClientEnrollRequest(
client_id="client-3",
hostname="host-3",
os="linux",
enrollment_secret=secret
)
with self.assertRaises(HTTPException) as cm:
Server.enroll_client(req3)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("License seat limit reached", cm.exception.detail)
# 5. Re-enrollment for existing client 1 should succeed
resp1_re = Server.enroll_client(req1)
self.assertIn("client_cert", resp1_re)
# 6. Revoked client should be rejected
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE clients SET status = 'revoked' WHERE client_id = 'client-1'")
conn.commit()
conn.close()
with self.assertRaises(HTTPException) as cm:
Server.enroll_client(req1)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("revoked", cm.exception.detail)
finally:
import shutil
if os.path.exists(test_cert_dir):
shutil.rmtree(test_cert_dir, ignore_errors=True)
def test_cert_validity_and_hub_pki_renewal(self):
test_cert_dir = "test_certs_renew"
try:
ca_cert, ca_key, ca_pem, _ = Server.enrollment.generate_ca_if_needed(cert_dir=test_cert_dir)
srv_cert, srv_key, srv_pem, _ = Server.enrollment.generate_server_cert_if_needed(
ca_cert, ca_key, hostnames=["127.0.0.1"], cert_dir=test_cert_dir
)
# 1. Freshly generated certificates should NOT be expiring soon with standard 30-day threshold
self.assertFalse(Server.enrollment.is_cert_expiring_soon(ca_pem, threshold_days=30))
self.assertFalse(Server.enrollment.is_cert_expiring_soon(srv_pem, threshold_days=30))
# 2. Huge threshold (e.g. 5000 days) should flag expiration
self.assertTrue(Server.enrollment.is_cert_expiring_soon(srv_pem, threshold_days=5000))
# 3. check_and_renew_hub_pki with standard threshold should report no renewal needed
ca_renewed, srv_renewed = Server.enrollment.check_and_renew_hub_pki(cert_dir=test_cert_dir, threshold_days=30)
self.assertFalse(ca_renewed)
self.assertFalse(srv_renewed)
# 4. In-flight reload of SSLContext
ssl_ctx = Server.init_mtls_server_context(cert_dir=test_cert_dir)
Server.SERVER_STATE["ssl_ctx"] = ssl_ctx
Server.SERVER_STATE["config"] = {"db_path": self.test_db, "cert_dir": test_cert_dir, "tcp_host": "127.0.0.1"}
# Trigger rotation using high threshold
rotated = Server.check_and_rotate_server_certs(cert_dir=test_cert_dir, hostnames=["127.0.0.1"], threshold_days=5000)
self.assertTrue(rotated)
# Check that backup files were generated
bak_files = [f for f in os.listdir(test_cert_dir) if f.endswith(".bak")]
self.assertGreater(len(bak_files), 0)
finally:
import shutil
if os.path.exists(test_cert_dir):
shutil.rmtree(test_cert_dir, ignore_errors=True)
def test_user_auth_and_session(self):
"""Tests admin password hashing, verification, session tokens, and password change."""
Server.init_db(self.test_db)
# Verify default admin credentials
self.assertTrue(Server.verify_user_credentials("admin", "admin_logar_2026!", db_path=self.test_db))
self.assertFalse(Server.verify_user_credentials("admin", "wrong_password", db_path=self.test_db))
self.assertFalse(Server.verify_user_credentials("nonexistent", "password", db_path=self.test_db))
# Test session token generation & validation
token = Server.generate_session_token("admin")
self.assertEqual(Server.verify_session_token(token), "admin")
self.assertIsNone(Server.verify_session_token("invalid.token.structure"))
# Test changing password
Server.change_user_password("admin", "new_secure_pass123", db_path=self.test_db)
self.assertFalse(Server.verify_user_credentials("admin", "admin_logar_2026!", db_path=self.test_db))
self.assertTrue(Server.verify_user_credentials("admin", "new_secure_pass123", db_path=self.test_db))
def test_hermes_tokens_and_auth(self):
"""Tests Hermes Bearer token creation, verification, revocation, and deletion."""
Server.init_db(self.test_db)
# Test default Hermes token was created on init
tokens = Server.list_hermes_tokens(db_path=self.test_db)
self.assertEqual(len(tokens), 1)
self.assertEqual(tokens[0]["status"], "active")
default_token = tokens[0]["token"]
self.assertTrue(Server.verify_hermes_token(default_token, db_path=self.test_db))
# Test creating new token
new_tok = Server.create_hermes_token(name="Hermes Autonomous Agent 01", db_path=self.test_db)
self.assertIn("token", new_tok)
self.assertEqual(new_tok["name"], "Hermes Autonomous Agent 01")
self.assertTrue(Server.verify_hermes_token(new_tok["token"], db_path=self.test_db))
# Test revoking token
self.assertTrue(Server.revoke_hermes_token(new_tok["token"], db_path=self.test_db))
self.assertFalse(Server.verify_hermes_token(new_tok["token"], db_path=self.test_db))
# Test deleting token
self.assertTrue(Server.delete_hermes_token(new_tok["token"], db_path=self.test_db))
tokens_after = Server.list_hermes_tokens(db_path=self.test_db)
self.assertEqual(len(tokens_after), 1)
def test_client_profile_export(self):
"""Tests client profile dictionary generation and export formatting."""
cfg = Server.load_or_init_config(self.test_config)
profile = Server.generate_client_profile_dict(
server_host="logar-hub.corp.internal",
server_port=9443,
client_id="worker-node-101",
config_dict=cfg
)
self.assertEqual(profile["server_host"], "logar-hub.corp.internal")
self.assertEqual(profile["server_port"], 9443)
self.assertEqual(profile["client_id"], "worker-node-101")
self.assertEqual(profile["server_fingerprint"], cfg["server_fingerprint"])
self.assertEqual(profile["server_public_key"], cfg["public_key"])
self.assertEqual(profile["enrollment_secret"], cfg["enrollment_secret"])
self.assertEqual(profile["format"], "LOGAR_CLIENT_PROFILE")
def test_client_seat_management_and_relative_time(self):
"""Tests client listing, seat approval, revocation, deletion, and relative timestamp helper."""
Server.init_db(self.test_db, enrollment_secret="test-sec-123", max_seats=5)
Server.SERVER_STATE["config"] = {"db_path": self.test_db}
# Populate test clients
conn = sqlite3.connect(self.test_db)
now = datetime.now(timezone.utc)
five_min_ago = (now - timedelta(minutes=5)).strftime("%Y-%m-%d %H:%M:%S")
two_hours_ago = (now - timedelta(hours=2)).strftime("%Y-%m-%d %H:%M:%S")
conn.execute("INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, last_seen) VALUES ('c-win', 'WIN-PROD-01', 'windows', 'FP_W1', 'active', ?)", (five_min_ago,))
conn.execute("INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, last_seen) VALUES ('c-lin', 'LIN-PROD-01', 'linux', 'FP_L1', 'active', ?)", (two_hours_ago,))
conn.commit()
conn.close()
# 1. Test listing
client_data = Server.list_clients()
self.assertEqual(client_data["active_seats"], 2)
self.assertEqual(client_data["max_seats"], 5)
self.assertEqual(len(client_data["clients"]), 2)
self.assertEqual(client_data["clients"][0]["last_activity_relative"], "5m ago")
# 2. Test revoking
rev_req = Server.ClientActionRequest(client_id="c-win")
rev_res = Server.revoke_client(rev_req)
self.assertEqual(rev_res["status"], "revoked")
client_data = Server.list_clients()
self.assertEqual(client_data["active_seats"], 1)
# 3. Test approving / reactivating
app_res = Server.approve_client(rev_req)
self.assertEqual(app_res["status"], "active")
client_data = Server.list_clients()
self.assertEqual(client_data["active_seats"], 2)
# 4. Test deleting client record
del_res = Server.delete_client(rev_req)
self.assertEqual(del_res["status"], "deleted")
client_data = Server.list_clients()
self.assertEqual(len(client_data["clients"]), 1)
self.assertEqual(client_data["active_seats"], 1)
# 5. Test format_relative_time edge cases
self.assertEqual(Server.format_relative_time(None), "Never")
self.assertEqual(Server.format_relative_time(""), "Never")
self.assertEqual(Server.format_relative_time("invalid-date-string"), "invalid-date-str")
now_fresh = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
rel = Server.format_relative_time(now_fresh)
self.assertTrue(rel.endswith("s ago") or rel == "Just now", f"Unexpected relative time: {rel}")
def test_integrated_local_log_collector_zero_seats(self):
"""Tests that local hub log collection runs without license, registering a 0-seat built-in client."""
Server.init_db(self.test_db, enrollment_secret="test-sec-456", max_seats=0)
Server.SERVER_STATE["config"] = {
"db_path": self.test_db,
"evaluation_window_hours": 12,
"min_persistence_runs": 1
}
# Run cycle
state = {}
processed = Server.run_local_log_collection_cycle(self.test_db, window_hours=12, min_runs=1, state=state)
# Verify local hub client entry exists in clients table
machine_id = Server.get_machine_identifier()
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT client_id, cert_fingerprint, status FROM clients WHERE client_id = ?", (machine_id,))
row = c.fetchone()
conn.close()
self.assertIsNotNone(row)
self.assertEqual(row[0], machine_id)
self.assertEqual(row[1], "LOCAL-HOST-BUILTIN")
self.assertEqual(row[2], "active")
# Verify active seat count is STILL 0 (exempt from seat accounting)
client_data = Server.list_clients()
self.assertEqual(client_data["active_seats"], 0)
self.assertEqual(len(client_data["clients"]), 1)
def test_two_factor_auth_and_totp_flow(self):
"""Tests TOTP secret generation, token verification, URI generation, and enable/disable lifecycle."""
Server.init_db(self.test_db)
# 1. Test TOTP helpers
secret = Server.generate_totp_secret()
self.assertTrue(len(secret) >= 16)
token = Server.get_totp_token(secret)
self.assertEqual(len(token), 6)
self.assertTrue(token.isdigit())
self.assertTrue(Server.verify_totp_code(secret, token))
self.assertFalse(Server.verify_totp_code(secret, "000000" if token != "000000" else "999999"))
self.assertFalse(Server.verify_totp_code(secret, "invalid"))
# 2. Test OTPAuth URI
uri = Server.get_totp_uri("admin", secret, issuer="LOGAR Fleet Hub")
self.assertTrue(uri.startswith("otpauth://totp/"))
self.assertIn("secret=" + secret, uri)
self.assertIn("LOGAR", uri)
# 3. Test initial user 2FA status
info = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertFalse(info["enabled"])
self.assertEqual(info["secret"], "")
# 4. Test enabling 2FA
enabled = Server.enable_user_totp("admin", secret, db_path=self.test_db)
self.assertTrue(enabled)
info_active = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertTrue(info_active["enabled"])
self.assertEqual(info_active["secret"], secret)
# 5. Test disabling 2FA
disabled = Server.disable_user_totp("admin", db_path=self.test_db)
self.assertTrue(disabled)
info_disabled = Server.get_user_totp_info("admin", db_path=self.test_db)
self.assertFalse(info_disabled["enabled"])
def test_persistent_session_secret_and_rate_limiting(self):
"""Tests DB persistence of session secret and sliding window rate limiting."""
Server.init_db(self.test_db)
# 1. Session secret persistence
sec1 = Server.get_session_secret(self.test_db)
self.assertTrue(len(sec1) >= 32)
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT value FROM server_meta WHERE key = 'session_secret'")
row = c.fetchone()
conn.close()
self.assertIsNotNone(row)
self.assertEqual(row[0], sec1)
# Token generated with test_db
token = Server.generate_session_token("admin", db_path=self.test_db)
self.assertEqual(Server.verify_session_token(token, db_path=self.test_db), "admin")
# 2. Rate limiting
test_ip = "10.0.0.99"
Server.reset_failed_ip_attempts(test_ip, scope="login", db_path=self.test_db)
self.assertTrue(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="login", db_path=self.test_db))
Server.record_failed_ip_attempt(test_ip, scope="login", db_path=self.test_db)
Server.record_failed_ip_attempt(test_ip, scope="login", db_path=self.test_db)
self.assertTrue(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="login", db_path=self.test_db))
Server.record_failed_ip_attempt(test_ip, scope="login", db_path=self.test_db)
self.assertFalse(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="login", db_path=self.test_db))
# Different scope ('enroll') should still be allowed
self.assertTrue(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="enroll", db_path=self.test_db))
Server.reset_failed_ip_attempts(test_ip, scope="login", db_path=self.test_db)
self.assertTrue(Server.check_ip_rate_limit(test_ip, max_attempts=3, window_seconds=60, scope="login", db_path=self.test_db))
def test_ingest_queue_manager_and_batch_flush(self):
"""Tests IngestQueueManager buffering, capacity bounds, and atomic batch flushing."""
Server.init_db(self.test_db)
qm = Server.IngestQueueManager(flush_interval=1.0, batch_size=5, max_queue=50)
# Initial stats
stats = qm.get_stats()
self.assertEqual(stats["queue_size"], 0)
self.assertEqual(stats["total_ingested"], 0)
self.assertEqual(stats["total_flushed"], 0)
# Push 3 payloads
for i in range(3):
payload = {
"server": f"node-{i}.corp.local",
"logs": [
{"server": f"node-{i}", "signature": f"Sig_{i}", "severity": "WARNING", "message": f"Log msg {i}", "os_type": "linux"}
]
}
pushed = qm.push(payload)
self.assertTrue(pushed)
stats = qm.get_stats()
self.assertEqual(stats["queue_size"], 3)
self.assertEqual(stats["total_ingested"], 3)
# Flush to database
res_flush = qm.flush(db_path=self.test_db, window_hours=12, min_runs=1)
self.assertEqual(res_flush["flushed_runs"], 3)
self.assertEqual(res_flush["processed_logs"], 3)
# Verify DB records
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT COUNT(*) FROM ingest_runs")
run_count = c.fetchone()[0]
c.execute("SELECT COUNT(*) FROM active_issues WHERE status = 'VERIFIED'")
issue_count = c.fetchone()[0]
conn.close()
self.assertEqual(run_count, 3)
self.assertEqual(issue_count, 3)
# Stats after flush
stats = qm.get_stats()
self.assertEqual(stats["queue_size"], 0)
self.assertEqual(stats["total_flushed"], 3)
def test_enterprise_db_feature_gating(self):
"""Tests feature check helpers and DB storage info under standard and enterprise licensed states."""
Server.init_db(self.test_db)
# 1. Standard / Unlicensed state
self.assertFalse(Server.is_feature_licensed("enterprise_db", db_path=self.test_db))
info = Server.get_storage_engine_info(db_path=self.test_db)
self.assertEqual(info["engine"], "sqlite")
self.assertFalse(info["feature_unlocked"])
self.assertIn("SQLite 3 (WAL Mode)", info["backend_display"])
# 2. Unlock enterprise_db feature in DB
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE license_config SET features = ? WHERE id = 1", (json.dumps(["enterprise_db", "ai_heuristics"]),))
conn.commit()
conn.close()
self.assertTrue(Server.is_feature_licensed("enterprise_db", db_path=self.test_db))
self.assertTrue(Server.is_feature_licensed("ai_heuristics", db_path=self.test_db))
self.assertFalse(Server.is_feature_licensed("unknown_feat", db_path=self.test_db))
info_unlocked = Server.get_storage_engine_info(db_path=self.test_db)
self.assertTrue(info_unlocked["feature_unlocked"])
self.assertIn("enterprise_db", info_unlocked["licensed_features"])
def test_postgres_storage_endpoints_authorization(self):
"""Tests REST API security gating on DB engine management endpoints."""
Server.init_db(self.test_db)
Server.SERVER_STATE["config"] = {"db_path": self.test_db, "storage_engine": "sqlite"}
# 1. Test direct authorization & endpoint logic
stats = Server.api_db_stats()
self.assertIn("queue", stats)
self.assertIn("storage", stats)
self.assertFalse(stats["enterprise_db_licensed"])
# 2. Test Connection without enterprise_db -> HTTPException 403
pg_req = Server.DbConnectionTestRequest(host="localhost", port=5432, database="logar", user="postgres", password="")
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_test_connection(pg_req)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
# 3. Switch backend without enterprise_db -> HTTPException 403
switch_req = Server.DbSwitchBackendRequest(db_type="postgres", host="localhost", port=5432, database="logar", user="postgres", password="")
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_switch_backend(switch_req)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
# 4. Flush buffer endpoint executes successfully
flush_res = Server.api_db_flush_buffer()
self.assertEqual(flush_res["status"], "success")
# 5. Optional HTTP client verification if starlette testclient dependencies are present
try:
from fastapi.testclient import TestClient
client = TestClient(Server.app)
# Unauthenticated request to /api/db/stats -> 401
res = client.get("/api/db/stats")
self.assertEqual(res.status_code, 401)
# Authenticated request with session cookie
token = Server.generate_session_token("admin", db_path=self.test_db)
client.cookies.set(Server.SESSION_COOKIE_NAME, token)
res_auth = client.get("/api/db/stats")
self.assertEqual(res_auth.status_code, 200)
data = res_auth.json()
self.assertIn("queue", data)
self.assertIn("storage", data)
self.assertFalse(data["enterprise_db_licensed"])
# 403 checks via HTTP client
res_test = client.post("/api/db/test-connection", json={"host": "localhost", "port": 5432, "database": "logar", "user": "postgres", "password": ""})
self.assertEqual(res_test.status_code, 403)
res_switch = client.post("/api/db/switch-backend", json={"db_type": "postgres", "host": "localhost", "port": 5432, "database": "logar", "user": "postgres", "password": ""})
self.assertEqual(res_switch.status_code, 403)
res_flush = client.post("/api/db/flush-buffer")
self.assertEqual(res_flush.status_code, 200)
except (ImportError, RuntimeError, Exception):
# Starlette TestClient may raise RuntimeError when httpx/httpx2 is missing in lightweight runner environments
pass
def test_db_encryption_at_rest_and_full_config_api(self):
"""Tests OpenPGP data-at-rest encryption/decryption and full DB/Cache configuration API."""
Server.init_db(self.test_db)
priv_armored, pub_armored, fp = Server.generate_server_keypair("test-db-node")
priv_key, _ = pgpy.PGPKey.from_blob(priv_armored)
pub_key, _ = pgpy.PGPKey.from_blob(pub_armored)
Server.SERVER_STATE["config"] = {
"db_path": self.test_db,
"storage_engine": "sqlite",
"cache_mode": "direct",
"encrypt_at_rest": False,
"private_key": priv_armored,
"public_key": pub_armored,
"server_fingerprint": fp
}
Server.SERVER_STATE["private_key_obj"] = priv_key
Server.SERVER_STATE["public_key_obj"] = pub_key
# 1. Test OpenPGP encryption & decryption roundtrip
raw_msg = "Critical database corruption: segment 0x89FA damaged"
encrypted_msg = Server.encrypt_field_for_storage(raw_msg, force=True)
self.assertTrue(encrypted_msg.startswith("-----BEGIN PGP MESSAGE-----"))
self.assertNotIn("Critical database corruption", encrypted_msg)
decrypted_msg = Server.decrypt_field_from_storage(encrypted_msg)
self.assertEqual(decrypted_msg, raw_msg)
# 2. Test that without enterprise_db license, buffering, encryption at rest, and postgres are blocked
cfg_unlicensed_buf = Server.DbFullConfigRequest(
storage_engine="sqlite",
cache_mode="buffered",
encrypt_at_rest=False
)
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_update_config(cfg_unlicensed_buf)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
cfg_unlicensed_enc = Server.DbFullConfigRequest(
storage_engine="sqlite",
cache_mode="direct",
encrypt_at_rest=True
)
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_update_config(cfg_unlicensed_enc)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
pg_req = Server.DbFullConfigRequest(
storage_engine="postgres",
cache_mode="direct",
pg_config=Server.DbConnectionTestRequest(host="db.corp.internal", port=5432, database="logar", user="postgres", password="")
)
with self.assertRaises(Server.HTTPException) as cm:
Server.api_db_update_config(pg_req)
self.assertEqual(cm.exception.status_code, 403)
self.assertIn("enterprise_db", cm.exception.detail)
# 3. Standard local SQLite configuration succeeds when unlicensed
cfg_standard = Server.DbFullConfigRequest(
storage_engine="sqlite",
cache_mode="direct",
encrypt_at_rest=False
)
res_std = Server.api_db_update_config(cfg_standard)
self.assertEqual(res_std["status"], "success")
# 4. Unlock enterprise_db feature in license_config
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE license_config SET features = ? WHERE id = 1", (json.dumps(["enterprise_db"]),))
conn.commit()
conn.close()
# 5. Test updating config with enterprise_db unlocked (direct -> buffered, encrypt_at_rest = True)
cfg_req = Server.DbFullConfigRequest(
storage_engine="sqlite",
cache_mode="buffered",
flush_interval=4.0,
batch_size=200,
max_queue=25000,
encrypt_at_rest=True
)
res = Server.api_db_update_config(cfg_req)
self.assertEqual(res["status"], "success")
self.assertEqual(Server.SERVER_STATE["config"]["cache_mode"], "buffered")
self.assertTrue(Server.SERVER_STATE["config"]["encrypt_at_rest"])
self.assertEqual(Server.INGEST_QUEUE.flush_interval, 4.0)
self.assertEqual(Server.INGEST_QUEUE.batch_size, 200)
# 6. Verify storage engine info output
info = Server.get_storage_engine_info(db_path=self.test_db)
self.assertEqual(info["cache_mode"], "buffered")
self.assertTrue(info["encrypt_at_rest"])
self.assertEqual(info["flush_interval"], 4.0)
def test_active_licensed_features_helper(self):
"""Tests helper function to extract active licensed capabilities from DB and certificates."""
Server.init_db(self.test_db)
Server.SERVER_STATE["ca_cert_pem"] = ""
Server.SERVER_STATE["config"] = {"db_path": self.test_db, "cert_dir": "non_existent_test_certs"}
# 1. Unlicensed state
features_unlicensed = Server.get_active_licensed_features(db_path=self.test_db)
self.assertEqual(features_unlicensed, [])
self.assertFalse(Server.is_feature_licensed("enterprise_db", db_path=self.test_db))
# 2. Add features via JSON string in DB
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE license_config SET features = ? WHERE id = 1", (json.dumps(["enterprise_db", "ai_heuristics"]),))
conn.commit()
conn.close()
features_licensed = Server.get_active_licensed_features(db_path=self.test_db)
self.assertEqual(features_licensed, ["ai_heuristics", "enterprise_db"])
self.assertTrue(Server.is_feature_licensed("enterprise_db", db_path=self.test_db))
self.assertTrue(Server.is_feature_licensed("ai_heuristics", db_path=self.test_db))
self.assertFalse(Server.is_feature_licensed("non_existent_feature", db_path=self.test_db))
# 3. Verify get_license_summary includes features list
summary = Server.get_license_summary()
self.assertIn("features", summary)
self.assertIn("enterprise_db", summary["features"])
def test_webui_tab_rendering_and_feature_cards(self):
"""Tests WebUI HTML generation for clean tab buttons and feature cards in PKI tab."""
Server.init_db(self.test_db)
Server.SERVER_STATE["config"] = {
"db_path": self.test_db,
"cert_dir": "certs",
"tcp_port": 9443,
"hermes_port": 8443,
"server_name": "Test-Hub"
}
# 1. Render WebUI in standard / unlicensed mode
resp = Server.serve_webui(request=None)
html_content = resp.body.decode("utf-8")
# Verify clean tab buttons without number suffixes
self.assertIn('>💻 Fleet Forwarders</button>', html_content)
self.assertIn('>🤖 Hermes AI & Tokens</button>', html_content)
self.assertIn('>📊 Verified Telemetry</button>', html_content)
self.assertIn('>📜 Sub-CA & Licensing</button>', html_content)
self.assertIn('>💾 DB Engine</button>', html_content)
self.assertNotIn('🟢 UNLOCKED', html_content)
self.assertNotIn('Fleet Forwarders (', html_content)
# Verify Licensed Capabilities card is present
self.assertIn('Licensed Capabilities &amp; Feature Modules', html_content)
self.assertIn('Standard Community Edition is running', html_content)
# 2. Render WebUI with enterprise_db unlocked
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE license_config SET features = ? WHERE id = 1", (json.dumps(["enterprise_db"]),))
conn.commit()
conn.close()
resp_ent = Server.serve_webui(request=None)
html_ent = resp_ent.body.decode("utf-8")
self.assertIn('enterprise_db', html_ent)
self.assertIn('Enterprise Database &amp; High-Throughput Engine', html_ent)
self.assertIn('>💾 DB Engine</button>', html_ent)
self.assertNotIn('🟢 UNLOCKED', html_ent)
def test_explicit_opt_in_guarantee(self):
"""Tests that licensing does NOT automatically turn on encryption or buffering without explicit admin setting."""
Server.init_db(self.test_db)
priv_armored, pub_armored, fp = Server.generate_server_keypair("test-opt-in-node")
priv_key, _ = pgpy.PGPKey.from_blob(priv_armored)
pub_key, _ = pgpy.PGPKey.from_blob(pub_armored)
Server.SERVER_STATE["config"] = {
"db_path": self.test_db,
"cert_dir": "certs",
"storage_engine": "sqlite",
"cache_mode": "direct",
"encrypt_at_rest": False,
"private_key": priv_armored,
"public_key": pub_armored,
"server_fingerprint": fp
}
Server.SERVER_STATE["private_key_obj"] = priv_key
Server.SERVER_STATE["public_key_obj"] = pub_key
# Unlock enterprise license
conn = sqlite3.connect(self.test_db)
conn.execute("UPDATE license_config SET features = ? WHERE id = 1", (json.dumps(["enterprise_db"]),))
conn.commit()
conn.close()
# License is active, but config has encrypt_at_rest=False -> plaintext storage
raw_msg = "Sample operational log event"
result_msg = Server.encrypt_field_for_storage(raw_msg)
self.assertEqual(result_msg, raw_msg)
self.assertFalse(result_msg.startswith("-----BEGIN PGP MESSAGE-----"))
# Ingestion cache mode is direct
self.assertEqual(Server.SERVER_STATE["config"]["cache_mode"], "direct")
def test_reset_enrollment_secret_and_invalidation(self):
"""Tests that rotating enrollment secret invalidates the previous secret and updates DB & config."""
init_sec = "initial-secret-123456"
Server.init_db(self.test_db, enrollment_secret=init_sec, max_seats=5)
Server.SERVER_STATE["config"] = {
"db_path": self.test_db,
"enrollment_secret": init_sec,
"config_file_path": self.test_config
}
with open(self.test_config, "w", encoding="utf-8") as f:
json.dump({"enrollment_secret": init_sec, "db_path": self.test_db}, f)
# 1. Rotate enrollment secret
new_sec = Server.rotate_enrollment_secret()
self.assertNotEqual(new_sec, init_sec)
self.assertEqual(Server.SERVER_STATE["config"]["enrollment_secret"], new_sec)
# 2. Check DB
conn = sqlite3.connect(self.test_db)
c = conn.cursor()
c.execute("SELECT enrollment_secret FROM license_config WHERE id = 1")
db_sec = c.fetchone()[0]
conn.close()
self.assertEqual(db_sec, new_sec)
# 3. Check config file
with open(self.test_config, "r", encoding="utf-8") as f:
cfg_file = json.load(f)
self.assertEqual(cfg_file["enrollment_secret"], new_sec)
# 4. Check export_client_profile automatically generates a new secret
resp = Server.export_client_profile(server_host="10.0.0.5", server_port=9443)
exported_profile = json.loads(resp.body.decode("utf-8"))
self.assertIn("enrollment_secret", exported_profile)
self.assertNotEqual(exported_profile["enrollment_secret"], new_sec)
self.assertEqual(exported_profile["enrollment_secret"], Server.SERVER_STATE["config"]["enrollment_secret"])
# 5. Check reset API endpoint
reset_res = Server.reset_server_enrollment_secret()
self.assertEqual(reset_res["status"], "success")
self.assertIn("enrollment_secret", reset_res)
self.assertEqual(reset_res["enrollment_secret"], Server.SERVER_STATE["config"]["enrollment_secret"])
def test_hermes_offline_node_detection_and_metrics(self):
"""Tests that Hermes reporting detects silent forwarders (>15m) and includes host telemetry metrics."""
Server.init_db(self.test_db)
Server.SERVER_STATE["config"] = {
"db_path": self.test_db,
"evaluation_window_hours": 12
}
conn = sqlite3.connect(self.test_db)
now = datetime.now(timezone.utc)
thirty_min_ago = (now - timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M:%S")
two_min_ago = (now - timedelta(minutes=2)).strftime("%Y-%m-%d %H:%M:%S")
metrics_node1 = json.dumps({"cpu_percent": 15.5, "memory_percent": 60.0, "disk_percent_free": 45.0})
metrics_node2 = json.dumps({"cpu_percent": 88.0, "memory_percent": 95.0, "disk_percent_free": 4.5})
conn.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, last_seen, metrics_json, cpu_percent, memory_percent, disk_percent_free)
VALUES ('node-online', 'srv-online.corp', 'windows', 'FP1', 'active', ?, ?, 15.5, 60.0, 45.0)
""", (two_min_ago, metrics_node1))
conn.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, last_seen, metrics_json, cpu_percent, memory_percent, disk_percent_free)
VALUES ('node-dead', 'srv-dead.corp', 'linux', 'FP2', 'active', ?, ?, 88.0, 95.0, 4.5)
""", (thirty_min_ago, metrics_node2))
# Also insert a verified operational error for the online node
conn.execute("""
INSERT INTO active_issues (fingerprint, site_name, server, signature, severity, message, os_type, first_seen, last_seen, run_count, status, last_run_id)
VALUES ('default:srv-online.corp:AppCrash', 'default', 'srv-online.corp', 'AppCrash', 'ERROR', 'Segmentation fault', 'windows', ?, ?, 2, 'VERIFIED', 'run-1')
""", (two_min_ago, two_min_ago))
conn.commit()
conn.close()
report = Server.get_verified_anomalies_for_hermes()
self.assertGreaterEqual(len(report), 2)
# Verify offline node alert
offline_alert = next((r for r in report if r.get("status") == "NODE_OFFLINE"), None)
self.assertIsNotNone(offline_alert)
self.assertEqual(offline_alert["server"], "srv-dead.corp")
self.assertEqual(offline_alert["severity"], "CRITICAL")
self.assertIn("has gone silent", offline_alert["message"])
self.assertEqual(offline_alert["metrics"]["disk_percent_free"], 4.5)
# Verify online error has attached telemetry
online_err = next((r for r in report if r.get("signature") == "AppCrash"), None)
self.assertIsNotNone(online_err)
self.assertEqual(online_err["metrics"]["cpu_percent"], 15.5)
def test_routine_service_noise_filtering(self):
"""Tests that benign routine background service start/stop cycles are filtered from anomaly spam."""
self.assertTrue(Server.is_routine_service_noise("sppsvc", "The Software Protection service has entered the stopped state.", "INFO"))
self.assertTrue(Server.is_routine_service_noise("edgeupdate", "The Microsoft Edge Update service entered the running state.", "INFO"))
self.assertTrue(Server.is_routine_service_noise("Service Control Manager", "The Windows Update service entered the stopped state.", "INFO"))
self.assertTrue(Server.is_routine_service_noise("systemd", "Service started successfully.", "INFO"))
# Errors or warnings must NOT be filtered
self.assertFalse(Server.is_routine_service_noise("sppsvc", "Software Protection activation failed with error 0xC004F074", "ERROR"))
self.assertFalse(Server.is_routine_service_noise("Database", "Connection pool exhausted", "WARNING"))
if __name__ == "__main__":
unittest.main()