Add Windows Server vs Client differentiation, installed server roles introspection, dedicated /client-info directory, and best practice baseline analyzer
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 1m50s

This commit is contained in:
2026-10-11 09:37:05 +02:00
parent bcaae4a9ee
commit cccf928f29
11 changed files with 1902 additions and 38 deletions
+272 -25
View File
@@ -102,6 +102,22 @@ from src.license_manager import LicenseManager
from src.pki_manager import PKIManager
from src.web_ui import WebUIRenderer, format_relative_time
try:
from src.common.system_info import get_full_system_inventory, get_os_details
except ImportError:
try:
from common.system_info import get_full_system_inventory, get_os_details
except ImportError:
from system_info import get_full_system_inventory, get_os_details
try:
from src.hub.best_practices import BEST_PRACTICES_ENGINE
except ImportError:
try:
from best_practices import BEST_PRACTICES_ENGINE
except ImportError:
BEST_PRACTICES_ENGINE = None
CONFIG_FILE_NAME = "server_config.json"
DEFAULT_DB_FILE = "logar_state.db"
@@ -939,9 +955,15 @@ def process_ingested_logs(payload: Dict[str, Any], db_path: str, window_hours: i
processed_count += 1
# Update client node metrics & version if attached
# Update client node metrics, version & server role introspection if attached
metrics = payload.get("metrics")
client_ver = payload.get("version")
payload_ost = payload.get("os_type")
payload_os_name = payload.get("os_name")
payload_is_srv = payload.get("is_server")
payload_roles = payload.get("server_roles")
payload_info = payload.get("client_info")
if metrics and isinstance(metrics, dict) and client_server:
cpu_p = float(metrics.get("cpu_percent", 0.0))
mem_p = float(metrics.get("memory_percent", 0.0))
@@ -969,6 +991,24 @@ def process_ingested_logs(payload: Dict[str, Any], db_path: str, window_hours: i
except Exception:
pass
# Update OS details, server roles and client info telemetry if provided
if (payload_os_name or payload_roles is not None or payload_info is not None or payload_is_srv is not None or payload_ost) and client_server:
roles_val = json.dumps(payload_roles) if isinstance(payload_roles, list) else (str(payload_roles) if payload_roles is not None else None)
info_val = json.dumps(payload_info) if isinstance(payload_info, dict) else (str(payload_info) if payload_info is not None else None)
is_srv_val = 1 if payload_is_srv else (0 if payload_is_srv is not None else (1 if payload_ost == "windows_server" else None))
try:
cursor.execute("""
UPDATE clients
SET os_type = COALESCE(?, os_type),
os_name = COALESCE(NULLIF(?, ''), os_name),
is_server = COALESCE(?, is_server),
server_roles = COALESCE(?, server_roles),
client_info_json = COALESCE(?, client_info_json)
WHERE client_id = ? OR hostname = ?
""", (payload_ost, payload_os_name, is_srv_val, roles_val, info_val, client_server, client_server))
except Exception:
pass
conn.commit()
conn.close()
@@ -997,8 +1037,17 @@ def get_local_system_logs(hours: int = 24, state: Optional[dict] = None) -> list
def run_local_log_collection_cycle(db_path: str, window_hours: int = 12, min_runs: int = 1, state: Optional[dict] = None) -> int:
"""Executes one cycle of local host log collection and direct ingestion into SQLite."""
try:
inv = get_full_system_inventory() if "get_full_system_inventory" in globals() else {}
except Exception:
inv = {}
machine_id = get_machine_identifier()
os_type = "windows" if sys.platform == "win32" else "linux"
os_type = inv.get("os_type", "windows" if sys.platform == "win32" else "linux")
os_name = inv.get("os_name", "")
is_server = 1 if inv.get("is_server") else (1 if os_type == "windows_server" else 0)
server_roles = json.dumps(inv.get("server_roles", []))
client_info_json = json.dumps(inv)
now_sql = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
try:
@@ -1006,16 +1055,30 @@ def run_local_log_collection_cycle(db_path: str, window_hours: int = 12, min_run
c = conn.cursor()
c.execute("SELECT client_id FROM clients WHERE client_id = ?", (machine_id,))
if c.fetchone():
c.execute("""
UPDATE clients
SET hostname = ?, os_type = ?, cert_fingerprint = 'LOCAL-HOST-BUILTIN', status = 'active', last_seen = ?
WHERE client_id = ?
""", (machine_id, os_type, now_sql, machine_id))
try:
c.execute("""
UPDATE clients
SET hostname = ?, os_type = ?, cert_fingerprint = 'LOCAL-HOST-BUILTIN', status = 'active', last_seen = ?,
os_name = ?, is_server = ?, server_roles = ?, client_info_json = ?
WHERE client_id = ?
""", (machine_id, os_type, now_sql, os_name, is_server, server_roles, client_info_json, machine_id))
except sqlite3.OperationalError:
c.execute("""
UPDATE clients
SET hostname = ?, os_type = ?, cert_fingerprint = 'LOCAL-HOST-BUILTIN', status = 'active', last_seen = ?
WHERE client_id = ?
""", (machine_id, os_type, now_sql, machine_id))
else:
c.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen)
VALUES (?, ?, ?, 'LOCAL-HOST-BUILTIN', 'active', ?, ?)
""", (machine_id, machine_id, os_type, now_sql, now_sql))
try:
c.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen, os_name, is_server, server_roles, client_info_json)
VALUES (?, ?, ?, 'LOCAL-HOST-BUILTIN', 'active', ?, ?, ?, ?, ?, ?)
""", (machine_id, machine_id, os_type, now_sql, now_sql, os_name, is_server, server_roles, client_info_json))
except sqlite3.OperationalError:
c.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen)
VALUES (?, ?, ?, 'LOCAL-HOST-BUILTIN', 'active', ?, ?)
""", (machine_id, machine_id, os_type, now_sql, now_sql))
conn.commit()
conn.close()
except Exception as e:
@@ -1394,30 +1457,49 @@ def enroll_client(req: ClientEnrollRequest, request: Request = None):
now_sql = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
client_ver = req.version or ""
req_os_name = req.os_name or ""
req_is_server = 1 if req.is_server else (1 if req.os == "windows_server" else 0)
req_server_roles = json.dumps(req.server_roles) if req.server_roles else "[]"
req_client_info = json.dumps(req.client_info) if req.client_info else "{}"
if existing:
try:
c.execute("""
UPDATE clients
SET hostname = ?, os_type = ?, cert_fingerprint = ?, status = 'active', last_seen = ?, client_version = ?
SET hostname = ?, os_type = ?, cert_fingerprint = ?, status = 'active', last_seen = ?, client_version = ?,
os_name = ?, is_server = ?, server_roles = ?, client_info_json = ?
WHERE client_id = ?
""", (req.hostname, req.os, fp, now_sql, client_ver, req.client_id))
""", (req.hostname, req.os, fp, now_sql, client_ver, req_os_name, req_is_server, req_server_roles, req_client_info, req.client_id))
except sqlite3.OperationalError:
c.execute("""
UPDATE clients
SET hostname = ?, os_type = ?, cert_fingerprint = ?, status = 'active', last_seen = ?
WHERE client_id = ?
""", (req.hostname, req.os, fp, now_sql, req.client_id))
try:
c.execute("""
UPDATE clients
SET hostname = ?, os_type = ?, cert_fingerprint = ?, status = 'active', last_seen = ?, client_version = ?
WHERE client_id = ?
""", (req.hostname, req.os, fp, now_sql, client_ver, req.client_id))
except sqlite3.OperationalError:
c.execute("""
UPDATE clients
SET hostname = ?, os_type = ?, cert_fingerprint = ?, status = 'active', last_seen = ?
WHERE client_id = ?
""", (req.hostname, req.os, fp, now_sql, req.client_id))
else:
try:
c.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen, client_version)
VALUES (?, ?, ?, ?, 'active', ?, ?, ?)
""", (req.client_id, req.hostname, req.os, fp, now_sql, now_sql, client_ver))
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen, client_version, os_name, is_server, server_roles, client_info_json)
VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?)
""", (req.client_id, req.hostname, req.os, fp, now_sql, now_sql, client_ver, req_os_name, req_is_server, req_server_roles, req_client_info))
except sqlite3.OperationalError:
c.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen)
VALUES (?, ?, ?, ?, 'active', ?, ?)
""", (req.client_id, req.hostname, req.os, fp, now_sql, now_sql))
try:
c.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen, client_version)
VALUES (?, ?, ?, ?, 'active', ?, ?, ?)
""", (req.client_id, req.hostname, req.os, fp, now_sql, now_sql, client_ver))
except sqlite3.OperationalError:
c.execute("""
INSERT INTO clients (client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen)
VALUES (?, ?, ?, ?, 'active', ?, ?)
""", (req.client_id, req.hostname, req.os, fp, now_sql, now_sql))
conn.commit()
conn.close()
@@ -2484,6 +2566,171 @@ def serve_webui(request: Request = None):
return HTMLResponse(content=html_content)
@app.get("/client-info", response_class=HTMLResponse)
@app.get("/webui/client-info", response_class=HTMLResponse)
def serve_client_info(request: Request = None):
"""Renders the dedicated LOGAR Client & Server Role Directory subpage."""
auth_user = None
if request is not None:
auth_user = get_current_user_optional(request)
if not auth_user:
return RedirectResponse(url="/login", status_code=303)
else:
auth_user = DEFAULT_ADMIN_USER
html_content = WebUIRenderer.render_client_info_html(
auth_user=auth_user,
request=request,
server_state=SERVER_STATE
)
return HTMLResponse(content=html_content)
@app.get("/api/client-info")
def api_client_info(request: Request = None, auth_user: str = Depends(require_admin_auth)):
"""API endpoint providing comprehensive host telemetry, OS classification, and server roles."""
db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE)
conn = sqlite3.connect(db_path)
c = conn.cursor()
try:
c.execute("""
SELECT client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen,
COALESCE(client_version, ''), COALESCE(os_name, ''), COALESCE(is_server, 0),
COALESCE(server_roles, '[]'), COALESCE(client_info_json, '{}'),
COALESCE(cpu_percent, 0.0), COALESCE(memory_percent, 0.0), COALESCE(disk_percent_free, 0.0)
FROM clients
ORDER BY is_server DESC, last_seen DESC
""")
rows = c.fetchall()
except Exception as e:
conn.close()
raise HTTPException(status_code=500, detail=f"Database query error: {str(e)}")
conn.close()
clients_list = []
win_servers = 0
win_workstations = 0
linux_nodes = 0
for r in rows:
cid, host, ost, fp, st, first, last, cver, os_name, is_srv, roles_raw, info_raw, cpu_p, mem_p, disk_p = r
try:
roles = json.loads(roles_raw) if roles_raw else []
except Exception:
roles = []
try:
info = json.loads(info_raw) if info_raw else {}
except Exception:
info = {}
is_srv_bool = bool(is_srv) or ost == "windows_server"
if "server" in ost or is_srv_bool:
if "windows" in ost:
win_servers += 1
else:
linux_nodes += 1
elif "windows" in ost:
win_workstations += 1
elif "linux" in ost:
linux_nodes += 1
clients_list.append({
"client_id": cid,
"hostname": host,
"os_type": ost,
"os_name": os_name,
"is_server": is_srv_bool,
"server_roles": roles,
"client_info": info,
"cert_fingerprint": fp,
"status": st,
"first_seen": first,
"last_seen": last,
"client_version": cver,
"metrics": {
"cpu_percent": cpu_p,
"memory_percent": mem_p,
"disk_percent_free": disk_p
}
})
return {
"status": "success",
"total_nodes": len(clients_list),
"windows_servers": win_servers,
"windows_workstations": win_workstations,
"linux_nodes": linux_nodes,
"clients": clients_list
}
@app.get("/api/best-practices/status")
def api_best_practices_status(request: Request = None, auth_user: str = Depends(require_admin_auth)):
"""API endpoint providing fleet-wide Best Practice baseline compliance evaluation."""
db_path = SERVER_STATE.get("config", {}).get("db_path", DEFAULT_DB_FILE)
is_licensed = is_feature_licensed("best_practices", db_path)
if not is_licensed:
return {
"status": "locked",
"licensed": False,
"message": "The Best Practice Analyzer is an enterprise capability that requires an active license with the 'best_practices' feature enabled.",
"rules_available": len(BEST_PRACTICES_ENGINE.get_rules()) if BEST_PRACTICES_ENGINE else 0
}
conn = sqlite3.connect(db_path)
c = conn.cursor()
try:
c.execute("""
SELECT client_id, hostname, os_type, cert_fingerprint, status, first_seen, last_seen,
COALESCE(client_version, ''), COALESCE(os_name, ''), COALESCE(is_server, 0),
COALESCE(server_roles, '[]'), COALESCE(client_info_json, '{}'),
COALESCE(cpu_percent, 0.0), COALESCE(memory_percent, 0.0), COALESCE(disk_percent_free, 0.0),
COALESCE(update_status, 'up_to_date')
FROM clients
WHERE status = 'active'
""")
rows = c.fetchall()
except Exception:
rows = []
finally:
conn.close()
node_records = []
for r in rows:
cid, host, ost, fp, st, first, last, cver, os_name, is_srv, roles_raw, info_raw, cpu_p, mem_p, disk_p, up_st = r
try:
roles = json.loads(roles_raw) if roles_raw else []
except Exception:
roles = []
try:
info = json.loads(info_raw) if info_raw else {}
except Exception:
info = {}
node_records.append({
"client_id": cid,
"hostname": host,
"os_type": ost,
"os_name": os_name,
"is_server": bool(is_srv) or ost == "windows_server",
"server_roles": roles,
"client_info": info,
"cpu_percent": cpu_p,
"memory_percent": mem_p,
"disk_percent_free": disk_p,
"client_version": cver,
"update_status": up_st
})
eval_result = BEST_PRACTICES_ENGINE.evaluate_fleet(node_records) if BEST_PRACTICES_ENGINE else {}
return {
"status": "active",
"licensed": True,
"evaluation": eval_result
}
@app.get("/health")
def health_check():
return {