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:])
|
'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):
|
def extract_cert_info(cert_der):
|
||||||
"""Extract certificate information from DER-encoded certificate.
|
"""Extract certificate information from DER-encoded certificate.
|
||||||
@@ -1574,6 +1612,13 @@ class Proxywatchd():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log('failed to save final session state: %s' % str(e), 'warn')
|
_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):
|
def _prep_db(self):
|
||||||
self.mysqlite = mysqlite.mysqlite(config.watchd.database, str)
|
self.mysqlite = mysqlite.mysqlite(config.watchd.database, str)
|
||||||
def _close_db(self):
|
def _close_db(self):
|
||||||
@@ -2128,6 +2173,11 @@ class Proxywatchd():
|
|||||||
self.stats.load_state(saved_state)
|
self.stats.load_state(saved_state)
|
||||||
|
|
||||||
self._close_db()
|
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')
|
_log('database: %d total proxies, %d due for testing' % (total, due), 'watchd')
|
||||||
|
|
||||||
# Initialize Tor connection pool
|
# Initialize Tor connection pool
|
||||||
@@ -2211,6 +2261,12 @@ class Proxywatchd():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log('failed to save session state: %s' % str(e), 'warn')
|
_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
|
# Hourly stats snapshot
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if not hasattr(self, '_last_snapshot'):
|
if not hasattr(self, '_last_snapshot'):
|
||||||
|
|||||||
Reference in New Issue
Block a user