64 lines
2.5 KiB
Python
64 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Proxmox-side fail-closed monitor; only the disposable VM223 is stopped."""
|
|
import json
|
|
import pathlib
|
|
import shutil
|
|
import subprocess
|
|
import time
|
|
|
|
DIRECTORY = pathlib.Path('/var/lib/vz/honeypot')
|
|
|
|
|
|
def qm(*args):
|
|
return subprocess.run(['/usr/sbin/qm', *args], capture_output=True, text=True, timeout=25, check=True).stdout
|
|
|
|
|
|
def stop(reason):
|
|
with (DIRECTORY / 'watchdog-events.jsonl').open('a') as output:
|
|
output.write(json.dumps(dict(time_ns=time.time_ns(), event='execution_vm_stopped', reason=reason)) + '\n')
|
|
output.flush()
|
|
qm('stop', '223')
|
|
# This latch prevents unattended auto-restart. Operator must verify evidence before clearing.
|
|
(DIRECTORY / 'STOP').touch(mode=0o600)
|
|
|
|
|
|
def main():
|
|
last_health = 0
|
|
while True:
|
|
try:
|
|
if 'status: running' not in qm('status', '223'):
|
|
time.sleep(2)
|
|
continue
|
|
reason = None
|
|
pcap = DIRECTORY / 'execution.pcap'
|
|
if (DIRECTORY / 'STOP').exists():
|
|
reason = 'host stop latch'
|
|
elif pcap.exists() and pcap.stat().st_size >= 512 * 1024 * 1024:
|
|
reason = 'pcap quota'
|
|
elif shutil.disk_usage(DIRECTORY).free < 4 * 1024**3:
|
|
reason = 'host free space reserve'
|
|
elif time.monotonic() - last_health >= 10:
|
|
last_health = time.monotonic()
|
|
usage = subprocess.run(['/usr/sbin/lvs', '--noheadings', '-o', 'data_percent', 'pve/data'], capture_output=True, text=True, check=True, timeout=5)
|
|
if float(usage.stdout.strip()) >= 85:
|
|
stop('Proxmox thin pool reserve')
|
|
continue
|
|
try:
|
|
result = json.loads(qm('guest', 'exec', '222', '--timeout', '10', '--',
|
|
'/bin/sh', '-c',
|
|
'test ! -e /var/lib/decoy/STOP && '
|
|
'systemctl is-active --quiet decoy.service decoy-network.service'))
|
|
if result.get('exitcode') != 0 or not result.get('exited'):
|
|
reason = 'collector services stopped or capture failed'
|
|
except (OSError, ValueError, subprocess.SubprocessError):
|
|
reason = 'collector health unavailable'
|
|
if reason:
|
|
stop(reason)
|
|
except (OSError, subprocess.SubprocessError) as error:
|
|
print(type(error).__name__, flush=True)
|
|
time.sleep(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|