nixos-config/hosts/plover/modules/services/bind.nix

229 lines
5.8 KiB
Nix

{ config, lib, pkgs, ... }:
let
inherit (config.networking) domain fqdn;
inherit (import ../hardware/networks.nix) privateIPv6Prefix interfaces clientNetworks serverNetworks secondaryNameServers wireguardPeers;
secondaryNameserverDomains = lib.attrNames secondaryNameServers;
secondaryNameServersIPv4 = lib.foldl'
(total: addresses: total ++ addresses.IPv4)
[ ]
(lib.attrValues secondaryNameServers);
secondaryNameServersIPv6 = lib.foldl'
(total: addresses: total ++ addresses.IPv6)
[ ]
(lib.attrValues secondaryNameServers);
secondaryNameServersIPs = secondaryNameServersIPv4 ++ secondaryNameServersIPv6;
domainZone = pkgs.substituteAll {
src = ../../config/dns/${domain}.zone;
ploverWANIPv4 = interfaces.wan.IPv4.address;
ploverWANIPv6 = interfaces.wan.IPv6.address;
};
fqdnZone = pkgs.substituteAll {
src = ../../config/dns/${fqdn}.zone;
ploverLANIPv4 = interfaces.lan.IPv4.address;
ploverLANIPv6 = interfaces.lan.IPv6.address;
};
zonesDir = "/etc/bind/zones";
zoneFile = domain: "${zonesDir}/${domain}.zone";
localhostIP = [
"127.0.0.1"
"::1"
];
allowedLANIPs = [
# Loopback address
"127.0.0.0/8"
# Private uses
"10.48.0.0/12"
"172.27.0.0/16" # The private subnet for our internal network.
"172.28.0.0/16" # The Wireguard subnet.
];
allowedLANIPv6s = [
"::1" # Loopback
"${privateIPv6Prefix}::/48" # Private uses
];
internalsACL = clientNetworks ++ serverNetworks;
in
{
sops.secrets =
let
getKey = key: {
inherit key;
sopsFile = ../../secrets/secrets.yaml;
};
getSecrets = secrets:
lib.mapAttrs'
(secret: config:
lib.nameValuePair
"plover/${secret}"
((getKey secret) // config))
secrets;
dnsFileAttribute = {
owner = config.users.users.named.name;
group = config.users.users.named.group;
mode = "0400";
};
in
getSecrets {
"dns/${domain}/mailbox-security-key" = dnsFileAttribute;
"dns/${domain}/mailbox-security-key-record" = dnsFileAttribute;
"dns/${domain}/rfc2136-key" = dnsFileAttribute // {
reloadUnits = [ "bind.service" ];
};
};
# Install the utilities.
environment.systemPackages = [ config.services.bind.package ];
services.bind = {
enable = true;
forward = "first";
cacheNetworks = [
"127.0.0.1"
"::1"
];
listenOn = [
"127.0.0.1"
interfaces.lan.IPv4.address
interfaces.wan.IPv4.address
];
listenOnIpv6 = [
"::1"
interfaces.lan.IPv6.address
interfaces.wan.IPv6.address
];
extraConfig = ''
include "${config.sops.secrets."plover/dns/${domain}/rfc2136-key".path}";
acl trusted { ${lib.concatStringsSep "; " internalsACL}; localhost; };
view internal {
match-clients { trusted; };
allow-query { any; };
allow-recursion { any; };
forwarders { 127.0.0.53 port 53; };
zone "${fqdn}" {
type primary;
file "${zoneFile fqdn}";
};
zone "${domain}" {
type primary;
file "${zoneFile domain}";
allow-transfer { ${lib.concatStringsSep "; " secondaryNameServersIPs}; };
update-policy {
grant rfc2136key.${domain}. zonesub TXT;
};
};
};
view external {
match-clients { any; };
forwarders { };
empty-zones-enable yes;
allow-query { any; };
allow-recursion { none; };
zone "${domain}" {
in-view internal;
};
};
'';
};
# Additional service hardening. You can see most of the options
# from systemd.exec(5) manual.
systemd.services.bind = {
serviceConfig = {
# Run it as an unprivileged user.
User = config.users.users.named.name;
Group = config.users.users.named.group;
UMask = "0037";
# Lock and protect various system components.
LockPersonality = true;
PrivateTmp = true;
NoNewPrivileges = true;
RestrictSUIDSGID = true;
ProtectHome = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
ProtectProc = "invisible";
# Make the filesystem invisible to the service.
ProtectSystem = "strict";
ReadWritePaths = [
config.services.bind.directory
"/etc/bind"
];
# Set up writable directories.
RuntimeDirectory = "named";
RuntimeDirectoryMode = "0750";
CacheDirectory = "named";
CacheDirectoryMode = "0750";
# Filtering system calls.
SystemCallFilter = [ "@system-service" ];
SystemCallErrorNumber = "EPERM";
SystemCallArchitectures = "native";
# Granting and restricting its capabilities. Take note we're not using
# syslog for this even if the application can so no syslog capability.
CapabilityBoundingSet = [
"CAP_NET_BIND_SERVICE"
"CAP_NET_RAW"
"CAP_CHOWN"
"CAP_SYS_CHROOT"
];
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
# Restrict what address families can it access.
RestrictAddressFamilies = [
"AF_LOCAL"
"AF_NETLINK"
"AF_BRIDGE"
"AF_INET"
"AF_INET6"
];
# Restricting what namespaces it can create.
RestrictNamespaces = true;
};
};
networking.firewall = {
allowedUDPPorts = [
53 # DNS
853 # DNS-over-TLS/DNS-over-QUIC
];
allowedTCPPorts = [ 53 853 ];
};
# Set up a fail2ban which is apparently already available in the package.
services.fail2ban.jails."named-refused" = ''
enabled = true
backend = systemd
filter = named-refused[journalmatch='_SYSTEMD_UNIT=bind.service']
maxretry = 3
'';
}