diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 9155b8f04715b..7b0c2e32427ad 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -163,6 +163,8 @@ - [Veilid](https://veilid.com), a headless server that enables privacy-focused data sharing and messaging on a peer-to-peer network. Available as [services.veilid](#opt-services.veilid.enable). +- [crab-hole](https://github.com/LuckyTurtleDev/crab-hole), a cross platform Pi-hole clone written in Rust using hickory-dns/trust-dns. Available as [services.crab-hole](#opt-services.crab-hole.enable). + ## Backward Incompatibilities {#sec-release-24.11-incompatibilities} - The `sound` options have been removed or renamed, as they had a lot of unintended side effects. See [below](#sec-release-24.11-migration-sound) for details. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index fe67d39e70f92..1504fed90ac71 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1008,6 +1008,7 @@ ./services/networking/coredns.nix ./services/networking/corerad.nix ./services/networking/coturn.nix + ./services/networking/crab-hole.nix ./services/networking/create_ap.nix ./services/networking/croc.nix ./services/networking/dae.nix diff --git a/nixos/modules/services/networking/crab-hole.md b/nixos/modules/services/networking/crab-hole.md new file mode 100644 index 0000000000000..2e2b3af306053 --- /dev/null +++ b/nixos/modules/services/networking/crab-hole.md @@ -0,0 +1,210 @@ +# 🦀 crab-hole {#module-services-crab-hole} + +Crab-hole is a cross platform Pi-hole clone written in Rust using [hickory-dns/trust-dns](https://github.com/hickory-dns/hickory-dns). +It can be use as a network wide Ad and spy blocker or run on your local pc. + +For a secure and private communication, crab-hole has builtin support for doh(https), doq(quic) and dot(tls) for down- and upstreams and dnssec for upstreams. +It also comes with privacy friendly default logging settings. + +## Configuration {#module-services-crab-hole-configuration} +As an example config file using cloudflare as dot (dns-over-tls) upstream, you can use this [crab-hole.toml](https://github.com/LuckyTurtleDev/crab-hole/blob/main/example-config.toml) + + +The following is a basic nix config using UDP as a downstream and cloudflare as upstream. + +```nix +{ + services.crab-hole = { + enable = true; + + settings = { + blocklist = { + include_subdomains = true; + lists = [ + "https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn/hosts" + "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt" + ]; + }; + + downstream = [ + { + protocol = "udp"; + listen = "localhost"; + port = 53; + } + ]; + + upstream = { + name_servers = [ + { + socket_addr = "1.1.1.1:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + { + socket_addr = "[2606:4700:4700::1111]:853"; + protocol = "tls"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + ]; + }; + }; + }; +} +``` + +To test your setup, just query the DNS server with any domain like `example.com`. +To test if a domain gets blocked, just choose one of the domains from the blocklist. +If the server does not return an IP, this worked correctly. + +### Downstream options {#module-services-crab-hole-downstream} +There are multiple protocols which are supported for the downstream. +UDP, TLS, HTTPS and QUIC. +In the following there is a short overview over the different protocol options together with an example. + +#### UDP {#module-services-crab-hole-udp} +UDP is the simplest downstream, but it is not encrypted. +For this you need to use any other protocol. +***Note:** This also opens a TCP port* +```nix +{ + services.crab-hole.settings.downstream = [ + { + protocol = "udp"; + listen = "localhost"; + port = 53; + } + ]; +} +``` + +#### TLS {#module-services-crab-hole-tls} +TLS is a simple encrypted options to serve DNS. +It has similar settings to UDP. +To have encryption you need a valid certificate and key. +These are specified via a path to the files. +A valid certificate and key can be created by using services like ACME. +Make sure the crab-hole service user has access to these files. +Additionally you can set an optional timeout value, but this is not needed. +```nix +{ + services.crab-hole.settings.downstream = [ + { + protocol = "tls"; + listen = "[::]"; + port = 853; + certificate = ./dns.example.com.crt; + key = "/dns.example.com.key"; + # optional (default = 3000) + timeout_ms = 3000 + } + ]; +} +``` + +#### HTTPS {#module-services-crab-hole-https} +HTTPS is similar to TLS, with the only difference of the additional `dns_hostname` option. +This protocol might need a reverse proxy if there are other HTTPS services running. +Make sure the service has permissions to access the certificate and key. + +***Note:** this config is untested* +```nix +{ + services.crab-hole.settings.downstream = [ + { + protocol = "https"; + listen = "[::]"; + port = 443; + certificate = ./dns.example.com.crt; + key = "/dns.example.com.key"; + # optional + dns_hostname = "dns.example.com"; + # optional (default = 3000) + timeout_ms = 3000; + } + ]; +} +``` + +#### QUIC {#module-services-crab-hole-quic} +QUIC is identical to the HTTPS settings. +But you do not need a reverse proxy, which might be needed for HTTPS. +Make sure the service has permissions to access the certificate and key. +```nix +{ + services.crab-hole.settings.downstream = [ + { + protocol = "quic"; + listen = "127.0.0.1"; + port = 853; + certificate = ./dns.example.com.crt; + key = "/dns.example.com.key"; + # optional + dns_hostname = "dns.example.com"; + # optional (default = 3000) + timeout_ms = 3000; + } + ]; +} +``` + +### Upstream options {#module-services-crab-hole-upstream-options} +You can set additional options of the underlying DNS server. A full list of all the options can be found in the [hickory-dns/trust-dns documentation](https://docs.rs/trust-dns-resolver/0.23.0/trust_dns_resolver/config/struct.ResolverOpts.html). + +This can look like the following example. +```nix +{ + services.crab-hole.settings.upstream.options = { + validate = false; + }; +} +``` + +#### DNSSEC Issues {#module-services-crab-hole-dnssec} +Due to an upstream issue of [hickory-dns](https://github.com/hickory-dns/hickory-dns/issues/2429), sites without DNSSEC will not be resolved if `validate = true`. +Only DNSSEC capable sites will be resolved with this setting. +To prevent this, set `validate = false` or omit the `[upstream.options]`. + +### API {#module-services-crab-hole-api} +The API allows a user to fetch statistic and information about the crab-hole instance. +Basic information is availablee for everyone, while more detailed information is secured by a key, which will be set with the `admin_key` option. + +```nix +{ + services.crab-hole.settings.api = { + listen = "127.0.0.1"; + port = 8080; + # optional (default = false) + show_doc = true; # OpenAPI doc loads content from third party websites + # optional + admin_key = "1234"; + }; +} + +``` + +The documentation can be enabled seperately for the instance with `show_doc`. +This will then create an additional webserver, which hosts the API documentation. +An additional resource is in work in the [crab-hole repositiory](https://github.com/LuckyTurtleDev/crab-hole). + +## Troubleshooting {#module-services-crab-hole-troubleshooting} +You can check for errors using `systemctl status crab-hole` or `journalctl -xeu crab-hole.service`. + +### Invalid config {#module-services-crab-hole-invalid-config} +Some options of the service are in freeform and not type checked. +This can lead to a config which is not valid or cannot be parsed by crab-hole. +The error message will tell you what config value could not be parsed. +For more information check the [example config](https://github.com/LuckyTurtleDev/crab-hole/blob/main/example-config.toml). + +### Permission Error {#module-services-crab-hole-permission-error} +It can happen that the created certificates for TLS, HTTPS or QUIC are owned by another user or group. +For ACME for example this would be `acme:acme`. +To give the crab-hole service access to these files, the group which owns the certificate can be added as a supplementary group to the service. +For ACME for example: +```nix +{ + services.crab-hole.supplementaryGroups = [ "acme" ]; +} +``` diff --git a/nixos/modules/services/networking/crab-hole.nix b/nixos/modules/services/networking/crab-hole.nix new file mode 100644 index 0000000000000..9a7315321f281 --- /dev/null +++ b/nixos/modules/services/networking/crab-hole.nix @@ -0,0 +1,230 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.crab-hole; + + settingsFormat = pkgs.formats.toml { }; +in +{ + options = { + services.crab-hole = { + enable = lib.mkEnableOption "Crab-hole Service"; + + package = lib.mkPackageOption pkgs "crab-hole" { }; + + supplementaryGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "acme" ]; + description = "Adds additional groups to the crab-hole service. Can be useful to prevent permission issues."; + }; + + settings = lib.mkOption { + description = "Crab-holes config. See big example https://github.com/LuckyTurtleDev/crab-hole/blob/main/example-config.toml"; + + example = { + downstream = [ + { + listen = "localhost"; + port = 8080; + protocol = "udp"; + } + { + certificate = "dns.example.com.crt"; + dns_hostname = "dns.example.com"; + key = "dns.example.com.key"; + listen = "[::]"; + port = 8055; + protocol = "https"; + timeout_ms = 3000; + } + ]; + api = { + admin_key = "1234"; + listen = "127.0.0.1"; + port = 8080; + show_doc = true; + }; + blocklist = { + allow_list = [ + "file:///allowed.txt" + ]; + include_subdomains = true; + lists = [ + "https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn/hosts" + "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt" + "file:///blocked.txt" + ]; + }; + upstream = { + name_servers = [ + { + protocol = "tls"; + socket_addr = "[2606:4700:4700::1111]:853"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + { + protocol = "tls"; + socket_addr = "1.1.1.1:853"; + tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com"; + trust_nx_responses = false; + } + ]; + options = { + validate = false; + }; + }; + }; + + type = lib.types.submodule { + freeformType = settingsFormat.type; + options = { + blocklist = { + include_subdomains = lib.mkEnableOption "Include Subdomains"; + lists = lib.mkOption { + type = lib.types.listOf (lib.types.either lib.types.str lib.types.path); + default = [ ]; + description = "List of blocklists. If files are added via url, make sure the service has access to them!"; + apply = x: (lib.lists.forEach x (v: if builtins.isPath v then ("file://${v}") else v)); + }; + allow_list = lib.mkOption { + type = lib.types.listOf (lib.types.either lib.types.str lib.types.path); + default = [ ]; + description = "List of allowlists. If files are added via url, make sure the service has access to them!"; + apply = x: (lib.lists.forEach x (v: if builtins.isPath v then ("file://${v}") else v)); + }; + }; + + downstream = lib.mkOption { + description = "Configuration for the downstream DNS Servers"; + type = lib.types.listOf ( + lib.types.submodule { + freeformType = settingsFormat.type; + options = { + protocol = lib.mkOption { + type = lib.types.enum [ + "udp" + "tls" + "https" + "quic" + ]; + example = "udp"; + default = "udp"; + description = "The protocol to listen on"; + }; + listen = lib.mkOption { + type = lib.types.str; + example = "[::]"; + default = "localhost"; + description = "The IP to listen on"; + }; + port = lib.mkOption { + type = lib.types.ints.u16; + example = 53; + default = 53; + description = "The port to listen on"; + }; + }; + } + ); + }; + + upstream = { + options = lib.mkOption { + default = { }; + description = "Set resolover options. For a full list see https://docs.rs/trust-dns-resolver/0.23.0/trust_dns_resolver/config/struct.ResolverOpts.html"; + type = lib.types.submodule { + freeformType = settingsFormat.type; + }; + }; + name_servers = lib.mkOption { + description = "Configuration for the NameServers"; + type = lib.types.listOf ( + lib.types.submodule { + options = { + protocol = lib.mkOption { + type = lib.types.enum [ + "udp" + "tls" + "https" + "quic" + ]; + example = "tls"; + default = "tls"; + description = "The protocol to use when communicating with the NameServer"; + }; + socket_addr = lib.mkOption { + type = lib.types.str; + example = "1.1.1.1:853"; + description = "Address of the upstream DNS server"; + }; + tls_dns_name = lib.mkOption { + type = lib.types.str; + example = "1dot1dot1dot1.cloudflare-dns.com"; + description = "Domain name of the upstream DNS server"; + }; + trust_nx_responses = lib.mkEnableOption "Whether to trust `NXDOMAIN` responses from upstream nameservers"; + }; + } + ); + }; + }; + }; + }; + }; + + configFile = lib.mkOption { + type = lib.types.path; + description = "The config file of crab-hole. If files are added via url, make sure the service has access to them. Setting this option will override any configuration applied by the settings option."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + # Warning due to DNSSec issue in crab-hole + warnings = lib.optional (cfg.settings.upstream.options.validate or false) '' + Validate options will ONLY allow DNSSec domains. See https://github.com/LuckyTurtleDev/crab-hole/issues/29 + ''; + + services.crab-hole.configFile = lib.mkDefault ( + settingsFormat.generate "crab-hole.toml" cfg.settings + ); + environment.etc."crab-hole.toml".source = cfg.configFile; + + systemd.services.crab-hole = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + description = "Crab-hole dns server"; + environment.HOME = "/var/lib/crab-hole"; + restartTriggers = [ cfg.configFile ]; + serviceConfig = { + Type = "simple"; + DynamicUser = true; + SupplementaryGroups = lib.strings.concatStringsSep " " cfg.supplementaryGroups; + + StateDirectory = "crab-hole"; + WorkingDirectory = "/var/lib/crab-hole"; + + ExecStart = lib.getExe cfg.package; + + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; + + Restart = "on-failure"; + RestartSec = 1; + }; + }; + }; + + meta.maintainers = [ + lib.maintainers.NiklasVousten + ]; + # Readme from upstream + meta.doc = ./crab-hole.md; +}