Back to Articles

Building Reproducible Penetration Testing Environments with NixOS: nixos-pentest

[ View on GitHub ]

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

Build Output

Custom Tooling

nixpkgs 24.11

module import

systemPackages

iptables rules

spawns instances

generates

packages as

flake.nix

Dependency Pinning

configuration.nix

System Settings

pentest-tools.nix

Tool Packages

anonsurf

Tor Routing

multitor

Multi-instance Tor

Shell Scripts

as Derivations

Tor TransPort

Multiple Tor

SOCKS Ports

proxychains4.conf

round_robin

nixosSystem

QEMU VM Image

System architecture — auto-generated

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.