239 lines
7.1 KiB
Nix
239 lines
7.1 KiB
Nix
{ 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;
|
|
};
|
|
};
|
|
}
|