feat(win_client): add proactive certificate expiry check and reactive self-healing in Win_Client.py

This commit is contained in:
2026-09-04 22:51:28 +02:00
parent 0e7d299594
commit 916d3764a2
+52 -7
View File
@@ -24,17 +24,46 @@ CONFIG_FILE_NAME = "client_config.json"
STATE_FILE_NAME = "client_state.json" STATE_FILE_NAME = "client_state.json"
def enroll_client_if_needed(hub_url: str, enrollment_secret: str, cert_dir: str, client_id: str, hostname: str, os_type: str = "windows"): def is_cert_expiring_soon(cert_path: str, threshold_days: int = 30) -> bool:
"""Bootstraps client enrollment if certificates are missing.""" """Checks if client certificate at cert_path is expiring within threshold_days."""
if not os.path.exists(cert_path):
return True
try:
from cryptography import x509
with open(cert_path, "r", encoding="utf-8") as f:
cert = x509.load_pem_x509_certificate(f.read().encode("utf-8"))
expiry = getattr(cert, "not_valid_after_utc", None)
if expiry is None:
expiry = cert.not_valid_after.replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
return expiry <= (now + timedelta(days=threshold_days))
except Exception:
return True
def enroll_client_if_needed(
hub_url: str,
enrollment_secret: str,
cert_dir: str,
client_id: str,
hostname: str,
os_type: str = "windows",
force_renew: bool = False,
threshold_days: int = 30
):
"""Bootstraps client enrollment if certificates are missing or expiring soon."""
os.makedirs(cert_dir, exist_ok=True) os.makedirs(cert_dir, exist_ok=True)
ca_path = os.path.join(cert_dir, "ca.crt") ca_path = os.path.join(cert_dir, "ca.crt")
cert_path = os.path.join(cert_dir, "client.crt") cert_path = os.path.join(cert_dir, "client.crt")
key_path = os.path.join(cert_dir, "client.key") key_path = os.path.join(cert_dir, "client.key")
if os.path.exists(ca_path) and os.path.exists(cert_path) and os.path.exists(key_path): if not force_renew and os.path.exists(ca_path) and os.path.exists(cert_path) and os.path.exists(key_path):
return True if not is_cert_expiring_soon(cert_path, threshold_days=threshold_days):
return True
print(f"[*] Client certificate at {cert_path} is expiring within {threshold_days} days. Auto-renewing...")
print(f"[*] Bootstrapping client enrollment with LOGAR Hub at {hub_url}...") action_name = "re-enrolling" if os.path.exists(cert_path) else "enrolling"
print(f"[*] Bootstrapping client {action_name} with LOGAR Hub at {hub_url}...")
enroll_endpoint = f"{hub_url.rstrip('/')}/api/client/enroll" enroll_endpoint = f"{hub_url.rstrip('/')}/api/client/enroll"
payload = { payload = {
"client_id": client_id, "client_id": client_id,
@@ -64,7 +93,7 @@ def enroll_client_if_needed(hub_url: str, enrollment_secret: str, cert_dir: str,
except Exception: except Exception:
pass pass
print(f"[+] Client enrolled successfully! Certificates saved to {os.path.abspath(cert_dir)}") print(f"[+] Client certificates updated successfully in {os.path.abspath(cert_dir)}")
return True return True
@@ -269,6 +298,7 @@ def send_encrypted_logs_over_socket(config: dict, logs: list):
machine_id = get_machine_identifier() machine_id = get_machine_identifier()
# Attempt automatic enrollment bootstrap if certs are missing and secret is provided # Attempt automatic enrollment bootstrap if certs are missing and secret is provided
hub_url = None
if enrollment_secret: if enrollment_secret:
hermes_host = config.get("hermes_host", server_host) hermes_host = config.get("hermes_host", server_host)
hermes_port = config.get("hermes_port", 8443) hermes_port = config.get("hermes_port", 8443)
@@ -285,7 +315,22 @@ def send_encrypted_logs_over_socket(config: dict, logs: list):
if has_mtls_certs: if has_mtls_certs:
print(f"[*] Connecting to LOGAR server at {server_host}:{server_port} over mTLS (TLS 1.3)...") print(f"[*] Connecting to LOGAR server at {server_host}:{server_port} over mTLS (TLS 1.3)...")
with get_tls_socket(server_host, server_port, cert_dir) as sock: sock = None
try:
sock = get_tls_socket(server_host, server_port, cert_dir)
except (ssl.SSLError, ssl.CertificateError, ConnectionResetError) as tls_err:
if enrollment_secret and hub_url:
print(f"[!] TLS handshake error ({tls_err}). Re-enrolling with LOGAR Hub...")
try:
enroll_client_if_needed(hub_url, enrollment_secret, cert_dir, machine_id, machine_id, os_type="windows", force_renew=True)
sock = get_tls_socket(server_host, server_port, cert_dir)
except Exception as retry_err:
print(f"[!] Re-enrollment or reconnection retry failed: {retry_err}")
raise
else:
raise
with sock:
payload = { payload = {
"server": machine_id, "server": machine_id,
"timestamp": datetime.now(timezone.utc).isoformat(), "timestamp": datetime.now(timezone.utc).isoformat(),