From c40aba61335451219bc480f7b4ffccbc61d48d2b Mon Sep 17 00:00:00 2001 From: niten Date: Sun, 27 Feb 2022 15:27:10 -0800 Subject: [PATCH] Misc changes, add local-network option --- lib/fudo/default.nix | 2 + lib/fudo/domains.nix | 19 +++++++ lib/fudo/git.nix | 10 +--- lib/fudo/grafana.nix | 24 +++++++-- lib/fudo/ldap.nix | 20 +------- lib/fudo/mail-container.nix | 10 ++-- lib/fudo/mail.nix | 23 ++++++++- lib/fudo/mail/dovecot.nix | 18 +++---- lib/fudo/mail/postfix.nix | 4 +- lib/fudo/mail/rspamd.nix | 6 ++- lib/fudo/prometheus.nix | 5 +- lib/fudo/sites.nix | 9 ++-- lib/fudo/wireguard-client.nix | 73 +++++++++++++++++++++++++++ lib/fudo/wireguard.nix | 95 +++++++++++++++++++++++++++++++++++ lib/lib/text.nix | 4 +- lib/types/host.nix | 39 ++++++++++++++ 16 files changed, 305 insertions(+), 56 deletions(-) create mode 100644 lib/fudo/wireguard-client.nix create mode 100644 lib/fudo/wireguard.nix diff --git a/lib/fudo/default.nix b/lib/fudo/default.nix index 3517122..0042492 100644 --- a/lib/fudo/default.nix +++ b/lib/fudo/default.nix @@ -46,6 +46,8 @@ with lib; { ./users.nix ./vpn.nix ./webmail.nix + # ./wireguard.nix + # ./wireguard-client.nix ./wireless-networks.nix ./zones.nix ]; diff --git a/lib/fudo/domains.nix b/lib/fudo/domains.nix index b351d81..7548a44 100644 --- a/lib/fudo/domains.nix +++ b/lib/fudo/domains.nix @@ -91,6 +91,25 @@ let default = null; }; + wireguard = { + gateway = mkOption { + type = str; + description = "Host serving as WireGuard gateway for this domain."; + }; + + network = mkOption { + type = str; + description = "IP subnet used for WireGuard clients."; + default = "172.16.0.0/16"; + }; + + routed-network = mkOption { + type = nullOr str; + description = "Subnet of larger network for which we NAT traffic."; + default = "172.16.16.0/20"; + }; + }; + gssapi-realm = mkOption { type = str; description = "GSSAPI (i.e. Kerberos) realm of this domain."; diff --git a/lib/fudo/git.nix b/lib/fudo/git.nix index 09268dc..848977d 100644 --- a/lib/fudo/git.nix +++ b/lib/fudo/git.nix @@ -150,6 +150,8 @@ in { nginx = { enable = true; + recommendedOptimisation = true; + recommendedProxySettings = true; virtualHosts = { "${cfg.hostname}" = { @@ -158,14 +160,6 @@ in { locations."/" = { proxyPass = "http://127.0.0.1:${toString cfg.local-port}"; - - extraConfig = '' - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-By $server_addr:$server_port; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - ''; }; }; }; diff --git a/lib/fudo/grafana.nix b/lib/fudo/grafana.nix index 3a2f873..47bf840 100644 --- a/lib/fudo/grafana.nix +++ b/lib/fudo/grafana.nix @@ -220,19 +220,35 @@ in { }; extraOptions = mkIf (cfg.ldap != null) (let + base = cfg.ldap.base-dn; + config-file = pkgs.writeText "grafana-ldap.toml" '' [[servers]] host = "${concatStringsSep " " cfg.ldap.hosts}" port = 389 start_tls = true - bind_dn = "${cfg.ldap.bind-dn}" - bind_password = "''${GRAFANA_LDAP_BIND_PASSWD}" + + bind_dn = "uid=%s,ou=members,${base}" + search_filter = "(uid=%s)" - search_base_dns = [ "${cfg.ldap.base-dn}" ] + search_base_dns = [ "ou=members,${base}" ] + + group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))" + group_search_base_dns = ["ou=groups,${base}"] + group_search_filter_user_attribute = "uid" + + [[servers.group_mappings]] + group_dn = "cn=admin,ou=groups,${base}" + org_role = "Admin" + grafana_admin = true + + [[servers.group_mappings]] + group_dn = "cn=*,ou=groups,${base}" + org_role = "Viewer" ''; in { AUTH_LDAP_ENABLED = "true"; - AUTH_LDAP_ALLOW_SIGN_UP = "false"; + AUTH_LDAP_ALLOW_SIGN_UP = "true"; AUTH_LDAP_CONFIG_FILE = config-file; }); diff --git a/lib/fudo/ldap.nix b/lib/fudo/ldap.nix index 91c65ca..5ddc814 100644 --- a/lib/fudo/ldap.nix +++ b/lib/fudo/ldap.nix @@ -392,31 +392,13 @@ in { # olcRootDN = "cn=admin,${cfg.base}"; # olcRootPW = FIXME; # NOTE: this should be hashed... olcDbDirectory = "${cfg.state-directory}/database"; - olcDbIndex = [ "objectClass eq" "uid pres,eq" ]; + olcDbIndex = [ "objectClass eq" "uid pres,eq" "memberUid eq"]; olcAccess = makeAccess { "attrs=userPassword,shadowLastChange" = { - # "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage"; "dn.exact=cn=auth_reader,${cfg.base}" = "read"; "*" = "auth"; }; - # "dn=cn=admin,ou=groups,${cfg.base}" = { - # # "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage"; - # "anonymous" = "auth"; - # "*" = "read"; - # }; - # "dn.subtree=ou=groups,${cfg.base} attrs=memberUid" = { - # # "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage"; - # # "dn.regex=cn=[a-zA-Z][a-zA-Z0-9_]+,ou=hosts,${cfg.base}" = "write"; - # "anonymous" = "auth"; - # "*" = "read"; - # }; - # "dn.subtree=ou=members,${cfg.base} attrs=cn,sn,homeDirectory,loginShell,gecos,description,homeDirectory,uidNumber,gidNumber" = { - # # "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage"; - # "anonymous" = "auth"; - # "*" = "read"; - # }; "*" = { - # "dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" = "manage"; "anonymous" = "auth"; "*" = "read"; }; diff --git a/lib/fudo/mail-container.nix b/lib/fudo/mail-container.nix index 8a890ab..fcdf459 100644 --- a/lib/fudo/mail-container.nix +++ b/lib/fudo/mail-container.nix @@ -23,7 +23,7 @@ in rec { # Disable postfix on this host--it'll be run in the container instead services.postfix.enable = false; - services.nginx = mkIf cfg.monitoring { + services.nginx = mkIf cfg.monitoring.enable { enable = true; virtualHosts = let @@ -45,7 +45,7 @@ in rec { forceSSL = true; locations."/metrics/postfix" = { - proxyPass = "http://127.0.0.1:9154/metrics"; + proxyPass = "http://127.0.0.1:${cfg.monitoring.postfix-listen-port}/metrics"; extraConfig = '' ${proxy-headers} @@ -55,7 +55,7 @@ in rec { }; locations."/metrics/dovecot" = { - proxyPass = "http://127.0.0.1:9166/metrics"; + proxyPass = "http://127.0.0.1:${cfg.monitoring.dovecot-listen-port}/metrics"; extraConfig = '' ${proxy-headers} @@ -65,7 +65,7 @@ in rec { }; locations."/metrics/rspamd" = { - proxyPass = "http://127.0.0.1:7980/metrics"; + proxyPass = "http://127.0.0.1:${cfg.monitoring.rspamd-listen-port}/metrics"; extraConfig = '' ${proxy-headers} @@ -178,7 +178,7 @@ in rec { domain = cfg.domain; debug = cfg.debug; - monitoring = cfg.monitoring; + monitoring = cfg.monitoring.enable; state-directory = container-statedir; mail-directory = container-maildir; diff --git a/lib/fudo/mail.nix b/lib/fudo/mail.nix index e31d948..840bd8c 100644 --- a/lib/fudo/mail.nix +++ b/lib/fudo/mail.nix @@ -32,7 +32,27 @@ in { example = "ldaps://auth.fudo.org/"; }; - monitoring = mkEnableOption "Enable monitoring for the mail server."; + monitoring = { + enable = mkEnableOption "Enable monitoring for the mail server."; + + dovecot-listen-port = mkOption { + type = port; + description = "Port on which to serve Postfix metrics."; + default = 9166; + }; + + postfix-listen-port = mkOption { + type = port; + description = "Port on which to serve Postfix metrics."; + default = 9154; + }; + + rspamd-listen-port = mkOption { + type = port; + description = "Port on which to serve Postfix metrics."; + default = 7980; + }; + }; mail-user = mkOption { type = str; @@ -50,6 +70,7 @@ in { mail-user-id = mkOption { type = int; description = "UID of mail-user."; + default = 525; }; local-domains = mkOption { diff --git a/lib/fudo/mail/dovecot.nix b/lib/fudo/mail/dovecot.nix index e33050e..974b408 100644 --- a/lib/fudo/mail/dovecot.nix +++ b/lib/fudo/mail/dovecot.nix @@ -130,11 +130,11 @@ in { config = mkIf cfg.enable { - services.prometheus.exporters.dovecot = mkIf cfg.monitoring { + services.prometheus.exporters.dovecot = mkIf cfg.monitoring.enable { enable = true; scopes = ["user" "global"]; listenAddress = "127.0.0.1"; - port = 9166; + port = cfg.monitoring.dovecot-listen-port; socketPath = "/var/run/dovecot2/old-stats"; }; @@ -159,13 +159,13 @@ in { sieveScripts = { after = builtins.toFile "spam.sieve" '' - require "fileinto"; + require "fileinto"; - if header :is "X-Spam" "Yes" { - fileinto "Junk"; - stop; - } - ''; + if header :is "X-Spam" "Yes" { + fileinto "Junk"; + stop; + } + ''; }; mailboxes = cfg.mailboxes; @@ -173,7 +173,7 @@ in { extraConfig = '' #Extra Config - ${optionalString cfg.monitoring '' + ${optionalString cfg.monitoring.enable '' # The prometheus exporter still expects an older style of metrics mail_plugins = $mail_plugins old_stats service old-stats { diff --git a/lib/fudo/mail/postfix.nix b/lib/fudo/mail/postfix.nix index 7525f4d..c66fc5e 100644 --- a/lib/fudo/mail/postfix.nix +++ b/lib/fudo/mail/postfix.nix @@ -97,12 +97,14 @@ in { config = mkIf cfg.enable { - services.prometheus.exporters.postfix = mkIf cfg.monitoring { + services.prometheus.exporters.postfix = mkIf cfg.monitoring.enable { enable = true; systemd.enable = true; showqPath = "/var/lib/postfix/queue/public/showq"; user = config.services.postfix.user; group = config.services.postfix.group; + port = cfg.monitoring.postfix-listen-port; + listenAddress = "127.0.0.1"; }; services.postfix = { diff --git a/lib/fudo/mail/rspamd.nix b/lib/fudo/mail/rspamd.nix index 4bc9324..3edf906 100644 --- a/lib/fudo/mail/rspamd.nix +++ b/lib/fudo/mail/rspamd.nix @@ -6,7 +6,11 @@ let in { config = mkIf cfg.enable { - services.prometheus.exporters.rspamd.enable = true; + services.prometheus.exporters.rspamd = mkIf cfg.monitoring.enable { + enable = true; + listenAddress = "127.0.0.1"; + port = cfg.monitoring.rspamd-listen-port; + }; services.rspamd = { diff --git a/lib/fudo/prometheus.nix b/lib/fudo/prometheus.nix index d67fca7..c604cd4 100644 --- a/lib/fudo/prometheus.nix +++ b/lib/fudo/prometheus.nix @@ -135,9 +135,8 @@ in { enable = if (cfg.push-url != null) then true else false; web = { external-url = if cfg.push-url == null then - cfg.push-address - else - cfg.push-url; + cfg.push-address else + cfg.push-url; listen-address = cfg.push-address; }; }; diff --git a/lib/fudo/sites.nix b/lib/fudo/sites.nix index 5bf4638..3a67ef8 100644 --- a/lib/fudo/sites.nix +++ b/lib/fudo/sites.nix @@ -22,6 +22,12 @@ let description = "Network to be treated as local."; }; + private-network = mkOption { + type = nullOr str; + description = "Network of the local site."; + default = null; + }; + dynamic-network = mkOption { type = nullOr str; description = "Network to be allocated by DHCP."; @@ -60,9 +66,6 @@ let default = [ ]; }; - enable-monitoring = - mkEnableOption "Enable site-wide monitoring with prometheus."; - nameservers = mkOption { type = listOf str; description = "List of nameservers to be used by hosts at this site."; diff --git a/lib/fudo/wireguard-client.nix b/lib/fudo/wireguard-client.nix new file mode 100644 index 0000000..43c06ec --- /dev/null +++ b/lib/fudo/wireguard-client.nix @@ -0,0 +1,73 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + +in { + options.fudo.wireguard-client = with types; { + enable = mkEnableOption "Enable WireGuard client on this host."; + + server = { + ip = { + type = str; + description = "IP address of the WireGuard server."; + }; + + port = { + type = port; + description = "Port on which to contact WireGuard server."; + default = 51820; + }; + + public-key = { + type = str; + description = "Server public key."; + }; + }; + + assigned-ip = mkOption { + type = str; + description = "IP assigned to the local host."; + }; + + private-key-file = mkOption { + type = str; + description = "Path (on the host) to the host WireGuard private key."; + }; + + listen-port = mkOption { + type = port; + description = "Port on which to listen for incoming connections."; + default = 51820; + }; + + wireguard-interface = mkOption { + type = str; + description = "Name of the created WireGuard interface."; + default = "wgclient0"; + }; + + managed-subnet = mkOption { + type = str; + description = "Subnet to route to WireGuard. 0.0.0.0/0 will send all traffic to it."; + }; + }; + + config = { + networking = { + firewall.allowedUDPPorts = [ cfg.listen-port ]; + + wireguard.interfaces.${cfg.wireguard-interface} = { + ips = [ "${cfg.assigned-ip}/32" ]; + listen-port = cfg.listen-port; + private-key-file = cfg.private-key-file; + peers = [{ + publicKey = cfg.server.public-key; + allowedIPs = [ cfg.managed-subnet ]; + endpoint = "${cfg.server.ip}:${cfg.server.port}"; + persistentKeepalive = 25; + }]; + }; + }; + }; +} diff --git a/lib/fudo/wireguard.nix b/lib/fudo/wireguard.nix new file mode 100644 index 0000000..0a8444f --- /dev/null +++ b/lib/fudo/wireguard.nix @@ -0,0 +1,95 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.fudo.wireguard; + + peerOpts = { name, ... }: { + options = { + public-key = mkOption { + type = str; + description = "Peer public key."; + }; + + assigned-ip = mkOption { + type = str; + description = "IP assigned to this peer."; + }; + }; + }; + +in { + options.fudo.wireguard = with types; { + enable = mkEnableOption "Enable WireGuard server."; + + network = mkOption { + type = str; + description = "WireGuard managed IP subnet."; + default = "172.16.0.0/16"; + }; + + routed-network = mkOption { + type = str; + description = "Subnet of larger network for which we act as a gateway."; + default = "172.16.16.0/20"; + }; + + peers = mkOption { + type = attrsOf (submodule peerOpts); + description = "Map of peer to peer options."; + default = { }; + }; + + listen-port = mkOption { + type = port; + description = "Port on which to listen for incoming connections."; + default = 51820; + }; + + private-key-file = mkOption { + type = str; + description = "Path (on the host) to the host WireGuard private key."; + }; + + wireguard-interface = mkOption { + type = str; + description = "Name of the created WireGuard interface."; + default = "wgnet0"; + }; + + external-interface = mkOption { + type = str; + description = "Name of the host public-facing interface."; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = all (peerOpts: + pkgs.lib.ip.ipv4OnNetwork peerOpts.assigned-ip cfg.network) + (attrValues cfg.peers); + message = "Peer IPs must be on the assigned network."; + } + ]; + + networking = { + firewall.allowedUDPPorts = [ cfg.listen-port ]; + wireguard.interfaces.${cfg.wireguard-interface} = { + ips = [ cfg.network ]; + listenPort = cfg.listen-port; + postSetup = + "${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s ${cfg.network} -o ${cfg.external-interface} -j MASQUERADE"; + postShutdown = + "${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s ${cfg.network} -o ${cfg.external-interface} -j MASQUERADE"; + + privateKeyFile = cfg.private-key-file; + + peers = mapAttrs (_: peerOpts: { + public-key = peerOpts.public-key; + allowedIPs = [ "${peerOpts.assigned-ip}/32" ]; + }) cfg.peers; + }; + }; + }; +} diff --git a/lib/lib/text.nix b/lib/lib/text.nix index 321a374..d6cdab4 100644 --- a/lib/lib/text.nix +++ b/lib/lib/text.nix @@ -2,11 +2,11 @@ with pkgs.lib; let - get-basename = filename: + get-basename-without-hash = filename: head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename)); format-json-file = filename: pkgs.stdenv.mkDerivation { - name = "formatted-${get-basename filename}"; + name = "formatted-${get-basename-without-hash filename}"; phases = [ "installPhase" ]; buildInputs = with pkgs; [ python ]; installPhase = "python -mjson.tool ${filename} > $out"; diff --git a/lib/types/host.nix b/lib/types/host.nix index 7677c33..fb3cfef 100644 --- a/lib/types/host.nix +++ b/lib/types/host.nix @@ -264,6 +264,45 @@ in rec { default = false; }; + wireguard = let + clientOpts = { + options = { + ip = mkOption { + type = nullOr str; + description = "IP address assigned to this host in the WireGuard network."; + }; + + bound = mkOption { + type = bool; + description = "Whether to route all traffic from this host."; + default = false; + }; + }; + }; + + wireguardOpts = { + options = { + private-key-file = mkOption { + type = str; + description = "WireGuard private key file of the host."; + }; + + public-key = mkOption { + type = str; + description = "WireGuard public key."; + }; + + client = mkOption { + type = nullOr (submodule clientOpts); + default = null; + }; + }; + }; + in mkOption { + type = nullOr (submodule wireguardOpts); + default = null; + }; + initrd-network = let keypair-type = { ... }: { options = {