diff --git a/config/fudo/backplane/dns.nix b/config/fudo/backplane/dns.nix index e6dee50..068cc2f 100644 --- a/config/fudo/backplane/dns.nix +++ b/config/fudo/backplane/dns.nix @@ -82,12 +82,21 @@ in { default = 53; }; - listen-addresses = mkOption { + listen-v4-addresses = mkOption { type = with types; listOf str; - description = "IP addresses on which to listen for dns requests."; + description = "IPv4 addresses on which to listen for dns requests."; default = [ "0.0.0.0" ]; }; + listen-v6-addresses = mkOption { + type = with types; listOf str; + description = "IPv6 addresses on which to listen for dns requests."; + example = [ + "[abcd::1]" + ]; + default = []; + }; + required-services = mkOption { type = with types; listOf str; description = "A list of services required before the DNS server can start."; @@ -152,7 +161,8 @@ in { backplane-powerdns = let configDir = pkgs.writeTextDir "pdns.conf" '' - local-address=${lib.concatStringsSep ", " cfg.listen-addresses} + local-address=${lib.concatStringsSep ", " cfg.listen-v4-addresses} + local-ipv6=${lib.concatStringsSep ", " cfg.listen-v6-addresses} local-port=${toString cfg.port} launch= include-dir=${powerdns-conf-dir}/ diff --git a/config/fudo/client/dns.nix b/config/fudo/client/dns.nix index 7a9d348..a1db07e 100644 --- a/config/fudo/client/dns.nix +++ b/config/fudo/client/dns.nix @@ -86,6 +86,19 @@ in { }; }; + services.backplane-dns-client-pw-file = { + enable = true; + requiredBy = [ "backplane-dns-client.services" ]; + reloadIfChanged = true; + serviceConfig = { + Type = "oneshot"; + }; + script = '' + chmod 600 ${cfg.password-file} + chown ${cfg.user} ${cfg.password-file} + ''; + }; + services.backplane-dns-client = { enable = true; serviceConfig = { @@ -94,6 +107,7 @@ in { User = cfg.user; }; path = [ pkgs.openssh ]; + reloadIfChanged = true; script = '' ${pkgs.backplane-dns-client}/bin/backplane-dns-client ${optionalString cfg.ipv4 "-4"} ${optionalString cfg.ipv6 "-6"} ${optionalString cfg.sshfp "-f"} ${optionalString (cfg.external-interface != null) "--interface=${cfg.external-interface}"} --domain=${cfg.domain} --server=${cfg.server} --password-file=${cfg.password-file} ''; diff --git a/config/fudo/dns.nix b/config/fudo/dns.nix index 9de4805..6a345ef 100644 --- a/config/fudo/dns.nix +++ b/config/fudo/dns.nix @@ -5,8 +5,6 @@ with lib; let cfg = config.fudo.dns; - ip = import ../../lib/ip.nix { lib = lib; }; - join-lines = concatStringsSep "\n"; hostOpts = { host, ...}: { @@ -32,6 +30,19 @@ let description = '' A list of DNS SSHFP records for this host. ''; + default = []; + }; + + description = mkOption { + type = with types; nullOr str; + description = "Description of this host for a TXT record."; + default = null; + }; + + rp = mkOption { + type = with types; nullOr str; + description = "Responsible person."; + default = null; }; }; }; @@ -71,7 +82,11 @@ let description = "A map of hostname to { host_attributes }."; }; - dnssec = mkEnableOption "Enable DNSSEC security for this zone."; + dnssec = mkOption { + type = bool; + description = "Enable DNSSEC security for this zone."; + default = true; + }; mx = mkOption { type = listOf str; @@ -125,9 +140,12 @@ let }; }; - hostARecords = host: data: + hostRecords = host: data: join-lines ((map (ip: "${host} IN A ${ip}") data.ip-addresses) ++ - (map (ip: "${host} IN AAAA ${ip}") data.ipv6-addresses)); + (map (ip: "${host} IN AAAA ${ip}") data.ipv6-addresses) ++ + (map (sshfp: "${host} IN SSHFP ${sshfp}") data.ssh-fingerprints) ++ + (optional (data.rp != null) "${host} IN RP ${data.rp}") ++ + (optional (data.description != null) "${host} IN TXT ${data.description}")); makeSrvRecords = protocol: type: records: join-lines (map (record: "_${type}._${protocol} IN SRV ${toString record.priority} ${toString record.weight} ${toString record.port} ${toString record.host}.") @@ -137,8 +155,6 @@ let cnameRecord = alias: host: "${alias} IN CNAME ${host}"; - hostSshFpRecords = host: data: join-lines (map (sshfp: "${host} IN SSHFP ${sshfp}") data.ssh-fingerprints); - mxRecords = mxs: concatStringsSep "\n" (map (mx: "@ IN MX 10 ${mx}.") mxs); @@ -147,9 +163,7 @@ let optionalString (dmarc-email != null) ''_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"''; - nsRecords = ns-hosts: - join-lines ((mapAttrsToList (host: _: "@ IN NS ${host}.") ns-hosts) ++ - (mapAttrsToList (host: ip: "${host} IN A ${ip}") ns-hosts)); + nsRecords = dom: ns-hosts: join-lines (mapAttrsToList (host: _: "@ IN NS ${host}.${dom}.") ns-hosts); in { @@ -157,10 +171,21 @@ in { enable = mkEnableOption "Enable master DNS services."; # FIXME: This should allow for AAAA addresses too... - dns-hosts = mkOption { - type = loaOf str; + nameservers = mkOption { + type = loaOf (submodule hostOpts); description = "Map of domain nameserver FQDNs to IP."; - example = { "ns1.domain.com" = "1.1.1.1"; }; + example = { + "ns1.domain.com" = { + ip-addresses = [ "1.1.1.1" ]; + ipv6-addresses = []; + description = "my fancy dns server"; + }; + }; + }; + + identity = mkOption { + type = str; + description = "The identity (CH TXT ID.SERVER) of this host."; }; domains = mkOption { @@ -179,7 +204,7 @@ in { config = mkIf cfg.enable { services.nsd = { enable = true; - identity = "procul.informis.land"; + identity = cfg.identity; interfaces = cfg.listen-ips; zones = mapAttrs' (dom: dom-cfg: nameValuePair "${dom}." { @@ -202,13 +227,13 @@ in { $TTL 6h - ${nsRecords cfg.dns-hosts} + ${nsRecords dom cfg.nameservers} + ${join-lines (mapAttrsToList hostRecords cfg.nameservers)} ${dmarcRecord dom-cfg.dmarc-report-address} ${join-lines (mapAttrsToList makeSrvProtocolRecords dom-cfg.srv-records)} - ${join-lines (mapAttrsToList hostARecords dom-cfg.hosts)} - ${join-lines (mapAttrsToList hostSshFpRecords dom-cfg.hosts)} + ${join-lines (mapAttrsToList hostRecords dom-cfg.hosts)} ${join-lines (mapAttrsToList cnameRecord dom-cfg.aliases)} ${join-lines dom-cfg.extra-dns-records} ''; diff --git a/config/fudo/local-network.nix b/config/fudo/local-network.nix index 023ad5d..5435dac 100644 --- a/config/fudo/local-network.nix +++ b/config/fudo/local-network.nix @@ -7,9 +7,6 @@ let join-lines = concatStringsSep "\n"; - ip = import ../../lib/ip.nix { inherit lib; }; - dns = import ../../lib/dns.nix { inherit lib; }; - hostOpts = { hostname, ... }: { options = { ip-address = mkOption { diff --git a/config/fudo/vpn.nix b/config/fudo/vpn.nix index 1467fbb..7f6eeeb 100644 --- a/config/fudo/vpn.nix +++ b/config/fudo/vpn.nix @@ -4,29 +4,44 @@ with lib; let cfg = config.fudo.vpn; - peerOpts = { peer-name, ... }: { - options = with types; { - public-key = mkOption { - type = str; - description = "Peer public key."; - }; + generate-pubkey-pkg = name: privkey: + pkgs.runCommand "wireguard-${name}-pubkey" { + WIREGUARD_PRIVATE_KEY = privkey; + } '' + mkdir $out + PUBKEY=$(echo $WIREGUARD_PRIVATE_KEY | ${pkgs.wireguard-tools}/bin/wg pubkey) + echo $PUBKEY > $out/pubkey.key + ''; - allowed-ips = mkOption { - type = listOf str; - description = "List of allowed IP ranges from which this peer can connect."; - example = [ "10.100.0.0/16" ]; - default = []; - }; - }; + generate-client-config = privkey-file: server-pubkey: network: server-ip: listen-port: dns-servers: '' + [Interface] + Address = ${ip.networkMinIp network} + PrivateKey = ${fileContents privkey-file} + ListenPort = ${toString listen-port} + DNS = ${concatStringsSep ", " dns-servers} + + [Peer] + PublicKey = ${server-pubkey} + Endpoint = ${server-ip}:${toString listen-port} + AllowedIps = 0.0.0.0/0, ::/0 + PersistentKeepalive = 25 + ''; + + generate-peer-entry = peer-name: peer-privkey-path: peer-allowed-ips: let + peer-pkg = generate-pubkey-pkg "client-${peer-name}" (fileContents peer-privkey-path); + pubkey-path = "${peer-pkg}/pubkey.key"; + in { + publicKey = fileContents pubkey-path; + allowedIPs = peer-allowed-ips; }; in { options.fudo.vpn = with types; { enable = mkEnableOption "Enable Fudo VPN"; - ips = mkOption { + network = mkOption { type = str; - description = "IP range to assign this interface."; + description = "Network range to assign this interface."; default = "10.100.0.0/16"; }; @@ -42,30 +57,68 @@ in { default = 51820; }; + dns-servers = mkOption { + type = listOf str; + description = "A list of dns servers to pass to clients."; + default = ["1.1.1.1" "8.8.8.8"]; + }; + + server-ip = mkOption { + type = str; + description = "IP of this WireGuard server."; + }; + peers = mkOption { - type = loaOf (submodule peerOpts); - description = "A list of peers allowed to connect."; + type = loaOf str; + description = "A map of peers to shared private keys."; default = {}; example = { - peer0 = { - public-key = "xyz"; - allowed-ips = ["10.100.1.0/24"]; - }; + peer0 = "/path/to/priv.key"; }; }; }; config = mkIf cfg.enable { + environment.etc = let + peer-data = imap1 (i: peer:{ + name = peer.name; + privkey-path = peer.privkey-path; + network-range = let + base = ip.intToIpv4 + ((ip.ipv4ToInt (ip.getNetworkBase cfg.network)) + (i * 256)); + in "${base}/24"; + }) (mapAttrsToList (name: privkey-path: { + name = name; + privkey-path = privkey-path; + }) cfg.peers); + + server-pubkey-pkg = generate-pubkey-pkg "server-pubkey" (fileContents cfg.private-key-file); + + server-pubkey = fileContents "${server-pubkey-pkg}/pubkey.key"; + + in listToAttrs + (map (peer: nameValuePair "wireguard/clients/${peer.name}.conf" { + mode = "0400"; + user = "root"; + group = "root"; + text = generate-client-config + peer.privkey-path + server-pubkey + peer.network-range + cfg.server-ip + cfg.listen-port + cfg.dns-servers; + }) peer-data); + networking.wireguard = { enable = true; interfaces.wgtun0 = { generatePrivateKeyFile = false; - ips = [ cfg.ips ]; + ips = [ cfg.network ]; listenPort = cfg.listen-port; - peers = mapAttrsToList (peer-name: peer-config: { - publicKey = peer-config.public-key; - allowedIPs = peer-config.allowed-ips; - }) cfg.peers; + peers = mapAttrsToList + (name: private-key: generate-peer-entry name private-key ["0.0.0.0/0" "::/0"]) + cfg.peers; privateKeyFile = cfg.private-key-file; }; }; diff --git a/defaults.nix b/defaults.nix index daa666a..361acad 100644 --- a/defaults.nix +++ b/defaults.nix @@ -1,6 +1,6 @@ # Ref: https://learnxinyminutes.com/docs/nix/ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: { imports = [ @@ -9,6 +9,12 @@ ./config/local.nix ]; + lib = { + buildLisp = import ./lib/buildLisp.nix; + ip = import ./lib/ip.nix; + dns = import ./lib/dns.nix; + }; + nixpkgs.config.allowUnfree = true; security.acme.acceptTerms = true; diff --git a/fudo/fudo.org.nix b/fudo/fudo.org.nix new file mode 100644 index 0000000..c977504 --- /dev/null +++ b/fudo/fudo.org.nix @@ -0,0 +1,268 @@ +{ config, ... }: + +{ + dnssec = true; + + mx = ["mail.fudo.org"]; + + hosts = { + cisco = { + ip-addresses = [ "198.163.150.211" ]; + description = "\"allbran\" \"converge\""; + }; + cisco-int = { + ip-addresses = [ "10.73.77.10" ]; + description = "\"fruitloops\" \"aironet\""; + }; + cupid = { + ip-addresses = [ "208.38.36.100" ]; + }; + docker = { + ip-addresses = [ "208.81.3.126" ]; + }; + france = { + ip-addresses = [ "208.81.3.117" ]; + ssh-fingerprints = [ + "4 1 c95a198f504a589fc62893a95424b12f0b24732d" + "4 2 3e7dad879d6cab7f7fb6769e156d7988d0c01281618d03b793834eea2f09bc96" + "1 1 1b6d62dafae9ebc59169dfb4ef828582a5450d94" + "1 2 079e7a57873542541095bf3d2f97b7350bb457d027b423a6fb56f7f6aa84ac80" + ]; + }; + frankfurt = { + ip-addresses = [ "208.81.3.120" ]; + ipv6-addresses = [ "2605:e200:d200:1:5054:ff:fe8c:9738" ]; + ssh-fingerprints = [ + "2 1 4b9e4ed16a6b3fe6d41ed0f5cdeed853cc101e12" + "2 2 286ce32326874fe8aa15e3fd60b176b906ebd87306109f7c250d077db4ba85c5" + "3 1 3531dfd2f240ce0cd548b748462f78451df3f081" + "3 2 338809345ed38eb6808fd468067a74b2a8000fd8cc3bc016b9f977050bf1bba8" + "1 1 fb9ba707daa78243f8a8801f024fe790516b99a7" + "1 2 407f9692fedbd83449f0daf1cf795258b561a7e9c7e8072577cc84ffc0c84130" + ]; + }; + germany = { + ip-addresses = [ "208.81.3.116" ]; + ipv6-addresses = [ "2605:e200:d200:1:78d9:d8ff:fe0f:dd88" ]; + ssh-fingerprints = [ + "2 1 5609a728a91d7e52a6060ea7f3a7790005ba5e81" + "2 2 520a8eb3b9013837ac3ab4b28254f96b7718f9613e751a20dc488bf7d967b485" + "3 1 ee5b49888a36a34e7d4ee0d18626c82a16c2fcdf" + "3 2 d5e44cf2d85032638d49c030a9ccbff6638198c354efcb11bf173017d1257f49" + "1 1 9915d2515d7acdb38924d8829925113d5ce80b88" + "1 2 a7c866306e9661b8b568b2de282367c84065301d6228e58e57e6c4d3d33e3051" + ]; + }; + hanover = { + ip-addresses = [ "208.81.1.130" ]; + ipv6-addresses = [ "2605:e200:d100:1:5054:ff:fe61:ac8b" ]; + }; + localhost = { + ip-addresses = [ "127.0.0.1" ]; + }; + lsbb-gba = { + ip-addresses = [ "199.101.56.34" ]; + }; + lsbb-abg = { + ip-addresses = [ "199.101.56.38" ]; + }; + lsbb-hwd = { + ip-addresses = [ "199.101.56.106" ]; + }; + lsbb-hcl = { + ip-addresses = [ "199.101.56.110" ]; + }; + procul = { + ip-addresses = [ "172.86.179.18" ]; + }; + prunel = { + ip-addresses = [ "208.81.3.123" ]; + }; + mbix = { + ip-addresses = [ "208.81.7.146" ]; + }; + ns3-fudo = { + ip-addresses = [ "208.75.74.205" ]; + }; + ns3-dair = { + ip-addresses = [ "208.75.74.205" ]; + }; + ns4-fudo = { + ip-addresses = [ "208.75.75.157" ]; + }; + ns4-dair = { + ip-addresses = [ "208.75.75.157" ]; + }; + paris = { + ip-addresses = [ "208.81.3.125" ]; + ipv6-addresses = [ "2605:e200:d200:1:5054:ff:fe67:d0c1" ]; + ssh-fingerprints = [ + "2 1 9fe9e689a36316831ffafffc22c85913748670a6" + "2 2 f2ce57bf470c907604b79b6ef031c928a64a81031e78892fd475bbcf65ae728b" + "3 1 5c56e93a20868886ffe76e1fab012989ce8e995f" + "3 2 af4f383cb349fc3b2496a0bf0911da3a09f98a6d4d2a3c81bb0fb23a45bde950" + "4 1 71a1d35c32b4445b98ce339696f155e1d4c39bd5" + "4 2 a9e4810a24bd52082c9bb2b1019a9de7d7983246fecb454dd8d918ac5a11af81" + "1 1 18e8dd7cac48f1ac6103ec21c279e339d8690be1" + "1 2 72e4aa05c733441da57c464e6540486f5306b6768d784dd97c666e16629d77a0" + ]; + }; + probe = { + ip-addresses = [ "208.81.3.119" ]; + }; + tours = { + ip-addresses = [ "208.81.3.121" ]; + ipv6-addresses = [ "2605:e200:d200:1:5054:ff:fe95:34e5" ]; + ssh-fingerprints = [ + "2 2 41cddf1457880c7e86fa3838eabdbbe7cf803f98998ed406319ba3e43036964c" + "3 1 89b72a740ef6ef7ad9aaf5fe2178d356cdc7ee5b" + "3 2 c39346def56817aaf4c64db5667ccc6aeb400ff1166125fe630b63b5eab0ef29" + "4 1 049b1e6ef1d338d35e97baf312d8a371a266b7d1" + "4 2 1a889e43148ea1ded9f8bc60799ccf1bc32cb084946c8815abed6cc31f212594" + "1 1 bae37560759ec8dba35755473fbb346f9dc4e333" + "1 2 3d0d5efe2da329ea19b191f227c3aaad45271c651717ec3315cda131e992bbcf" + ]; + }; + }; + + default-host = "208.81.3.117"; + + srv-records = { + tcp = { + domain = [ + { + host = "ns1.fudo.org"; + port = 53; + } + { + host = "ns2.fudo.org"; + port = 53; + } + { + host = "ns3.fudo.org"; + port = 53; + } + { + host = "ns4.fudo.org"; + port = 53; + } + ]; + ssh = [{ + host = "france.fudo.org"; + port = 22; + }]; + smtp = [{ + host = "mail.fudo.org"; + port = 25; + }]; + submission = [{ + host = "mail.fudo.org"; + port = 587; + }]; + kerberos = [{ + host = "france.fudo.org"; + port = 88; + }]; + imaps = [{ + host = "mail.fudo.org"; + port = 993; + }]; + ldap = [{ + host = "france.fudo.org"; + port = 389; + }]; + ldaps = [{ + host = "france.fudo.org"; + port = 636; + }]; + pop3s = [{ + host = "mail.fudo.org"; + port = 995; + }]; + http = [{ + host = "wiki.fudo.org"; + port = 80; + }]; + https = [{ + host = "wiki.fudo.org"; + port = 80; + }]; + xmpp-server = [{ + host = "fudo.im"; + port = 5269; + }]; + xmpp-client = [{ + host = "fudo.im"; + port = 5222; + }]; + }; + udp = { + domain = [ + { + host = "ns1.fudo.org"; + port = 53; + } + { + host = "ns2.fudo.org"; + port = 53; + } + { + host = "ns3.fudo.org"; + port = 53; + } + { + host = "ns4.fudo.org"; + port = 53; + } + ]; + kerberos = [{ + host = "france.fudo.org"; + port = 88; + }]; + kerberos-master = [{ + host = "france.fudo.org"; + port = 88; + }]; + kpasswd = [{ + host = "france.fudo.org"; + port = 464; + }]; + xmpp-server = [{ + host = "fudo.im"; + port = 5269; + }]; + }; + }; + + aliases = { + pop = "mail.fudo.org."; + smtp = "mail.fudo.org."; + imap = "mail.fudo.org."; + webmail = "france.fudo.org."; + + archiva = "france.fudo.org."; + auth = "france.fudo.org."; + backplane = "france.fudo.org."; + chat = "france.fudo.org."; + de = "germany.fudo.org."; + fr = "france.fudo.org."; + git = "france.fudo.org."; + metrics = "france.fudo.org."; + minecraft = "france.fudo.org."; + monitor = "france.fudo.org."; + user = "paris.fudo.org."; + u = "user.fudo.org."; + w = "www.fudo.org."; + ww = "www.fudo.org."; + www = "hanover.fudo.org."; + wiki = "hanover.fudo.org."; + }; + + extra-dns-records = [ + ''_kerberos IN TXT "FUDO.ORG"'' + ''@ IN TXT "v=spf1 mx ip4:208.81.3.112/28 ip6:2605:e200:d200::1/48 -all"'' + ''@ IN SPF "v=spf1 mx ip4:208.81.3.112/28 ip6:2605:e200:d200::1/48 -all"'' + ]; + + dmarc-report-address = "dmarc-report@fudo.org"; +} diff --git a/fudo/users.nix b/fudo/users.nix index b7c943e..3f53c62 100644 --- a/fudo/users.nix +++ b/fudo/users.nix @@ -75,7 +75,8 @@ uid = 10035; group = "selby"; common-name = "Ken Selby"; - hashed-password = "{SSHA}X8DxUcwH2Fzel5UKbGVNhC5B2vg0Prsc"; + hashed-password = "{SSHA}wUGV/9dr8inz/HyqSF/OWKxy0DCy5AI3"; + # hashed-password = "{SSHA}X8DxUcwH2Fzel5UKbGVNhC5B2vg0Prsc"; }; reaper = { @@ -390,7 +391,8 @@ uid = 10108; group = "selby"; common-name = "Lauren Hotel"; - hashed-password = "{SSHA}DKnhrycmXSu4HKWFPeBXA9xvZ0ytgXIpZA10tg=="; + hashed-password = "{SSHA}1q/MC5LKROlIT1nDrKrMvcFAXFtcQXIR"; + # hashed-password = "{SSHA}DKnhrycmXSu4HKWFPeBXA9xvZ0ytgXIpZA10tg=="; }; # Used to send alerts from grafana diff --git a/hosts/france.nix b/hosts/france.nix index 73c6e46..596e2cc 100644 --- a/hosts/france.nix +++ b/hosts/france.nix @@ -7,7 +7,7 @@ let mail-hostname = "mail.${domain}"; host_ipv4 = "208.81.3.117"; # Use a special IP for git.fudo.org, since it needs to be SSH-able - git_ipv4 = "208.81.3.126"; + link_ipv4 = "208.81.3.126"; all-hostnames = []; acme-private-key = hostname: "/var/lib/acme/${hostname}/key.pem"; @@ -40,6 +40,7 @@ in { lxd multipath-tools nix-prefetch-docker + powerdns tshark ]; @@ -185,22 +186,44 @@ in { }; }; - # fudo.dns = { - # enable = true; + fudo.dns = { + enable = true; - # dns-hosts = { - # "ns1.fudo.org" = host_ipv4; - # "ns2.fudo.org" = ""; - # }; + identity = "france.fudo.org"; - # listen-ips = [host_ipv4]; + nameservers = { + ns1 = { + ip-addresses = [ "208.81.3.117" ]; + ipv6-addresses = [ "2605:e200:d200:1:5054:ff:fe8c:9738" ]; + description = "Nameserver 1, france, in Winnipeg, MB, CA"; + rp = "reaper reaper.rp"; + }; + ns2 = { + ip-addresses = [ "209.117.102.102" ]; + ipv6-addresses = [ "2001:470:1f16:40::2" ]; + description = "Nameserver 2, musashi, in Winnipeg, MB, CA"; + rp = "reaper reaper.rp"; + }; + ns3 = { + ip-addresses = [ "104.131.53.95" ]; + ipv6-addresses = [ "2604:a880:800:10::8:7001" ]; + description = "Nameserver 3, ns2.henchmman21.net, in New York City, NY, US"; + rp = "reaper reaper.rp"; + }; + ns4 = { + ip-addresses = [ "204.42.254.5" ]; + ipv6-addresses = [ "2001:418:3f4::5" ]; + description = "Nameserver 4, puck.nether.net, in Chicago, IL, US"; + rp = "reaper reaper.rp"; + }; + }; - # domains = { - # "selby.ca" = import ../fudo.org/selby.ca.nix { - # inherit host_ipv4 config; - # }; - # }; - # }; + listen-ips = [host_ipv4]; + + domains = { + "fudo.org" = import ../fudo/fudo.org.nix { inherit config; }; + }; + }; # Not all users need access to france; don't allow LDAP-user access. fudo.authentication.enable = false; @@ -259,6 +282,15 @@ in { # TODO: not used yet fudo.acme.hostnames = all-hostnames; + fudo.client.dns = { + enable = true; + ipv4 = true; + ipv6 = true; + user = "fudo-client"; + external-interface = "extif0"; + password-file = "/srv/client/secure/client.passwd"; + }; + fudo.mail-server = import ../fudo/email.nix { inherit config; } // { enableContainer = true; debug = true; @@ -392,7 +424,7 @@ in { repository-dir = /srv/git/repo; state-dir = /srv/git/state; ssh = { - listen-ip = git_ipv4; + listen-ip = link_ipv4; listen-port = 2222; }; }; @@ -438,7 +470,7 @@ in { macAddress = "02:6d:e2:e1:ad:ca"; ipv4.addresses = [ { - address = git_ipv4; + address = link_ipv4; prefixLength = 28; } ]; diff --git a/hosts/france/backplane.nix b/hosts/france/backplane.nix index 8ce0ca8..0d98b60 100644 --- a/hosts/france/backplane.nix +++ b/hosts/france/backplane.nix @@ -58,8 +58,12 @@ in { databases = { backplane_dns = { access = "CONNECT"; + # entity-access = { + # "ALL TABLES IN SCHEMA public" = "SELECT"; + # }; entity-access = { - "ALL TABLES IN SCHEMA public" = "SELECT"; + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE"; + "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; }; }; }; @@ -70,7 +74,7 @@ in { backplane_dns = { access = "CONNECT"; entity-access = { - "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE"; + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE"; "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; }; }; @@ -87,8 +91,8 @@ in { backplane.dns = { enable = true; - port = 353; - listen-addresses = [ "208.81.3.117" ]; + listen-v4-addresses = [ "208.81.3.126" ]; + listen-v6-addresses = [ "[2605:e200:d200:1:6d:e2ff:fee1:adca]" ]; required-services = [ "fudo-passwords.target" ]; user = "backplane-dns"; group = "backplane-dns"; diff --git a/hosts/france/jabber.nix b/hosts/france/jabber.nix index df4c16b..b900bad 100644 --- a/hosts/france/jabber.nix +++ b/hosts/france/jabber.nix @@ -4,6 +4,9 @@ with lib; let backplane-auth = "/etc/nixos/static/backplane-auth.scm"; + host-passwd-file = "/srv/jabber/secret/hosts-passwd.scm"; + service-passwd-file = "/srv/jabber/secret/services-passwd.scm"; + cert-basedir = "/var/lib/ejabberd/certs"; target-certs = ["key" "cert" "chain" "fullchain"]; @@ -50,30 +53,67 @@ in { security.acme.certs."fudo.im".email = "admin@fudo.org"; security.acme.certs."backplane.fudo.org".email = "admin@fudo.org"; - systemd.services = { - ejabberd-generate-certs = { - enable = true; - description = "Generate required SSL certs for ejabberd."; - wantedBy = [ "ejabberd.service" ]; - after = [ - "acme-backplane.fudo.org.service" - "acme-fudo.im.service" - ]; + systemd = { + services = { + ejabberd-generate-certs = { + enable = true; + description = "Generate required SSL certs for ejabberd."; + wantedBy = [ "ejabberd.service" ]; + after = [ + "acme-backplane.fudo.org.service" + "acme-fudo.im.service" + ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${move-server-certs ["fudo.im" "backplane.fudo.org"]}"; + RemainAfterExit = true; + ExecStop = remove-server-certs; + StandardOutput = "journal"; + }; + }; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${move-server-certs ["fudo.im" "backplane.fudo.org"]}"; - RemainAfterExit = true; - ExecStop = remove-server-certs; - StandardOutput = "journal"; + ejabberd = { + requires = [ "ejabberd-generate-certs.service" ]; + environment = { + FUDO_HOST_PASSWD_FILE = host-passwd-file; + FUDO_SERVICE_PASSWD_FILE = service-passwd-file; + }; + }; + + ejabberd-hostfile-watcher = { + description = "Watch the ejabberd host file and restart if changes occur."; + serviceConfig.Type = "oneshot"; + after = [ "ejabberd.service" ]; + script = '' + SYSCTL=${pkgs.systemd}/bin/systemctl + if $SYSCTL is-active --quiet ejabberd.service; then + echo "restarting ejabberd.service because hostfile has changed." + $SYSCTL restart ejabberd.service + fi + ''; + }; + + ejabberd-servicefile-watcher = { + description = "Watch the ejabberd service file and restart if changes occur."; + serviceConfig.Type = "oneshot"; + after = [ "ejabberd.service" ]; + script = '' + SYSCTL=${pkgs.systemd}/bin/systemctl + if $SYSCTL is-active --quiet ejabberd.service; then + echo "restarting ejabberd.service because servicefile has changed." + $SYSCTL restart ejabberd.service + fi + ''; }; }; - ejabberd = { - requires = [ "ejabberd-generate-certs.service" ]; - environment = { - FUDO_HOST_PASSWD_FILE = "/srv/jabber/secret/hosts-passwd.scm"; - FUDO_SERVICE_PASSWD_FILE = "/srv/jabber/secret/services-passwd.scm"; + paths = { + ejabberd-hostfile-watcher = { + pathConfig.PathChanged = host-passwd-file; + }; + + ejabberd-servicefile-watcher = { + pathConfig.PathChanged = service-passwd-file; }; }; }; diff --git a/hosts/procul.nix b/hosts/procul.nix index ead5d09..5272d2a 100644 --- a/hosts/procul.nix +++ b/hosts/procul.nix @@ -31,10 +31,6 @@ in { ../informis/users.nix ]; - environment.systemPackages = with pkgs; [ - multipath-tools - ]; - networking = { hostName = hostname; @@ -110,6 +106,15 @@ in { ]; }; + client.dns = { + enable = true; + ipv4 = true; + ipv6 = true; + user = "fudo-client"; + external-interface = "extif0"; + password-file = "/srv/client/secure/client.passwd"; + }; + # Not all users need access to procul; don't allow LDAP-user access. authentication.enable = false; @@ -214,15 +219,23 @@ in { users = { gituser = { - password = fileContents "/srv/git/secure/db.passwd"; + password-file = "/srv/git/secure/db.passwd"; databases = { - git = "ALL PRIVILEGES"; + git = { + access = "CONNECT"; + entity-access = { + "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE"; + "ALL SEQUENCES IN SCHEMA public" = "SELECT, UPDATE"; + }; + }; }; }; }; databases = { - git = ["niten"]; + git = { + users = ["niten"]; + }; }; }; @@ -273,22 +286,15 @@ in { }; fudo.vpn = { - enable = true; - ips = "10.100.0.0/16"; + # fer some fuckin reason this sets the default gw to the vpn interface + enable = false; + network = "10.100.0.0/16"; + server-ip = host_ipv4; private-key-file = "/srv/wireguard/secure/secret.key"; peers = { - peter = { - allowed-ips = [ "10.100.1.0/24" ]; - public-key = "d1NfRFWRkcKq2gxvqfMy7Oe+JFYf5DjomnsTyisvgB4="; - }; - ken = { - allowed-ips = [ "10.100.2.0/24" ]; - public-key = "y294rTCK0iSRhA6EIOErPzEuqzJMuYAG4XbHasySMVU="; - }; - helen = { - allowed-ips = [ "10.100.3.0/24" ]; - public-key = "7Hdko6RibhIYdoPLWXGwmElY5vKvZ+rURmqFTDUfC2w="; - }; + peter = "/srv/wireguard/clients/peter.key"; + ken = "/srv/wireguard/clients/ken.key"; + helen = "/srv/wireguard/clients/helen.key"; }; }; diff --git a/lib/buildLisp.nix b/lib/buildLisp.nix new file mode 100644 index 0000000..51411b9 --- /dev/null +++ b/lib/buildLisp.nix @@ -0,0 +1,259 @@ +# buildLisp provides Nix functions to build Common Lisp packages, +# targeting SBCL. +# +# buildLisp is designed to enforce conventions and do away with the +# free-for-all of existing Lisp build systems. + +{ pkgs ? import {}, ... }: + +let + inherit (builtins) map elemAt match filter; + inherit (pkgs) lib runCommandNoCC makeWrapper writeText writeShellScriptBin sbcl; + + # + # Internal helper definitions + # + + # 'genLoadLisp' generates Lisp code that instructs SBCL to load all + # the provided Lisp libraries. + genLoadLisp = deps: lib.concatStringsSep "\n" + (map (lib: "(load \"${lib}/${lib.lispName}.fasl\")") (allDeps deps)); + + # 'genCompileLisp' generates a Lisp file that instructs SBCL to + # compile the provided list of Lisp source files to $out. + genCompileLisp = srcs: deps: writeText "compile.lisp" '' + ;; This file compiles the specified sources into the Nix build + ;; directory, creating one FASL file for each source. + (require 'sb-posix) + + ${genLoadLisp deps} + + (defun nix-compile-lisp (file srcfile) + (let ((outfile (make-pathname :type "fasl" + :directory (or (sb-posix:getenv "NIX_BUILD_TOP") + (error "not running in a Nix build")) + :name (substitute #\- #\/ srcfile)))) + (multiple-value-bind (_outfile _warnings-p failure-p) + (compile-file srcfile :output-file outfile) + (if failure-p (sb-posix:exit 1) + (progn + ;; For the case of multiple files belonging to the same + ;; library being compiled, load them in order: + (load outfile) + + ;; Write them to the FASL list in the same order: + (format file "cat ~a~%" (namestring outfile))))))) + + (let ((*compile-verbose* t) + ;; FASL files are compiled into the working directory of the + ;; build and *then* moved to the correct out location. + (pwd (sb-posix:getcwd))) + + (with-open-file (file "cat_fasls" + :direction :output + :if-does-not-exist :create) + + ;; These forms were inserted by the Nix build: + ${ + lib.concatStringsSep "\n" (map (src: "(nix-compile-lisp file \"${src}\")") srcs) + } + )) + ''; + + # 'genTestLisp' generates a Lisp file that loads all sources and deps and + # executes expression + genTestLisp = name: srcs: deps: expression: writeText "${name}.lisp" '' + ;; Dependencies + ${genLoadLisp deps} + + ;; Sources + ${lib.concatStringsSep "\n" (map (src: "(load \"${src}\")") srcs)} + + ;; Test expression + (unless ${expression} + (exit :code 1)) + ''; + + # 'dependsOn' determines whether Lisp library 'b' depends on 'a'. + dependsOn = a: b: builtins.elem a b.lispDeps; + + # 'allDeps' flattens the list of dependencies (and their + # dependencies) into one ordered list of unique deps. + allDeps = deps: (lib.toposort dependsOn (lib.unique ( + lib.flatten (deps ++ (map (d: d.lispDeps) deps)) + ))).result; + + # 'allNative' extracts all native dependencies of a dependency list + # to ensure that library load paths are set correctly during all + # compilations and program assembly. + allNative = native: deps: lib.unique ( + lib.flatten (native ++ (map (d: d.lispNativeDeps) deps)) + ); + + # 'genDumpLisp' generates a Lisp file that instructs SBCL to dump + # the currently loaded image as an executable to $out/bin/$name. + # + # TODO(tazjin): Compression is currently unsupported because the + # SBCL in nixpkgs is, by default, not compiled with zlib support. + genDumpLisp = name: main: deps: writeText "dump.lisp" '' + (require 'sb-posix) + + ${genLoadLisp deps} + + (let* ((bindir (concatenate 'string (sb-posix:getenv "out") "/bin")) + (outpath (make-pathname :name "${name}" + :directory bindir))) + (save-lisp-and-die outpath + :executable t + :toplevel (function ${main}) + :purify t)) + ;; + ''; + + # Add an `overrideLisp` attribute to a function result that works + # similar to `overrideAttrs`, but is used specifically for the + # arguments passed to Lisp builders. + makeOverridable = f: orig: (f orig) // { + overrideLisp = new: makeOverridable f (orig // (new orig)); + }; + + # 'testSuite' builds a Common Lisp test suite that loads all of srcs and deps, + # and then executes expression to check its result + testSuite = { name, expression, srcs, deps ? [], native ? [] }: + let + lispNativeDeps = allNative native deps; + lispDeps = allDeps deps; + in runCommandNoCC name { + LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; + LANG = "C.UTF-8"; + } '' + echo "Running test suite ${name}" + + ${sbcl}/bin/sbcl --script ${genTestLisp name srcs deps expression} \ + | tee $out + + echo "Test suite ${name} succeeded" + ''; + + # + # Public API functions + # + + # 'library' builds a list of Common Lisp files into a single FASL + # which can then be loaded into SBCL. + library = + { name + , srcs + , deps ? [] + , native ? [] + , tests ? null + }: + let + lispNativeDeps = (allNative native deps); + lispDeps = allDeps deps; + testDrv = if ! isNull tests + then testSuite { + name = tests.name or "${name}-test"; + srcs = srcs ++ (tests.srcs or []); + deps = deps ++ (tests.deps or []); + expression = tests.expression; + } + else null; + in lib.fix (self: runCommandNoCC "${name}-cllib" { + LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; + LANG = "C.UTF-8"; + } '' + ${if ! isNull testDrv + then "echo 'Test ${testDrv} succeeded'" + else "echo 'No tests run'"} + ${sbcl}/bin/sbcl --script ${genCompileLisp srcs lispDeps} + + echo "Compilation finished, assembling FASL files" + + # FASL files can be combined by simply concatenating them + # together, but it needs to be in the compilation order. + mkdir $out + + chmod +x cat_fasls + ./cat_fasls > $out/${name}.fasl + '' // { + inherit lispNativeDeps lispDeps; + lispName = name; + lispBinary = false; + tests = testDrv; + sbcl = sbclWith [ self ]; + }); + + # 'program' creates an executable containing a dumped image of the + # specified sources and dependencies. + program = + { name + , main ? "${name}:main" + , srcs + , deps ? [] + , native ? [] + , tests ? null + }: + let + lispDeps = allDeps deps; + libPath = lib.makeLibraryPath (allNative native lispDeps); + selfLib = library { + inherit name srcs native; + deps = lispDeps; + }; + testDrv = if ! isNull tests + then testSuite { + name = tests.name or "${name}-test"; + srcs = + ( + srcs ++ (tests.srcs or [])); + deps = deps ++ (tests.deps or []); + expression = tests.expression; + } + else null; + in lib.fix (self: runCommandNoCC "${name}" { + nativeBuildInputs = [ makeWrapper ]; + LD_LIBRARY_PATH = libPath; + LANG = "C.UTF-8"; + } '' + ${if ! isNull testDrv + then "echo 'Test ${testDrv} succeeded'" + else ""} + mkdir -p $out/bin + + ${sbcl}/bin/sbcl --script ${ + genDumpLisp name main ([ selfLib ] ++ lispDeps) + } + + wrapProgram $out/bin/${name} --prefix LD_LIBRARY_PATH : "${libPath}" + '' // { + lispName = name; + lispDeps = [ selfLib ] ++ (tests.deps or []); + lispNativeDeps = native; + lispBinary = true; + tests = testDrv; + sbcl = sbclWith [ self ]; + }); + + # 'bundled' creates a "library" that calls 'require' on a built-in + # package, such as any of SBCL's sb-* packages. + bundled = name: (makeOverridable library) { + inherit name; + srcs = lib.singleton (builtins.toFile "${name}.lisp" "(require '${name})"); + }; + + # 'sbclWith' creates an image with the specified libraries / + # programs loaded. + sbclWith = deps: + let lispDeps = filter (d: !d.lispBinary) (allDeps deps); + in writeShellScriptBin "sbcl" '' + export LD_LIBRARY_PATH="${lib.makeLibraryPath (allNative [] lispDeps)}" + export LANG="C.UTF-8" + exec ${sbcl}/bin/sbcl ${lib.optionalString (deps != []) "--load ${writeText "load.lisp" (genLoadLisp lispDeps)}"} $@ + ''; +in { + library = makeOverridable library; + program = makeOverridable program; + sbclWith = makeOverridable sbclWith; + bundled = makeOverridable bundled; +} diff --git a/lib/dns.nix b/lib/dns.nix index 7efaf8c..876b2a5 100644 --- a/lib/dns.nix +++ b/lib/dns.nix @@ -1,6 +1,6 @@ -{ lib }: +{ pkgs ? import {}, ... }: -with lib; +with pkgs.lib; let join-lines = concatStringsSep "\n"; diff --git a/lib/ip.nix b/lib/ip.nix index 7f55bf0..d38997b 100644 --- a/lib/ip.nix +++ b/lib/ip.nix @@ -1,45 +1,32 @@ -{ lib }: +{ pkgs ? import {}, ... }: -with lib; +with pkgs.lib; let - joinString = lib.concatStringsSep; + joinString = concatStringsSep; pow = x: e: if (e == 0) then 1 else x * (pow x (e - 1)); -in rec { - generateNBits = n: let helper = n: c: if (c == n) then pow 2 c else (pow 2 c) + (helper n (c + 1)); in if (n <= 0) then throw "Can't generate 0 or fewer bits" else helper (n - 1) 0; + rightPadBits = int: bits: bitOr int (generateNBits bits); + reverseIpv4 = ip: joinString "." (reverseList (splitString "." ip)); intToBinaryList = int: let helper = int: cur: let curExp = pow 2 cur; in if (curExp > int) then - [] - else - [(if ((bitAnd curExp int) > 0) then 1 else 0)] ++ (helper int (cur + 1)); + [] + else + [(if ((bitAnd curExp int) > 0) then 1 else 0)] ++ (helper int (cur + 1)); in reverseList (helper int 0); leftShift = int: n: int * (pow 2 n); rightShift = int: n: int / (pow 2 n); - ipv4ToInt = ip: let - els = map toInt (reverseList (splitString "." ip)); - in foldr (a: b: a + b) 0 (imap0 (i: el: (leftShift el (i * 8))) els); - - intToIpv4 = int: joinString "." (map (i: toString (bitAnd (rightShift int (i * 8)) 255)) [ 3 2 1 0 ]); - - rightPadBits = int: bits: bitOr int (generateNBits bits); - - maskFromV32Network = network: let - fullMask = ipv4ToInt "255.255.255.255"; - insignificantBits = 32 - (getNetworkMask network); - in intToIpv4 (leftShift (rightShift fullMask insignificantBits) insignificantBits); - getNetworkMask = network: toInt (elemAt (splitString "/" network) 1); getNetworkBase = network: let @@ -47,10 +34,30 @@ in rec { insignificantBits = 32 - (getNetworkMask network); in intToIpv4 (leftShift (rightShift (ipv4ToInt ip) insignificantBits) insignificantBits); +in rec { + + ipv4ToInt = ip: let + els = map toInt (reverseList (splitString "." ip)); + in foldr (a: b: a + b) 0 (imap0 (i: el: (leftShift el (i * 8))) els); + + intToIpv4 = int: joinString "." (map (i: toString (bitAnd (rightShift int (i * 8)) 255)) [ 3 2 1 0 ]); + + maskFromV32Network = network: let + fullMask = ipv4ToInt "255.255.255.255"; + insignificantBits = 32 - (getNetworkMask network); + in intToIpv4 (leftShift (rightShift fullMask insignificantBits) insignificantBits); + networkMinIp = network: intToIpv4 (1 + (ipv4ToInt (getNetworkBase network))); networkMaxIp = network: intToIpv4 (rightPadBits (ipv4ToInt (getNetworkBase network)) (32 - (getNetworkMask network))); # To avoid broadcast IP... networkMaxButOneIp = network: intToIpv4 ((rightPadBits (ipv4ToInt (getNetworkBase network)) (32 - (getNetworkMask network))) - 1); + + ipv4OnNetwork = ip: network: let + ip-int = ipv4ToInt ip; + net-min = networkMinIp network; + net-max = networkMaxIp network; + in + (ip-int >= networkMinIp) && (ip-int <= networkMaxIp); } diff --git a/packages/backplane-dns.nix b/packages/backplane-dns.nix index 85287bb..10d08c2 100644 --- a/packages/backplane-dns.nix +++ b/packages/backplane-dns.nix @@ -9,8 +9,8 @@ in stdenv.mkDerivation { src = fetchgit { url = url; - rev = "543df72f3962cf91b0e0508d15cdc083a3cd7ed4"; - sha256 = "0hda1wjf9wd4rvxchdlxw0af3i2cvl5plg37ric3ckma6gfzkmm0"; + rev = "c552394e55816541a9426974c5f8e6f1f83bf195"; + sha256 = "0r61bwj5a2dvzl41cwdf2pdnhdsmp3kzfyxa5x5hsg67al6s7vi8"; fetchSubmodules = false; }; diff --git a/static/backplane-auth.scm b/static/backplane-auth.scm index e017da5..f23ee80 100644 --- a/static/backplane-auth.scm +++ b/static/backplane-auth.scm @@ -16,8 +16,8 @@ (format (current-error-port "FUDO_SERVICE_PASSWD_FILE not set~%")) (exit 1)) -(define host-regex "^host-([a-zA-Z][a-zA-Z0-9_-]+)") -(define service-regex "^service-([a-zA-Z][a-zA-Z0-9_-]+)") +(define host-regex "^host-([a-zA-Z][a-zA-Z0-9_-]+)$") +(define service-regex "^service-([a-zA-Z][a-zA-Z0-9_-]+)$") (define (make-verifier passwd-file) (let ((passwds (load passwd-file)))