summaryrefslogtreecommitdiff
path: root/sys/nspawn
diff options
context:
space:
mode:
Diffstat (limited to 'sys/nspawn')
-rw-r--r--sys/nspawn/default.nix5
-rw-r--r--sys/nspawn/dmz.nix236
2 files changed, 241 insertions, 0 deletions
diff --git a/sys/nspawn/default.nix b/sys/nspawn/default.nix
new file mode 100644
index 0000000..15e60de
--- /dev/null
+++ b/sys/nspawn/default.nix
@@ -0,0 +1,5 @@
+{
+ imports = [
+ ./dmz.nix
+ ];
+}
diff --git a/sys/nspawn/dmz.nix b/sys/nspawn/dmz.nix
new file mode 100644
index 0000000..626993d
--- /dev/null
+++ b/sys/nspawn/dmz.nix
@@ -0,0 +1,236 @@
+{
+ config,
+ lib,
+ pkgs,
+ flakes,
+ doctrine,
+ ...
+}:
+with lib; let
+ cfg = config.local.nspawn.dmz;
+ inherit (config.local) mailHost;
+
+ dmzNet = config.local.nets.${cfg.netName};
+
+ hassPort = config.services.home-assistant.config.http.server_port;
+ hassEnable = config.local.home-assistant.enable;
+in {
+ options.local.nspawn.dmz = {
+ enable = mkEnableOption "DMZ services in a container";
+
+ dns64 = mkOption {
+ type = types.str;
+ };
+
+ netName = mkOption {
+ type = types.str;
+ };
+
+ net6 = mkOption {
+ type = types.str;
+ readOnly = true;
+ };
+
+ hostAddr6 = mkOption {
+ type = types.str;
+ readOnly = true;
+ };
+
+ mtaAddr6 = mkOption {
+ type = types.str;
+ readOnly = true;
+ };
+
+ system = mkOption {
+ type = types.raw;
+ };
+ };
+
+ # 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.enable {
+ local = {
+ mailHost.mdaListen = cfg.hostAddr6;
+
+ nspawn.dmz = {
+ mtaAddr6 = dmzNet.hosts.mta.v6.address;
+ hostAddr6 = dmzNet.hosts.gateway.v6.address;
+
+ system = let
+ containerModule = {...}: {
+ #TODO: urgente: bloquear puertos de dovecot a non-postfix con iptables
+ config = {
+ local = {
+ preset.dmz = {
+ enable = true;
+ container = true;
+ };
+
+ mta = {
+ mdaAddr = "[${mailHost.mdaListen}]";
+ mtaListen = cfg.mtaAddr6;
+ inherit (mailHost) saslPort lmtpPort;
+ };
+
+ web.sites = {
+ home = {
+ enable = hassEnable;
+ proxyUrl = "http://[${cfg.hostAddr6}]:${toString hassPort}";
+ };
+ };
+ };
+
+ nixpkgs = {
+ pkgs = mkDefault pkgs;
+ localSystem = mkDefault pkgs.stdenv.hostPlatform;
+ };
+
+ services.nginx.virtualHosts = {
+ "${config.local.domains.imap.main}".locations."^~ /.well-known/acme-challenge/" = {
+ root = "/var/lib/acme/acme-challenge";
+
+ extraConfig = ''
+ auth_basic off;
+ auth_request off;
+ '';
+ };
+ };
+
+ systemd.network.networks."40-host0" = {
+ name = "host0";
+
+ networkConfig = {
+ DNS = [cfg.dns64];
+
+ DHCP = "no";
+ IPv6AcceptRA = "yes";
+ LinkLocalAddressing = "ipv6";
+ };
+
+ ipv6AcceptRAConfig = {
+ Token = [
+ "static:::${dmzNet.hosts.dmz.v6.suffix}"
+ "eui64"
+ "static:::${dmzNet.hosts.mta.v6.suffix}"
+ "static:::${dmzNet.hosts.web.v6.suffix}"
+ ];
+
+ UseDNS = false;
+ };
+ };
+ };
+ };
+ in
+ flakes.trivionomicon.lib.mkSystem {
+ inherit doctrine flakes pkgs;
+
+ modules = [
+ ../.
+ containerModule
+ ];
+ };
+ };
+ };
+
+ services = {
+ home-assistant.config.http = mkIf hassEnable {
+ server_host = [cfg.hostAddr6];
+ trusted_proxies = [dmzNet.hosts.web.v6.address];
+ use_x_forwarded_for = true;
+ };
+ };
+
+ 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.system.config.system.build.toplevel}/init:/sbin/init"
+ ];
+ };
+
+ network.networks."40-ve-dmz" = {
+ matchConfig = {
+ Name = "ve-dmz";
+ Driver = "veth";
+ };
+
+ addresses = [
+ {
+ Address = dmzNet.hosts.gateway.v6.cidr;
+ AddPrefixRoute = "no";
+ PreferredLifetime = 0;
+ }
+ ];
+
+ networkConfig = {
+ LinkLocalAddressing = "ipv6";
+ DHCPServer = "no";
+ IPMasquerade = "no";
+ LLDP = "no";
+ EmitLLDP = "no";
+ IPv6SendRA = "yes";
+ IPv6AcceptRA = "no";
+ };
+
+ ipv6Prefixes = [
+ {
+ Assign = "no";
+ Prefix = dmzNet.v6.cidr;
+ }
+ ];
+
+ routes = [
+ {
+ Destination = dmzNet.v6.cidr;
+ # Sin esto, siempre se escogerá una ULA como source address debido a "PreferredLifetime = 0" en la GUA
+ PreferredSource = dmzNet.hosts.gateway.v6.address;
+ }
+ ];
+ };
+
+ services = {
+ dovecot.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 =
+ [mailHost.saslPort mailHost.lmtpPort]
+ ++ optional hassEnable hassPort;
+
+ allowedUDPPorts = [67]; # DHCP
+ };
+ };
+ };
+}