{ lib, config, pkgs, ... }: with lib; let cfg = config.local; in { options.local.nspawn.dmz = with types; { enable = mkEnableOption "DMZ services in a container"; net = mkOption { type = str; }; netBits = mkOption { type = int; }; hostAddr = mkOption { type = str; }; system = mkOption { type = attrs; }; }; # Situación con os-release # # La idea aquí es poder hacer 'btrfs subvol create /var/lib/machines/foo' y # dejar que systemd-nspawn y el activation script creen todo lo demás. Esto # no sirve bien debido a la prueba barata que hace systemd para revisar si el # árbol parece contener una imagen de sistema operativo. Esta prueba falla en # dos momentos distintos: # # 1. Inmediatamente tras crear un árbol vacío, puesto que os-release no existe. # La solución naive es 'mkdir rootfs/etc && touch rootfs/etc/os-release'. # # 2. Luego de reiniciar el contenedor una vez que NixOS ha preparado /etc, ya que # systemd espera un archivo regular y no telera el symlink a la store. # # Resulta ser que systemd revisa tanto /etc/os-release como /usr/lib/os-release. # NixOS evidentemente no usa la segunda ruta por ser FHS, así que la duct tape # final es 'mkdir rootfs/usr/lib && touch rootfs/usr/lib/os-release'. config = mkIf cfg.nspawn.dmz.enable { local = { mailHost = { mdaListen = cfg.nspawn.dmz.hostAddr; saslPort = 11000; lmtpPort = 11001; }; nspawn.dmz = { system = let containerModule = { ... }: { #TODO: urgente: bloquear puertos de dovecot a non-postfix con iptables config = { boot.isContainer = true; local.mta = { mdaAddr = cfg.mailHost.mdaListen; inherit (cfg.mailHost) saslPort lmtpPort; }; }; }; in pkgs.nixos [ ../dmz containerModule ]; net = "10.34.3.0"; netBits = 28; hostAddr = "10.34.3.1"; }; }; systemd = { nspawn.dmz = { execConfig.PrivateUsers = "pick"; filesConfig.BindReadOnly = [ # idmap porque algunos hacks en nixpkgs (postfix-setup.service) # asumen que la store es de root "/nix/store:/nix/store:idmap" "${cfg.nspawn.dmz.system.toplevel}/init:/sbin/init" ]; networkConfig.Port = [ "tcp:25" "tcp:80" "tcp:443" "tcp:465" "tcp:587" ]; }; network.networks."40-ve-dmz" = { matchConfig = { Name = "ve-dmz"; Driver = "veth"; }; networkConfig = { Address = "${cfg.nspawn.dmz.hostAddr}/${toString cfg.nspawn.dmz.netBits}"; LinkLocalAddressing = "yes"; DHCPServer = "yes"; IPMasquerade = "both"; LLDP = "yes"; EmitLLDP = "customer-bridge"; IPv6SendRA = "yes"; }; # IP de contenedor fijada en hostAddr + 1 dhcpServerConfig = { PoolOffset = 2; PoolSize = 1; }; }; services = { dovecot2.after = [ "systemd-nspawn@dmz.service" ]; "systemd-nspawn@dmz" = { overrideStrategy = "asDropin"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "machines.target" ]; }; }; }; networking.firewall = { allowedTCPPorts = [ 25 80 443 ]; interfaces.ve-dmz = { allowedTCPPorts = [ cfg.mailHost.saslPort cfg.mailHost.lmtpPort ]; allowedUDPPorts = [ 67 ]; # DHCP }; }; }; }