Organize codebase into hub, clients, and common packages with backward compatibility and comprehensive unit test suites
CI Test Suite / Run Component Tests & Pipeline Verification (push) Failing after 1m18s

This commit is contained in:
2026-10-02 23:14:55 +02:00
parent 9ebfadbe89
commit df9f5d191b
47 changed files with 10262 additions and 9068 deletions
+174
View File
@@ -0,0 +1,174 @@
"""
LOGAR Windows Edge Log Forwarder
Collects Windows Event Logs (Application/System) and securely streams them
to the central LOGAR Hub via mutual TLS (mTLS 1.3) or OpenPGP envelope.
"""
import os
import sys
import time
import argparse
import warnings
from typing import Optional
# Ensure script directory and repo root are in sys.path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
if SCRIPT_DIR not in sys.path:
sys.path.insert(0, SCRIPT_DIR)
REPO_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, ".."))
if REPO_ROOT not in sys.path:
sys.path.insert(0, REPO_ROOT)
# Suppress cryptography / pgpy deprecation notices
warnings.filterwarnings("ignore")
import pgpy
try:
import win32evtlog
except ImportError:
win32evtlog = None
try:
from src.clients.log_collector import (
get_machine_identifier,
is_path_writable,
get_default_data_dir,
resolve_writable_path,
get_state_path,
load_state,
save_state,
commit_state,
WindowsLogCollector,
BaseLogCollector
)
from src.clients.client_transport import (
is_cert_expiring_soon,
enroll_client_if_needed,
get_tls_socket,
load_config,
ensure_enrolled as transport_ensure_enrolled,
send_encrypted_logs_over_socket as transport_send_encrypted_logs,
check_and_apply_auto_update
)
except ImportError:
from log_collector import (
get_machine_identifier,
is_path_writable,
get_default_data_dir,
resolve_writable_path,
get_state_path,
load_state,
save_state,
commit_state,
WindowsLogCollector,
BaseLogCollector
)
from client_transport import (
is_cert_expiring_soon,
enroll_client_if_needed,
get_tls_socket,
load_config,
ensure_enrolled as transport_ensure_enrolled,
send_encrypted_logs_over_socket as transport_send_encrypted_logs,
check_and_apply_auto_update
)
CONFIG_FILE_NAME = "client_config.json"
STATE_FILE_NAME = "client_state.json"
def get_recent_windows_logs(hours: int = 24, state: Optional[dict] = None) -> list:
"""
Scans the Windows Application Event Log backwards for events within the window.
Edge Filtering: Retains INFO, WARNING, and ERROR. Drops Audit and Debug noise.
State Tracking: Skips events older than lookback window and already sent records.
"""
collector = WindowsLogCollector(state=state, channels=["Application"])
return collector.collect_logs(hours=hours)
def send_encrypted_logs_over_socket(config: dict, logs: list):
"""Streams logs to the LOGAR hub over mTLS or encrypted TCP envelope."""
return transport_send_encrypted_logs(config=config, logs=logs, os_type="windows")
def ensure_enrolled(config: dict, machine_id: str) -> bool:
"""Proactively ensures client certificate enrollment with the Hub if enrollment_secret is available."""
return transport_ensure_enrolled(config=config, machine_id=machine_id, os_type="windows")
def main():
parser = argparse.ArgumentParser(description="LOGAR Windows Edge Log Forwarder with State Tracking")
parser.add_argument("--config", default=CONFIG_FILE_NAME, help="Path to client_config.json")
parser.add_argument("--hours", type=int, default=24, help="Lookback window in hours for event logs (default: 24)")
parser.add_argument("--state-file", default=None, help="Path to state tracking file (default: client_state.json next to config)")
parser.add_argument("--no-state", action="store_true", help="Disable state tracking and send all events matching lookback window")
parser.add_argument("--interval", type=int, default=0, help="Continuous polling interval in seconds (0 = run once and exit, >0 = service loop)")
args = parser.parse_args()
try:
config = load_config(args.config)
except Exception as e:
print(f"[!] Configuration error: {e}")
sys.exit(1)
state_path = get_state_path(args.config, args.state_file)
state = None if args.no_state else load_state(state_path)
machine_id = config.get("client_id") or get_machine_identifier()
print(f"[*] Edge Forwarder Node: {machine_id}")
if state and "last_record_number" in state:
print(f"[*] State tracking active: resuming from record #{state['last_record_number']} (state file: {state_path})")
elif not args.no_state:
print(f"[*] State tracking initialized (state file: {state_path})")
# Proactively ensure dynamic PKI certificate enrollment with Server Hub
ensure_enrolled(config, machine_id)
collector = WindowsLogCollector(state=state, machine_id=machine_id, channels=["Application"])
def run_forwarder_cycle():
# Continuously verify/renew PKI enrollment if server is licensed & online
ensure_enrolled(config, machine_id)
# Periodically check server for client auto-updates
try:
check_and_apply_auto_update(config=config, machine_id=machine_id, os_type="windows")
except Exception as update_err:
pass
print(f"[*] Scanning Windows Application event log for unsent entries (last {args.hours} hours)...")
candidate_logs = collector.collect_logs(hours=args.hours)
print(f"[*] Found {len(candidate_logs)} unsent candidate entries (INFO to ERROR, entries > {args.hours}h and already-sent skipped).")
if not candidate_logs:
print("[*] No new unsent events to transmit.")
if state is not None:
commit_state(state, state_path)
return
try:
resp = send_encrypted_logs_over_socket(config, candidate_logs)
if state is not None and resp and resp.get("status") == "success":
commit_state(state, state_path)
print(f"[+] State successfully committed to {state_path}")
except Exception as e:
print(f"[!] Failed to stream logs to server: {e}")
if not args.interval:
sys.exit(1)
if args.interval and args.interval > 0:
print(f"[*] Starting continuous forwarder service loop (Interval: {args.interval}s)...")
while True:
try:
run_forwarder_cycle()
except Exception as e:
print(f"[!] Error in forwarder cycle: {e}")
time.sleep(args.interval)
else:
run_forwarder_cycle()
if __name__ == "__main__":
main()