authoritative-dns: Init

23.11
Simon Bruder 2023-10-23 23:23:37 +02:00
parent 7a7d38c2f0
commit 8519bada60
Signed by: simon
GPG Key ID: 8D3C82F9F309F8EC
3 changed files with 189 additions and 31 deletions

View File

@ -0,0 +1,177 @@
{ 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"
];
extraConfig = ''
server:
${lib.concatStringsSep "\n" (map (address: " listen: ${address}@53") addresses.${config.networking.hostName})}
automatic-acl: on
log:
- target: syslog
any: info
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
- 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
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]
'');
};
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";
BindReadOnlyPaths = [ "/run/knot/knot.sock" ];
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
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
};
};
networking.firewall = {
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 ];
};
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.";
}
];
};
}

View File

@ -20,6 +20,7 @@
imports = [
../pkgs/modules.nix
./ausweisapp.nix
./authoritative-dns.nix
./cups.nix
./docker.nix
./fancontrol.nix

View File

@ -126,38 +126,18 @@ in
networking.firewall = {
trustedInterfaces = [ "wg-home" ];
allowedUDPPorts = lib.optionals enableServer [
serverPort
53
];
allowedUDPPorts = lib.optional enableServer serverPort;
};
services.bind = lib.mkIf enableServer {
enable = true;
zones = lib.singleton {
name = "vpn.sbruder.de";
master = true;
file =
let
# !!! very hacky
hexStringToInt = hex: (builtins.fromTOML "int = 0x${hex}").int;
peerRecords = lib.concatStrings
(lib.mapAttrsToList
(peer: peerConfig: ''
${peer} IN A ${peerConfig.address}
'')
peers);
peerRecordsHash = builtins.hashString "sha256" peerRecords;
serial = hexStringToInt (lib.substring 0 8 peerRecordsHash);
in
pkgs.writeText "vpn.sbruder.de.zone" (''
$TTL 3600
@ IN SOA ${serverHostName}.sbruder.de. hostmaster.sbruder.de. ${toString serial} 28800 3600 604800 3600
@ IN NS ${serverHostName}.sbruder.de.
'' + peerRecords);
};
};
sbruder.knot.generated-zones."vpn.sbruder.de" = pkgs.writeText "vpn.sbruder.de.zone" (''
; having $ORIGIN set here fails
@ IN SOA ${serverHostName}.sbruder.de. hostmaster.sbruder.de. 1 86400 10800 3600000 3600
@ IN NS ${serverHostName}.sbruder.de.
'' + lib.concatStrings
(lib.mapAttrsToList
(peer: peerConfig: ''
${peer} IN A ${peerConfig.address}
'')
peers));
};
}