Go big or go home
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
{ lib, config, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.local-network;
|
||||
|
||||
join-lines = concatStringsSep "\n";
|
||||
|
||||
traceout = out: builtins.trace out out;
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.local-network = with types; {
|
||||
|
||||
enable = mkEnableOption "Enable local network configuration (DHCP & DNS).";
|
||||
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = "The domain to use for the local network.";
|
||||
};
|
||||
|
||||
dns-servers = mkOption {
|
||||
type = listOf str;
|
||||
description = "A list of domain name servers to pass to local clients.";
|
||||
};
|
||||
|
||||
dhcp-interfaces = mkOption {
|
||||
type = listOf str;
|
||||
description = "A list of interfaces on which to serve DHCP.";
|
||||
};
|
||||
|
||||
dns-listen-ips = mkOption {
|
||||
type = listOf str;
|
||||
description = "A list of IPs on which to server DNS queries.";
|
||||
};
|
||||
|
||||
gateway = mkOption {
|
||||
type = str;
|
||||
description = "The gateway to use for the local network.";
|
||||
};
|
||||
|
||||
network = mkOption {
|
||||
type = str;
|
||||
description = "Network to treat as local.";
|
||||
example = "10.0.0.0/16";
|
||||
};
|
||||
|
||||
dhcp-dynamic-network = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
The network from which to dynamically allocate IPs via DHCP.
|
||||
|
||||
Must be a subnet of <network>.
|
||||
'';
|
||||
example = "10.0.1.0/24";
|
||||
};
|
||||
|
||||
enable-reverse-mappings = mkOption {
|
||||
type = bool;
|
||||
description = "Genereate PTR reverse lookup records.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
recursive-resolver = mkOption {
|
||||
type = str;
|
||||
description = "DNS nameserver to use for recursive resolution.";
|
||||
default = "1.1.1.1 port 53";
|
||||
};
|
||||
|
||||
search-domains = mkOption {
|
||||
type = listOf str;
|
||||
description = "A list of domains which clients should consider local.";
|
||||
example = [ "my-domain.com" "other-domain.com" ];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
network-definition = let
|
||||
networkOpts = import ../types/network-definition.nix { inherit lib; };
|
||||
in mkOption {
|
||||
type = submodule networkOpts;
|
||||
description = "Definition of network to be served by local server.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
extra-records = mkOption {
|
||||
type = listOf str;
|
||||
description = "Extra records to add to the local zone.";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
fudo.system.hostfile-entries = let
|
||||
other-hosts = filterAttrs
|
||||
(hostname: hostOpts: hostname != config.instance.hostname)
|
||||
cfg.network-definition.hosts;
|
||||
in mapAttrs' (hostname: hostOpts:
|
||||
nameValuePair hostOpts.ipv4-address ["${hostname}.${cfg.domain}" hostname])
|
||||
other-hosts;
|
||||
|
||||
services.dhcpd4 = let network = cfg.network-definition;
|
||||
in {
|
||||
enable = true;
|
||||
|
||||
machines = mapAttrsToList (hostname: hostOpts: {
|
||||
ethernetAddress = hostOpts.mac-address;
|
||||
hostName = hostname;
|
||||
ipAddress = hostOpts.ipv4-address;
|
||||
}) (filterAttrs (host: hostOpts:
|
||||
hostOpts.mac-address != null && hostOpts.ipv4-address != null)
|
||||
network.hosts);
|
||||
|
||||
interfaces = cfg.dhcp-interfaces;
|
||||
|
||||
extraConfig = ''
|
||||
subnet ${pkgs.lib.fudo.ip.getNetworkBase cfg.network} netmask ${
|
||||
pkgs.lib.fudo.ip.maskFromV32Network cfg.network
|
||||
} {
|
||||
authoritative;
|
||||
option subnet-mask ${pkgs.lib.fudo.ip.maskFromV32Network cfg.network};
|
||||
option broadcast-address ${pkgs.lib.fudo.ip.networkMaxIp cfg.network};
|
||||
option routers ${cfg.gateway};
|
||||
option domain-name-servers ${concatStringsSep " " cfg.dns-servers};
|
||||
option domain-name "${cfg.domain}";
|
||||
option domain-search "${
|
||||
concatStringsSep " " ([ cfg.domain ] ++ cfg.search-domains)
|
||||
}";
|
||||
range ${pkgs.lib.fudo.ip.networkMinIp cfg.dhcp-dynamic-network} ${
|
||||
pkgs.lib.fudo.ip.networkMaxButOneIp cfg.dhcp-dynamic-network
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
services.bind = let
|
||||
blockHostsToZone = block: hosts-data: {
|
||||
master = true;
|
||||
name = "${block}.in-addr.arpa";
|
||||
file = let
|
||||
# We should add these...but need a domain to assign them to.
|
||||
# ip-last-el = ip: toInt (last (splitString "." ip));
|
||||
# used-els = map (host-data: ip-last-el host-data.ipv4-address) hosts-data;
|
||||
# unused-els = subtractLists used-els (map toString (range 1 255));
|
||||
|
||||
in pkgs.writeText "db.${block}-zone" ''
|
||||
$ORIGIN ${block}.in-addr.arpa.
|
||||
$TTL 1h
|
||||
|
||||
@ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. (
|
||||
${toString config.instance.build-timestamp}
|
||||
1800
|
||||
900
|
||||
604800
|
||||
1800)
|
||||
|
||||
@ IN NS ns1.${cfg.domain}.
|
||||
|
||||
${join-lines (map hostPtrRecord hosts-data)}
|
||||
'';
|
||||
};
|
||||
|
||||
ipToBlock = ip:
|
||||
concatStringsSep "." (reverseList (take 3 (splitString "." ip)));
|
||||
compactHosts =
|
||||
mapAttrsToList (host: data: data // { host = host; }) network.hosts;
|
||||
hostsByBlock =
|
||||
groupBy (host-data: ipToBlock host-data.ipv4-address) compactHosts;
|
||||
hostPtrRecord = host-data:
|
||||
"${
|
||||
last (splitString "." host-data.ipv4-address)
|
||||
} IN PTR ${host-data.host}.${cfg.domain}.";
|
||||
|
||||
blockZones = mapAttrsToList blockHostsToZone hostsByBlock;
|
||||
|
||||
hostARecord = host: data: "${host} IN A ${data.ipv4-address}";
|
||||
hostSshFpRecords = host: data:
|
||||
let
|
||||
ssh-fingerprints = if (hasAttr host known-hosts) then
|
||||
known-hosts.${host}.ssh-fingerprints
|
||||
else
|
||||
[ ];
|
||||
in join-lines
|
||||
(map (sshfp: "${host} IN SSHFP ${sshfp}") ssh-fingerprints);
|
||||
cnameRecord = alias: host: "${alias} IN CNAME ${host}";
|
||||
|
||||
network = cfg.network-definition;
|
||||
|
||||
known-hosts = config.fudo.hosts;
|
||||
|
||||
in {
|
||||
enable = true;
|
||||
cacheNetworks = [ cfg.network "localhost" "localnets" ];
|
||||
forwarders = [ cfg.recursive-resolver ];
|
||||
listenOn = cfg.dns-listen-ips;
|
||||
extraOptions = concatStringsSep "\n" [
|
||||
"dnssec-enable yes;"
|
||||
"dnssec-validation yes;"
|
||||
"auth-nxdomain no;"
|
||||
"recursion yes;"
|
||||
"allow-recursion { any; };"
|
||||
];
|
||||
zones = [{
|
||||
master = true;
|
||||
name = cfg.domain;
|
||||
file = pkgs.writeText "${cfg.domain}-zone" ''
|
||||
@ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. (
|
||||
${toString config.instance.build-timestamp}
|
||||
5m
|
||||
2m
|
||||
6w
|
||||
5m)
|
||||
|
||||
$TTL 1h
|
||||
|
||||
@ IN NS ns1.${cfg.domain}.
|
||||
|
||||
$ORIGIN ${cfg.domain}.
|
||||
|
||||
$TTL 30m
|
||||
|
||||
${optionalString (network.gssapi-realm != null)
|
||||
''_kerberos IN TXT "${network.gssapi-realm}"''}
|
||||
|
||||
${join-lines
|
||||
(imap1 (i: server-ip: "ns${toString i} IN A ${server-ip}")
|
||||
cfg.dns-servers)}
|
||||
${join-lines (mapAttrsToList hostARecord network.hosts)}
|
||||
${join-lines (mapAttrsToList hostSshFpRecords network.hosts)}
|
||||
${join-lines (mapAttrsToList cnameRecord network.aliases)}
|
||||
${join-lines network.verbatim-dns-records}
|
||||
${pkgs.lib.fudo.dns.srvRecordsToBindZone network.srv-records}
|
||||
${join-lines cfg.extra-records}
|
||||
'';
|
||||
}] ++ blockZones;
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user