nixos-config/modules/authoritative-dns.nix

203 lines
6.4 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{ config, lib, pkgs, ... }:
let
cfg = config.sbruder.knot;
primaryHost = "vueko";
secondaryHosts = [ "okarin" ];
isPrimaryHost = config.networking.hostName == primaryHost;
isSecondaryHost = lib.elem config.networking.hostName secondaryHosts;
addresses = {
vueko = [ "168.119.176.53" "2a01:4f8:c012:2f4::1" ];
okarin = [ "82.165.242.252" "2001:8d8:1800:8627::1" ];
};
in
{
options = {
sbruder.knot.generated-zones = lib.mkOption {
type = lib.types.attrsOf lib.types.path;
default = { };
description = "List of zones generated by a nix expression";
};
};
config = lib.mkIf (isPrimaryHost || isSecondaryHost) {
services.knot = {
enable = true;
keyFiles = [
# Managed in separate repository.
# It includes all statically managed zones.
# Even though it is not a key,
# it needs to be included here
# so the module disables configuration checks.
"/var/lib/knot/static.conf"
];
# TODO migrate to settings
settingsFile = pkgs.writeText "knot.conf" (''
include: /var/lib/knot/static.conf
server:
${lib.concatStringsSep "\n" (map (address: " listen: ${address}@53") addresses.${config.networking.hostName})}
automatic-acl: on
log:
- target: syslog
server: info
control: warning # otherwise stats gets logged every scrape
zone: info
mod-stats:
- id: custom
edns-presence: on
flag-presence: on
query-size: on
query-type: on
reply-size: on
remote:
${lib.concatStrings (lib.mapAttrsToList (host: hostAddresses: ''
- id: ${host}
address: [${lib.concatStringsSep ", " hostAddresses}]
'') addresses)}
'' + (lib.optionalString isPrimaryHost ''
# HACK: this string just continues the previous section
- id: inwx
# INWX only allows the specification of one primary DNS,
# which limits the IP protocol usable for zone transfers to one.
address: [185.181.104.96]
policy:
- id: default
nsec3: on
template:
- id: default
storage: /var/lib/knot/zones/
semantic-checks: on
# auto increment serial
zonefile-sync: -1
zonefile-load: difference-no-serial
journal-content: all
# secondary
notify: [inwx, ${lib.concatStringsSep ", " secondaryHosts}]
# dnssec
dnssec-signing: on
dnssec-policy: default
# stats
module: mod-stats/custom
- id: nix-generated
storage: /var/lib/knot/nix-zones/
semantic-checks: on
# auto increment serial
zonefile-sync: -1
zonefile-load: difference-no-serial
journal-content: all
# stats
module: mod-stats/custom
zone:
${lib.concatMapStrings (domain: ''
- domain: ${domain}
template: nix-generated
'') (lib.attrNames cfg.generated-zones)}
'') + (lib.optionalString isSecondaryHost ''
acl:
- id: primary_notify
address: [${lib.concatStringsSep ", " (lib.flatten addresses.${primaryHost})}]
action: notify
template:
- id: default
master: [${primaryHost}]
acl: [primary_notify]
# stats
module: mod-stats/custom
''));
};
users.users.knot = {
openssh.authorizedKeys.keys = config.sbruder.pubkeys.trustedKeys;
shell = pkgs.bashInteractive;
};
systemd.tmpfiles.rules = [
"f /var/lib/knot/static.conf 0644 knot knot - -"
] ++ (lib.optionals isPrimaryHost [
"d /var/lib/knot/nix-zones 0755 knot knot - -"
]);
systemd.services.knot-generated-zones = lib.mkIf isPrimaryHost {
wantedBy = [ "knot.service" ];
after = [ "knot.service" ];
path = with pkgs; [ knot-dns ];
script = ''
set -euo pipefail
${lib.concatStringsSep "\n" (lib.mapAttrsToList (domain: zonefile: ''
kzonecheck -o ${lib.escapeShellArg domain} ${lib.escapeShellArg zonefile}
target=/var/lib/knot/nix-zones/${lib.escapeShellArg domain}.zone
if [ -h "$target" ]; then
pre_target="$(readlink "$target")"
else
pre_target="/invalid/path"
fi
ln -sf ${lib.escapeShellArg zonefile} "$target"
if [ "$pre_target" != ${lib.escapeShellArg domain} ]; then
echo -n "Zone for ${lib.escapeShellArg domain} changed, reloading "
knotc zone-reload ${lib.escapeShellArg domain}
fi
'') cfg.generated-zones)}
'';
restartTriggers = lib.attrValues cfg.generated-zones;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = "knot";
CapabilityBoundingSet = ""; # clear
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateNetwork = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = true;
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_UNIX" ]; # knot socket
# this is not ideal, but I couldnt find out how to get a bind mount of the knot socket to work otherwise
RestrictNamespaces = [ true "~mnt" ];
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
};
};
networking.firewall = {
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 ];
};
services.prometheus.exporters.knot = {
enable = true;
listenAddress = config.sbruder.wireguard.home.address;
};
assertions = [
{
assertion = isPrimaryHost -> (lib.hasAttr "vpn.sbruder.de" cfg.generated-zones);
message = "The authoritative DNS module requires the server the wg-home wireguard server to run on the same host.";
}
];
};
}