nixos-config/modules/authoritative-dns.nix
Simon Bruder d93d724b9f
okarin: Migrate to different VPS
Previously, it was hosted on Ionos’s VMware-based infrastructure. I
already had a VPS on their new KVM-based infrastructure, as I was
planning to migrate okarin to it eventually (as it is cheaper). However,
the new infrastructure does not offer PTR records for IPv6 addresses.
Therefore, I was waiting until they would implement that feature (as the
support promised me they would to in the near future).

However, they are now migrating the (at least my) guests from their
VMware hypervisors onto the KVM ones, assigning new IPv6 addresses to
them. This makes the old VPS essentially the same as the old one, but
with less memory and more expensive. So I decided to migrate now.
2024-06-01 13:57:33 +02:00

217 lines
6.6 KiB
Nix
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
{ config, lib, pkgs, ... }:
let
cfg = config.sbruder.knot;
primaryHost = "vueko";
secondaryHosts = [ "renge" "okarin" "yuzuru" ];
isPrimaryHost = config.networking.hostName == primaryHost;
isSecondaryHost = lib.elem config.networking.hostName secondaryHosts;
addresses = {
vueko = [ "168.119.176.53" "2a01:4f8:c012:2f4::1" ];
renge = [ "152.53.13.113" "2a03:4000:6b:d2::1" ];
okarin = [ "85.215.165.213" "2a01:239:24b:1c00::1" ];
yuzuru = [ "85.215.73.203" "2a02:247a:272:1600::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"
];
settings = lib.mkMerge [
{
server = {
listen = map (address: "${address}@53") addresses.${config.networking.hostName};
automatic-acl = true;
};
log = lib.singleton {
target = "syslog";
server = "info";
control = "warning"; # otherwise stats gets logged every scrape
zone = "info";
};
mod-stats = lib.singleton {
id = "custom";
edns-presence = true;
flag-presence = true;
query-size = true;
query-type = true;
reply-size = true;
};
remote = (lib.mapAttrsToList
(host: hostAddresses: {
id = host;
address = hostAddresses;
})
addresses);
}
(lib.mkIf isPrimaryHost {
policy = lib.singleton {
id = "default";
nsec3 = true;
};
template = [
{
id = "default";
storage = "/var/lib/knot/zones/";
semantic-checks = true;
# auto increment serial
zonefile-sync = -1;
zonefile-load = "difference-no-serial";
journal-content = "all";
# secondary
notify = secondaryHosts;
# dnssec
dnssec-signing = true;
dnssec-policy = "default";
# stats
module = "mod-stats/custom";
}
{
id = "nix-generated";
storage = "/var/lib/knot/nix-zones/";
semantic-checks = true;
# auto increment serial
zonefile-sync = -1;
zonefile-load = "difference-no-serial";
journal-content = "all";
# stats
module = "mod-stats/custom";
}
];
zone = map
(domain: {
inherit domain;
template = "nix-generated";
})
(lib.attrNames cfg.generated-zones);
})
(lib.mkIf isSecondaryHost {
acl = lib.singleton {
id = "primary_notify";
address = lib.flatten addresses.${primaryHost};
action = "notify";
};
template = lib.singleton {
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.";
}
];
};
}