feat: aggregation server info webui with client profile export, seat management, last activity time, auth, and hermes tokens
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 17s
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 17s
This commit is contained in:
+1
-1
Submodule LOGAR-LICENSING updated: 6f1af3ea71...30dfc9f77f
@@ -1,12 +1,12 @@
|
||||
# LOGAR: Fleet Log Aggregation & Centralized Provisioning System
|
||||
### *Sichere Log-Zusammenführung, PKI-Flottenlizenzierung & Bereitstellung für autonome KI-Analyse*
|
||||
### *Secure Log Aggregation, PKI Fleet Licensing & Centralized Delivery for Autonomous AI Analysis*
|
||||
|
||||
**LOGAR** is an enterprise-grade log aggregation, normalization, and centralized provisioning system (**"Log-Zusammenführung und Bereitstellung"**) built for heterogeneous server fleets (Windows & Linux). It pairs ultra-lightweight, zero-state edge forwarders with a secure centralized server hub that enforces mutual TLS 1.3 (**mTLS**) authentication, dynamic PKI fleet licensing with cryptographic seat leasing, 12-hour temporal persistence tracking, and direct anomaly pass-through—delivering clean, structured, verified log telemetry directly to **Hermes AI** for autonomous deep reasoning, contextual root-cause analysis, and operational decision-making.
|
||||
**LOGAR** is an enterprise-grade log aggregation, normalization, and centralized provisioning system built for heterogeneous server fleets (Windows & Linux). It pairs ultra-lightweight, zero-state edge forwarders with a secure centralized server hub that enforces mutual TLS 1.3 (**mTLS**) authentication, dynamic PKI fleet licensing with cryptographic seat leasing, 12-hour temporal persistence tracking, and direct anomaly pass-through—delivering clean, structured, verified log telemetry directly to **Hermes AI** for autonomous deep reasoning, contextual root-cause analysis, and operational decision-making.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Core Purpose & Philosophy (Log-Zusammenführung & Bereitstellung)](#core-purpose--philosophy)
|
||||
1. [Core Purpose & Philosophy (Log Aggregation & Provisioning)](#core-purpose--philosophy)
|
||||
2. [End-to-End System Architecture](#end-to-end-system-architecture)
|
||||
3. [System State Machines & Subsystem Lifecycles](#system-state-machines--subsystem-lifecycles)
|
||||
- 3.1 [High-Level Overview State Machine](#31-high-level-system-state-machine)
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
## Core Purpose & Philosophy
|
||||
|
||||
### 1. Centralized Log Aggregation & Provisioning (*Log-Zusammenführung & Bereitstellung*)
|
||||
### 1. Centralized Log Aggregation & Provisioning
|
||||
The foundational objective of LOGAR is reliable, secure collection and structured delivery of system events:
|
||||
- **Heterogeneous Fleet Aggregation**: Ingests Windows Event Logs (System, Application, Security) and Linux `systemd journalctl` events into a unified, normalized JSON data format.
|
||||
- **Centralized Provisioning**: Serves as the authoritative clearinghouse and real-time streaming endpoint (`/api/hermes/report`), providing structured logs with temporal metadata to external consumers and automated AI agents.
|
||||
|
||||
+1295
-438
File diff suppressed because it is too large
Load Diff
@@ -329,6 +329,119 @@ class TestServerComponent(unittest.TestCase):
|
||||
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")
|
||||
just_now = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.assertIn(Server.format_relative_time(just_now), ("0s ago", "1s ago", "Just now"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user