feat(win_client): add proactive certificate expiry check and reactive self-healing in Win_Client.py
This commit is contained in:
+52
-7
@@ -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(),
|
||||||
|
|||||||
Reference in New Issue
Block a user