Building Reproducible Penetration Testing Environments with NixOS: nixos-pentest
Hook
Your Kali Linux installation worked perfectly last week. Today, after routine updates, half your tools are broken and you have no idea which package caused it. What if you could roll back your entire pentesting environment with a single command?
Context
Traditional penetration testing distributions like Kali Linux and Parrot Security OS face a fundamental tension: security professionals need bleeding-edge exploit tools, but system updates frequently break working configurations. Teams running multiple operators struggle with environment drift—one analyst's metasploit works flawlessly while another's crashes on launch due to subtly different Python dependencies. The standard solution involves careful snapshot management, extensive documentation about which versions work together, and resigned acceptance that 'it works on my machine' is an occupational hazard.
nixos-pentest takes a radically different approach by applying NixOS's functional package management philosophy to offensive security. Instead of imperatively installing tools onto a mutable filesystem, the entire system configuration—desktop environment, user accounts, network settings, and all 80+ penetration testing tools—is declared in version-controlled Nix expressions. This declarative approach means you can reproduce the exact same environment on any machine, roll back breaking changes atomically, and distribute identical tooling across your red team by sharing a Git repository.
Technical Insight
The architecture splits concerns across three core files: flake.nix pins exact dependency versions, configuration.nix defines system-level settings, and pentest-tools.nix catalogs the security tooling. This separation enables granular control—updating your tool list doesn't require touching network configuration, and vice versa.
The flake structure demonstrates Nix's approach to reproducibility. Here's the simplified version:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
};
outputs = { self, nixpkgs }: {
nixosConfigurations.pentest-vm = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
./pentest-tools.nix
];
};
};
}
The inputs section locks nixpkgs to release 24.11. When you run nix flake update, it generates a flake.lock file with cryptographic hashes for every dependency—not just top-level packages, but their transitive dependencies too. This means a colleague running nixos-rebuild switch --flake .#pentest-vm six months later gets identical versions of nmap, metasploit, and even obscure libraries like libpcap.
The most architecturally interesting component is the custom tooling implementation. Rather than shipping bash scripts in /usr/local/bin, they're packaged as proper Nix derivations. The multitor tool demonstrates this pattern—it spawns multiple Tor instances with offset SOCKS ports, enabling distributed attacks where each connection exits from a different node:
multitor = pkgs.writeScriptBin "multitor" ''
#!/bin/sh
NUM_INSTANCES=$${1:-5}
BASE_PORT=9050
for i in $(seq 0 $(($NUM_INSTANCES - 1))); do
PORT=$((BASE_PORT + i))
DATA_DIR="/tmp/tor-$i"
mkdir -p $DATA_DIR
${pkgs.tor}/bin/tor \
--SOCKSPort $PORT \
--DataDirectory $DATA_DIR \
--RunAsDaemon 1
done
# Generate proxychains config
echo "round_robin" > /tmp/multitor-proxychains.conf
echo "[ProxyList]" >> /tmp/multitor-proxychains.conf
for i in $(seq 0 $(($NUM_INSTANCES - 1))); do
echo "socks5 127.0.0.1 $((BASE_PORT + i))" >> /tmp/multitor-proxychains.conf
done
''';
The writeScriptBin function creates a derivation that builds a script with pkgs.tor properly referenced—no PATH manipulation needed. When you install this via environment.systemPackages = [ multitor ];, Nix ensures Tor is available and correctly wired. The round-robin proxychains configuration means tools like hydra or nikto automatically distribute requests across exit nodes, circumventing single-IP rate limiting.
The anonsurf implementation showcases service-level integration. It manipulates iptables to route all traffic through Tor's TransPort while exempting local networks:
# Simplified from actual implementation
iptables -t nat -A OUTPUT -d 192.168.0.0/16 -j RETURN
iptables -t nat -A OUTPUT -d 10.0.0.0/8 -j RETURN
iptables -t nat -A OUTPUT -d 172.16.0.0/12 -j RETURN
iptables -t nat -A OUTPUT -p tcp --syn -j REDIRECT --to-ports 9040
iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports 5353
These LAN exemptions are critical for homelab scenarios—you maintain SSH access to local infrastructure while external reconnaissance traffic exits through Tor. The script packages this as a systemd service, so you can systemctl start anonsurf with proper dependency ordering (ensuring Tor starts first).
The tool selection in pentest-tools.nix covers reconnaissance (nmap, masscan, subfinder), exploitation (metasploit, sqlmap, impacket), post-exploitation (mimikatz via wine, pwncat), and wireless tools (aircrack-ng, wifite2). Each package reference like pkgs.nmap pulls from nixpkgs with all dependencies resolved. Adding a tool means appending it to the list and rebuilding—Nix computes the minimal system delta and applies it atomically. If something breaks, nixos-rebuild switch --rollback reverts to the previous generation in seconds.
Gotcha
The fundamental limitation is tool coverage gaps. NixOS's nixpkgs repository contains fewer security tools than Kali's apt repositories—CrackMapExec, BloodHound, Covenant, and many Windows-focused tools simply don't have Nix packages. You end up supplementing with Docker containers or manual installations, which defeats the reproducibility promise. Running docker run byt3bl33d3r/crackmapexec works, but now you're managing two package ecosystems and Docker state isn't captured in your Nix configuration.
The custom scripts lack production-grade error handling. The multitor implementation writes to world-readable /tmp/multitor-proxychains.conf without cleaning up previous instances. If you run multitor twice, you get duplicate Tor daemons consuming resources with no health monitoring. The anonsurf script doesn't validate that Tor successfully started before modifying iptables—if the Tor daemon fails, you've firewalled yourself into a non-functional networking state with no graceful rollback. For homelab experimentation this is acceptable, but operational use requires hardening.
Persistence and engagement data management are unaddressed. The pentest-rec tool records terminal sessions to markdown reports in your home directory, but there's no backup strategy, no database for aggregating findings across engagements, and no sync mechanism for team collaboration. Multi-operator scenarios require building additional tooling around these scripts.
Verdict
Use if: You're already running NixOS infrastructure and want pentesting capabilities on the same platform, you prioritize reproducible environments over comprehensive tool coverage, or you're tired of 'works on my machine' debugging across a distributed red team. The atomic rollback capability alone justifies this for teams standardizing tooling where version drift causes operational friction. The multitor implementation is genuinely useful for distributed brute-forcing scenarios. Skip if: You require enterprise-grade Windows/AD tooling like BloodHound or Covenant as primary workflow components, you're new to Nix (the learning curve isn't worth it versus running Parrot directly), or you need the 600+ tools that Kali provides. The gap coverage problem—where essential tools require Docker anyway—undermines the core value unless you're willing to contribute Nix packages upstream.