Skip to content

Instantly share code, notes, and snippets.

@dwoffinden
Last active October 19, 2024 04:52
Show Gist options
  • Save dwoffinden/7ffb28874989932b584d1a5a0075f489 to your computer and use it in GitHub Desktop.
Save dwoffinden/7ffb28874989932b584d1a5a0075f489 to your computer and use it in GitHub Desktop.
Ad-blocking DNS-over-TLS/HTTPS server w/ NixOS & Knot Resolver. Pi-hole alternative.
# Copyright 2020 Google LLC.
# SPDX-License-Identifier: Apache-2.0
{ config, pkgs, ... }:
let
# https://github.com/StevenBlack/hosts/issues/451
# https://github.com/ScriptTiger/Hosts-Conversions
# https://github.com/ScriptTiger/scripttiger.github.io
# https://scripttiger.github.io/alts/
adblockLocalZones = pkgs.stdenv.mkDerivation {
name = "adblock-rpz";
src = (pkgs.fetchFromGitHub {
owner = "ScriptTiger";
repo = "scripttiger.github.io";
# nix-prefetch-github ScriptTiger scripttiger.github.io
rev = "04402a6726f97c5f0d30436a70ac1344dccb7cf1";
sha256 = "iSTR7j7QEr5xYtImyntDlVLbnN2ipcLcTRr4sfdx078=";
} + "/alts/rpz/blacklist.txt");
phases = [ "installPhase" ];
installPhase = "install -m 444 -D $src $out";
};
# TODO: config.networking.domain?
domain = "FILL ME IN";
certdir = config.security.acme.certs.${domain}.directory;
in {
networking.firewall.allowedTCPPorts = [ 80 443 853 ];
# TODO: ACME fails before the local resolver is up :/
networking.resolvconf.useLocalResolver = false;
services.nginx = {
enable = true;
virtualHosts.${domain} = {
enableACME = true;
locations."/" = { return = "204"; };
};
};
security.acme.acceptTerms = true;
security.acme.email = "FILL ME IN";
# for acme certs, TODO: make a proper group
users.users.knot-resolver.extraGroups = [ "nginx" ];
# https://github.com/NixOS/nixpkgs/issues/81109
nixpkgs.config.packageOverrides = pkgs: rec {
knot-resolver = pkgs.knot-resolver.override { extraFeatures = true; };
};
services.kresd = {
enable = true;
# Plain DNS only from localhost.
# You might want to add a LAN or VPN subnet, depending on deployment.
listenPlain = [ "127.0.0.1:53" "[::1]:53" ];
listenTLS = [ "0.0.0.0:853" "[::]:853" ];
listenDoH = [ "0.0.0.0:443" "[::]:443" ];
instances = 1;
# TODO: chain.pem for stapling: https://gitlab.nic.cz/knot/knot-resolver/-/issues/517
extraConfig = ''
policy.add(
policy.rpz(
policy.DENY_MSG('domain blocked by your resolver operator'),
'${adblockLocalZones}',
false))
policy.add(
policy.all(
policy.TLS_FORWARD({
{'8.8.8.8', hostname='dns.google'},
{'8.8.4.4', hostname='dns.google'}})))
net.tls("${certdir}/fullchain.pem", "${certdir}/key.pem")
-- refresh certs when ACME updates them
-- TODO: could probably do with systemd and a control socket?
-- TODO: may fail on first boot if ACME isn't fast, add a systemd dep
local notify = require('cqueues.notify')
local watcher = notify.opendir('${certdir}')
watcher:add('fullchain.pem')
worker.coroutine(function ()
for flags, name in watcher:changes() do
for flag in notify.flags(flags) do
print('file watcher:', name, notify[flag])
end
if name == 'fullchain.pem' then
net.tls("${certdir}/fullchain.pem", "${certdir}/key.pem")
end
end
end)
-- Below we create a tmpfs FS for the cache -- use almost all of it:
cache.size = cache.fssize() - 1*MB
'';
fileSystems."/var/cache/knot-resolver" = {
device = "tmpfs"; fsType = "tmpfs";
options = [ "defaults" "size=512M" ];
};
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment