diff --git a/cluster/simulacrum/default.nix b/cluster/simulacrum/default.nix index a900de7..101b345 100644 --- a/cluster/simulacrum/default.nix +++ b/cluster/simulacrum/default.nix @@ -17,7 +17,8 @@ let }; nodes = lib.attrNames config.gods.fromLight; - digits = lib.attrsets.listToAttrs (lib.zipListsWith lib.nameValuePair nodes (lib.range 1 255)); + nodes' = lib.attrNames (config.gods.fromLight // { nowhere = null; }); + digits = lib.attrsets.listToAttrs (lib.zipListsWith lib.nameValuePair nodes' (lib.range 1 255)); depot' = extendModules { modules = [ ({ config, ... }: { @@ -51,6 +52,12 @@ testers.runNixOSTest { imports = [ serviceConfig.simulacrum.settings + ./nowhere + { + nodes.nowhere.imports = [ + config.flake.nixosModules.port-magic + ]; + } ] ++ allAugments; _module.args = { @@ -73,12 +80,15 @@ testers.runNixOSTest { ${hour.interfaces.primary.link} = { useDHCP = lib.mkForce false; virtual = true; - ipv4.addresses = lib.mkForce [ + ipv4.addresses = lib.mkForce ([ { address = hour.interfaces.primary.addr; prefixLength = 32; } - ]; + ] ++ lib.optional hour.interfaces.primary.isNat { + address = hour.interfaces.primary.addrPublic; + prefixLength = 32; + }); }; eth1.ipv4.routes = lib.pipe nodes [ (lib.filter (n: n != node)) @@ -95,6 +105,7 @@ testers.runNixOSTest { firewall.extraCommands = lib.mkAfter (lib.optionalString (hour.interfaces.primary.isNat) '' # self-nat iptables -t nat -A PREROUTING -d ${hour.interfaces.primary.addrPublic} -j DNAT --to-destination ${hour.interfaces.primary.addr} + iptables -t nat -A OUTPUT -d ${hour.interfaces.primary.addrPublic} -j DNAT --to-destination ${hour.interfaces.primary.addr} iptables -t nat -A POSTROUTING -s ${hour.interfaces.primary.addr} -j SNAT --to-source ${hour.interfaces.primary.addrPublic} ''); }; diff --git a/cluster/simulacrum/nowhere/default.nix b/cluster/simulacrum/nowhere/default.nix new file mode 100644 index 0000000..4e3aaaf --- /dev/null +++ b/cluster/simulacrum/nowhere/default.nix @@ -0,0 +1,101 @@ +{ cluster, config, lib, pkgs, ... }: + +let + lift = config; + + cfsslConfigIntermediateCA = pkgs.writeText "simulacrum-cfssl-config.json" (builtins.toJSON { + signing = { + default.expiry = "8760h"; + profiles.intermediate = { + expiry = "8760h"; + usages = [ + "cert sign" + "crl sign" + ]; + ca_constraint = { + is_ca = true; + max_path_len = 1; + }; + }; + }; + }); + + caCsr = pkgs.writeText "simulacrum-ca-csr.json" (builtins.toJSON { + CN = "Simulacrum Root CA"; + }); + + ca = pkgs.runCommand "simulacrum-snakeoil-ca" { + nativeBuildInputs = [ + pkgs.cfssl + ]; + } '' + mkdir $out + cfssl gencert --initca ${caCsr} | cfssljson --bare $out/ca + ''; + + genCert = extraFlags: csrData: let + csr = pkgs.writeText "simulacrum-csr.json" (builtins.toJSON csrData); + in pkgs.runCommand "simulacrum-snakeoil-cert" { + nativeBuildInputs = [ + pkgs.cfssl + ]; + } '' + mkdir $out + cfssl gencert ${lib.escapeShellArgs ([ + "--ca=file:${ca}/ca.pem" + "--ca-key=file:${ca}/ca-key.pem" + ] ++ extraFlags ++ [ + csr + ])} | cfssljson --bare $out/cert + ''; + + genHostCert = hostname: genCert [ "--hostname=${hostname}" ] { CN = hostname; }; + + getNodeAddr = node: (builtins.head config.nodes.${node}.networking.interfaces.eth1.ipv4.addresses).address; +in + +{ + imports = [ + ./options.nix + ]; + defaults = { + networking.hosts."${getNodeAddr "nowhere"}" = lib.attrNames config.nowhere.names; + security.pki.certificateFiles = [ + "${ca}/ca.pem" + ]; + }; + + nowhere.certs = { + inherit ca; + intermediate = genCert [ "--config=${cfsslConfigIntermediateCA}" "--profile=intermediate" ] { + CN = "Simulacrum Intermediate CA"; + }; + }; + + nodes.nowhere = { config, depot, ... }: { + networking = { + firewall.allowedTCPPorts = [ 443 ]; + interfaces.eth1.ipv4.routes = lib.mapAttrsToList (name: hour: { + address = hour.interfaces.primary.addrPublic; + prefixLength = 32; + via = getNodeAddr name; + }) depot.gods.fromLight; + nameservers = map (name: depot.hours.${name}.interfaces.primary.addrPublic) cluster.config.services.dns.nodes.authoritative; + }; + services.nginx = { + enable = true; + recommendedProxySettings = true; + virtualHosts = lib.mapAttrs (name: link: let + cert = genHostCert name; + in { + forceSSL = true; + sslCertificate = "${cert}/cert.pem"; + sslCertificateKey = "${cert}/cert-key.pem"; + locations."/" = { + proxyPass = config.links.${link}.url; + extraConfig = "proxy_ssl_verify off;"; + }; + }) lift.nowhere.names; + }; + }; +} diff --git a/cluster/simulacrum/nowhere/options.nix b/cluster/simulacrum/nowhere/options.nix new file mode 100644 index 0000000..b0420b6 --- /dev/null +++ b/cluster/simulacrum/nowhere/options.nix @@ -0,0 +1,16 @@ +{ lib, ... }: + +{ + options.nowhere = { + names = lib.mkOption { + description = "Hostnames that point Nowhere."; + type = with lib.types; attrsOf str; + default = {}; + }; + certs = lib.mkOption { + description = "Snakeoil certificate packages."; + type = with lib.types; attrsOf package; + default = {}; + }; + }; +}