203 lines
6.4 KiB
Nix
203 lines
6.4 KiB
Nix
{ 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 couldn’t 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.";
|
||
}
|
||
];
|
||
};
|
||
}
|