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:
@@ -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'):
|
||||
|
||||
Reference in New Issue
Block a user