selfhostblocks/modules/blocks/mitmdump.nix
2025-07-31 23:21:57 +02:00

136 lines
4.3 KiB
Nix

{ config, lib, pkgs, ... }:
let
inherit (lib) mapAttrs' mkOption nameValuePair types;
inherit (types) attrsOf listOf port submodule str;
cfg = config.shb.mitmdump;
in
{
options.shb.mitmdump = {
instances = mkOption {
default = {};
description = "Mitmdump instance.";
type = attrsOf (submodule ({ name, ... }: {
options = {
listenPort = mkOption {
type = port;
description = ''
Port the mitmdump instance will listen to.
The upstream port from the client's perspective.
'';
};
upstreamHost = mkOption {
type = str;
default = "http://127.0.0.1";
description = ''
Host the mitmdump instance will connect to.
If only an IP or domain is provided,
mitmdump will default to connect using HTTPS.
If this is not wanted, prefix the IP or domain with the 'http://' protocol.
'';
};
upstreamPort = mkOption {
type = port;
description = ''
Port the mitmdump instance will connect to.
The port the server is listening on.
'';
};
after = mkOption {
type = listOf str;
default = [];
description = ''
Systemd services that must be started before this mitmdump proxy instance.
You are guaranteed the mitmdump is listening on the `listenPort`
when its systemd service has started.
'';
};
};
}));
};
};
config = {
systemd.services = mapAttrs' (name: cfg: nameValuePair "mitmdump-${name}" {
environment = {
"HOME" = "/var/lib/private/mitmdump-${name}";
};
serviceConfig = {
Type = "notify";
Restart = "on-failure";
StandardOutput = "journal";
StandardError = "journal";
DynamicUser = true;
WorkingDirectory = "/var/lib/mitmdump-${name}";
StateDirectory = "mitmdump-${name}";
ExecStart = lib.getExe (pkgs.writers.writePython3Bin "mitmdump-${name}"
{
libraries = [ pkgs.python3Packages.systemd ];
flakeIgnore = [ "E501" ];
}
''
from systemd.daemon import notify
import logging
import subprocess
import socket
import sys
import time
logging.basicConfig(level=logging.INFO, format='%(message)s')
def wait_for_port(host, port, timeout=10):
deadline = time.time() + timeout
while time.time() < deadline:
try:
with socket.create_connection((host, port), timeout=0.5):
return True
except Exception:
time.sleep(0.1)
return False
logging.info("Waiting for upstream address '${cfg.upstreamHost}:${toString cfg.upstreamPort}' to be up.")
wait_for_port("${cfg.upstreamHost}", ${toString cfg.upstreamPort}, timeout=10)
logging.info("Upstream address '${cfg.upstreamHost}:${toString cfg.upstreamPort}' to is up.")
proc = subprocess.Popen(
[
"${pkgs.mitmproxy}/bin/mitmdump",
"--listen-host", "127.0.0.1",
"-p", "${toString cfg.listenPort}",
"--set", "flow_detail=3",
"--set", "content_view_lines_cutoff=2000",
"--mode", "reverse:${cfg.upstreamHost}:${toString cfg.upstreamPort}",
],
stdout=sys.stdout,
stderr=sys.stderr,
)
logging.info("Waiting for mitmdump instance to start on port ${toString cfg.listenPort}.")
if wait_for_port("127.0.0.1", ${toString cfg.listenPort}, timeout=10):
logging.info("Mitmdump is started on port ${toString cfg.listenPort}.")
notify("READY=1")
else:
proc.terminate()
exit(1)
proc.wait()
'');
};
requires = cfg.after;
after = cfg.after;
wantedBy = [ "multi-user.target" ];
}) cfg.instances;
};
}