From 96566ae523934f5a37b8d7c2a9ef928cd5c0d098 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Fri, 24 Mar 2023 15:26:39 +0100 Subject: refactor configuration syntax --- nix/deuxfleurs.nix | 190 +++++++++++++++++++++++++---------------------------- 1 file changed, 89 insertions(+), 101 deletions(-) (limited to 'nix/deuxfleurs.nix') diff --git a/nix/deuxfleurs.nix b/nix/deuxfleurs.nix index 6d27d5c..7632486 100644 --- a/nix/deuxfleurs.nix +++ b/nix/deuxfleurs.nix @@ -6,110 +6,98 @@ in with builtins; with pkgs.lib; { - options.deuxfleurs = - let wg_node = with types; submodule { - options = { - hostname = mkOption { - type = str; - description = "Host name"; - }; - site_name = mkOption { - type = nullOr str; - description = "Site where the node is located"; - default = null; - }; - IP = mkOption { - type = str; - description = "IP Address in the Wireguard network"; - }; - publicKey = mkOption { - type = str; - description = "Public key"; - }; - endpoint = mkOption { - type = nullOr str; - default = null; - description = "Wireguard endpoint on the public Internet"; - }; - }; - }; - in - { + options.deuxfleurs = with types; { # Parameters for individual nodes - ipv6 = mkOption { + hostName = mkOption { + description = "Node name"; + type = str; + }; + ipv6Address = mkOption { description = "Static public IPv6 address of this node"; - type = types.str; + type = str; }; staticIPv4.address = mkOption { description = "IP address (with prefix length) of this node on the local network interface"; - type = types.nullOr types.str; + type = nullOr str; default = null; }; - cluster_ip = mkOption { - description = "IP address of this node on the Wesher mesh network"; - type = types.str; - }; - - is_raft_server = mkOption { + isRaftServer = mkOption { description = "Make this node a RAFT server for the Nomad and Consul deployments"; - type = types.bool; + type = bool; default = false; }; # Parameters that generally vary between sites - site_name = mkOption { + siteName = mkOption { description = "Site (availability zone) on which this node is deployed"; - type = types.str; + type = str; }; staticIPv4.defaultGateway = mkOption { description = "IPv4 address of the default route on the local network interface"; - type = types.nullOr types.str; + type = nullOr str; default = null; }; - public_ipv4 = mkOption { + + publicIPv4 = mkOption { description = "Public IPv4 through which this node is accessible (possibly after port opening using DiploNAT), for domain names that are updated by D53"; - type = types.nullOr types.str; + type = nullOr str; default = null; }; - cname_target = mkOption { + cnameTarget = mkOption { description = "DNS CNAME target to use for services hosted in this site, for domain names that are updated by D53"; - type = types.nullOr types.str; + type = nullOr str; default = null; }; # Parameters common to all nodes - cluster_name = mkOption { + clusterName = mkOption { description = "Name of this Deuxfleurs deployment"; - type = types.str; + type = str; }; - cluster_prefix = mkOption { - description = "IP address prefix for the Wireguard overlay network"; - type = types.str; + clusterPrefix = mkOption { + description = "IP address prefix (and length) for the Wireguard overlay network"; + type = str; }; - cluster_prefix_length = mkOption { - description = "IP address prefix length for the Wireguard overlay network"; - type = types.int; - default = 16; - }; - cluster_nodes = mkOption { + clusterNodes = mkOption { description = "Nodes that are part of the cluster"; - type = types.listOf wg_node; + type = attrsOf (submodule { + options = { + siteName = mkOption { + type = nullOr str; + description = "Site where the node is located"; + default = null; + }; + address = mkOption { + type = str; + description = "IP Address in the Wireguard network"; + }; + publicKey = mkOption { + type = str; + description = "Public key"; + }; + endpoint = mkOption { + type = nullOr str; + default = null; + description = "Wireguard endpoint on the public Internet"; + }; + }; + }); }; - admin_accounts = mkOption { + adminAccounts = mkOption { description = "List of users having an admin account on cluster nodes, maps user names to a list of authorized SSH keys"; - type = types.attrsOf (types.listOf types.str); + type = attrsOf (listOf str); }; bootstrap = mkOption { description = "Whether to enable bootstrapping for Nomad and Consul"; - type = types.bool; + type = bool; default = false; }; # Options that generally stay to their default value wireguardPort = mkOption { description = "Port for incoming Wireguard VPN connections"; - type = types.port; + type = port; default = 33799; }; }; @@ -119,25 +107,29 @@ in ]; config = - let node_meta = { - "site" = cfg.site_name; - "public_ipv6" = cfg.ipv6; + let + clusterNodeCfg = getAttr cfg.hostName cfg.clusterNodes; + clusterAddress = clusterNodeCfg.address; + node_meta = { + "site" = cfg.siteName; + "public_ipv6" = cfg.ipv6Address; } // - (if cfg.public_ipv4 != null - then { "public_ipv4" = cfg.public_ipv4; } + (if cfg.publicIPv4 != null + then { "public_ipv4" = cfg.publicIPv4; } else {}) // - (if cfg.cname_target != null - then { "cname_target" = cfg.cname_target; } + (if cfg.cnameTarget != null + then { "cname_target" = cfg.cnameTarget; } else {}); in { + networking.hostName = cfg.hostName; # Configure admin accounts on all nodes - users.users = builtins.mapAttrs (name: publicKeys: { + users.users = mapAttrs (name: publicKeys: { isNormalUser = true; extraGroups = [ "wheel" ]; openssh.authorizedKeys.keys = publicKeys; - }) cfg.admin_accounts; + }) cfg.adminAccounts; # Configure network interfaces networking.useDHCP = false; @@ -148,7 +140,7 @@ in Name = "en* eth*"; }; ipv6AcceptRAConfig = { - Token = "static:${cfg.ipv6}"; + Token = "static:${cfg.ipv6Address}"; UseDNS = false; }; } // (if cfg.staticIPv4.address == null || cfg.staticIPv4.defaultGateway == null then { @@ -195,7 +187,7 @@ in # Forward .consul queries to Consul daemon { name = "consul."; - stub-addr = "${cfg.cluster_ip}@8600"; + stub-addr = "${clusterAddress}@8600"; stub-no-cache = true; stub-tcp-upstream = false; stub-tls-upstream = false; @@ -208,7 +200,7 @@ in # Configure Wireguard VPN between all nodes networking.wireguard.interfaces.wg0 = { - ips = [ "${cfg.cluster_ip}/16" ]; + ips = [ "${clusterAddress}/16" ]; listenPort = cfg.wireguardPort; privateKeyFile = "/var/lib/deuxfleurs/wireguard-keys/private"; mtu = 1420; @@ -220,28 +212,24 @@ in gossipSecretFile = "/var/lib/wgautomesh/gossip_secret"; persistFile = "/var/lib/wgautomesh/state"; upnpForwardPublicPort = - let - us = filter ({ hostname, ...}: hostname == config.networking.hostName) cfg.cluster_nodes; - in - if length us > 0 && (head us).endpoint != null then - strings.toInt (lists.last (split ":" (head us).endpoint)) + if clusterNodeCfg.endpoint != null then + strings.toInt (lists.last (split ":" clusterNodeCfg.endpoint)) else null; - peers = map ({ publicKey, endpoint, IP, ... }: { - address = IP; + peers = attrValues (mapAttrs (hostname: { publicKey, endpoint, address, ... }: { + inherit address endpoint; pubkey = publicKey; - endpoint = endpoint; - }) cfg.cluster_nodes; + }) cfg.clusterNodes); }; # Old code for wg-quick, we can use this as a fallback if we fail to make wgautomesh work # systemd.services."wg-quick-wg0".after = [ "unbound.service" ]; # networking.wg-quick.interfaces.wg0 = { - # address = [ "${cfg.cluster_ip}/16" ]; + # address = [ "${clusterAddress}/16" ]; # listenPort = cfg.wireguardPort; # privateKeyFile = "/var/lib/deuxfleurs/wireguard-keys/private"; # mtu = 1420; - # peers = map ({ publicKey, endpoint, IP, ... }: { + # peers = map ({ publicKey, endpoint, address, ... }: { # inherit publicKey endpoint; - # allowedIPs = [ "${IP}/32" ]; + # allowedIPs = [ "${address}/32" ]; # persistentKeepalive = 25; # }; @@ -255,25 +243,25 @@ in ''; # Configure /etc/hosts to link all hostnames to their Wireguard IP - networking.extraHosts = builtins.concatStringsSep "\n" (map - ({ hostname, IP, ...}: "${IP} ${hostname}") - cfg.cluster_nodes); + networking.extraHosts = concatStringsSep "\n" (attrValues (mapAttrs + (hostname: { address, ...}: "${address} ${hostname}") + cfg.clusterNodes)); # Enable Hashicorp Consul & Nomad services.consul.enable = true; systemd.services.consul.after = [ "wg-quick-wg0.service" ]; services.consul.extraConfig = - (if cfg.is_raft_server + (if cfg.isRaftServer then { server = true; } // (if cfg.bootstrap then { bootstrap_expect = 3; } else {}) else {}) // { inherit node_meta; - datacenter = cfg.cluster_name; + datacenter = cfg.clusterName; ui_config = { enabled = true; }; - bind_addr = "${cfg.cluster_ip}"; + bind_addr = "${clusterAddress}"; addresses = { https = "0.0.0.0"; @@ -303,18 +291,18 @@ in pkgs.zstd ]; services.nomad.settings = - (if cfg.is_raft_server + (if cfg.isRaftServer then { server = { enabled = true; } // (if cfg.bootstrap then { bootstrap_expect = 3; } else {}); } else {}) // { - region = cfg.cluster_name; - datacenter = cfg.site_name; + region = cfg.clusterName; + datacenter = cfg.siteName; advertise = { - rpc = "${cfg.cluster_ip}"; - http = "${cfg.cluster_ip}"; - serf = "${cfg.cluster_ip}"; + rpc = "${clusterAddress}"; + http = "${clusterAddress}"; + serf = "${clusterAddress}"; }; consul = { address = "localhost:8501"; @@ -367,7 +355,7 @@ in allowedTCPPorts = [ # Allow anyone to connect on SSH port - (builtins.head ({ openssh.ports = [22]; } // config.services).openssh.ports) + (head ({ openssh.ports = [22]; } // config.services).openssh.ports) ]; allowedUDPPorts = [ @@ -385,14 +373,14 @@ in iptables -A INPUT -s 172.17.0.0/16 -j ACCEPT # Allow other nodes on VPN to access all ports - iptables -A INPUT -s ${cfg.cluster_prefix}/${toString cfg.cluster_prefix_length} -j ACCEPT + iptables -A INPUT -s ${cfg.clusterPrefix} -j ACCEPT ''; # When stopping firewall, delete all rules that were configured manually above extraStopCommands = '' iptables -D INPUT -s 192.168.0.0/16 -p udp --sport 1900 -j ACCEPT iptables -D INPUT -s 172.17.0.0/16 -j ACCEPT - iptables -D INPUT -s ${cfg.cluster_prefix}/${toString cfg.cluster_prefix_length} -j ACCEPT + iptables -D INPUT -s ${cfg.clusterPrefix} -j ACCEPT ''; }; }; -- cgit v1.2.3