proxywatchd: persist MITM certificate stats across restarts

Add save_state/load_state to MITMCertStats for JSON persistence.
Stats saved periodically (5min) and at shutdown, loaded at startup.
This commit is contained in:
Username
2025-12-25 19:47:51 +01:00
parent 272eba0f05
commit 3ac7305954

View File

@@ -765,6 +765,44 @@ class MITMCertStats(object):
'recent': list(self.recent_certs[-20:])
}
def save_state(self, filepath):
"""Save MITM stats to JSON file for persistence."""
import json
with self.lock:
state = {
'certs': self.certs,
'by_org': self.by_org,
'by_issuer': self.by_issuer,
'by_proxy': self.by_proxy,
'total_count': self.total_count,
'recent_certs': self.recent_certs[-50:],
}
try:
with open(filepath, 'w') as f:
json.dump(state, f)
except Exception as e:
_log('failed to save MITM state: %s' % str(e), 'warn')
def load_state(self, filepath):
"""Load MITM stats from JSON file."""
import json
try:
with open(filepath, 'r') as f:
state = json.load(f)
with self.lock:
self.certs = state.get('certs', {})
self.by_org = state.get('by_org', {})
self.by_issuer = state.get('by_issuer', {})
self.by_proxy = state.get('by_proxy', {})
self.total_count = state.get('total_count', 0)
self.recent_certs = state.get('recent_certs', [])
_log('restored MITM state: %d certs, %d detections' % (
len(self.certs), self.total_count), 'info')
except IOError:
pass # File doesn't exist yet, start fresh
except Exception as e:
_log('failed to load MITM state: %s' % str(e), 'warn')
def extract_cert_info(cert_der):
"""Extract certificate information from DER-encoded certificate.
@@ -1574,6 +1612,13 @@ class Proxywatchd():
except Exception as e:
_log('failed to save final session state: %s' % str(e), 'warn')
# Save MITM certificate stats
try:
mitm_cert_stats.save_state(self.mitm_state_file)
_log('MITM cert state saved', 'watchd')
except Exception as e:
_log('failed to save MITM state: %s' % str(e), 'warn')
def _prep_db(self):
self.mysqlite = mysqlite.mysqlite(config.watchd.database, str)
def _close_db(self):
@@ -2128,6 +2173,11 @@ class Proxywatchd():
self.stats.load_state(saved_state)
self._close_db()
# Load MITM certificate state (same directory as database)
db_dir = os.path.dirname(config.watchd.database) or '.'
self.mitm_state_file = os.path.join(db_dir, 'mitm_certs.json')
mitm_cert_stats.load_state(self.mitm_state_file)
_log('database: %d total proxies, %d due for testing' % (total, due), 'watchd')
# Initialize Tor connection pool
@@ -2211,6 +2261,12 @@ class Proxywatchd():
except Exception as e:
_log('failed to save session state: %s' % str(e), 'warn')
# Save MITM certificate stats periodically
try:
mitm_cert_stats.save_state(self.mitm_state_file)
except Exception as e:
_log('failed to save MITM state: %s' % str(e), 'warn')
# Hourly stats snapshot
now = time.time()
if not hasattr(self, '_last_snapshot'):