firegex/backend/modules/services/mirror.py

50 lines
1.9 KiB
Python

"""The interface the engine writes decrypted traffic to.
TLS is terminated inside the engine, which is what lets a protected service occupy no
port beyond the one it already answered on — and it means the plaintext is never a packet
on any interface, so there would be nothing for a capture tool to read. The engine writes
it out itself (`proxysrc/src/capture.rs`), framed as the TCP stream it was before
encryption, onto the device this module puts there.
All that is needed here is the device: a `dummy` interface, which is a sink that carries
whatever is sent to it and goes nowhere. One for the whole instance, so every TLS
service's plaintext arrives on the same interface and a single capture covers the lot.
It exists only while some TLS service is running. Nothing is lost by removing it — the
engine reopens its socket per process — and an interface that is there when nothing is
decrypting is an interface somebody points a capture at and watches stay empty.
"""
import subprocess
#: What an operator types into a capture tool. Fixed, and the same name the engine uses.
DEVICE = "firegex0"
def _run(*args: str) -> subprocess.CompletedProcess:
return subprocess.run(args, capture_output=True, text=True)
def present() -> bool:
return _run("ip", "link", "show", DEVICE).returncode == 0
def ensure() -> bool:
"""Put the device there, if it is not already. Returns whether it is now.
A failure is not an error anybody hears about: a capture aid that could stop a
service from starting would be worse than no capture aid. The engine finds no
interface, writes nothing, and the traffic is filtered and forwarded exactly as it
would have been.
"""
if present():
return True
if _run("ip", "link", "add", DEVICE, "type", "dummy").returncode != 0:
return False
return _run("ip", "link", "set", DEVICE, "up").returncode == 0
def release() -> None:
"""Take it away again."""
_run("ip", "link", "del", DEVICE)