From 749f4be1ef9b115c97fa717cc6068ab342c7650c Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Tue, 12 Nov 2019 19:30:35 +0100 Subject: Configure nextdns usage via module --- system/modules/darwin/kresd.nix | 45 ++++++++ system/modules/darwin/stubby.nix | 218 +++++++++++++++++++++++++++++++++++++++ system/modules/nextdns.nix | 92 +++++++++++++++++ 3 files changed, 355 insertions(+) create mode 100644 system/modules/darwin/kresd.nix create mode 100644 system/modules/darwin/stubby.nix create mode 100644 system/modules/nextdns.nix (limited to 'system/modules') diff --git a/system/modules/darwin/kresd.nix b/system/modules/darwin/kresd.nix new file mode 100644 index 00000000..6bce8af1 --- /dev/null +++ b/system/modules/darwin/kresd.nix @@ -0,0 +1,45 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.kresd; + package = pkgs.knot-resolver; + + configFile = pkgs.writeText "kresd.conf" cfg.extraConfig; +in +{ + options = { + services.kresd.enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable knot-resolver daemon."; + }; + + services.kresd.extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration to be added to the generated configuration file. + ''; + }; + }; + + config = mkIf cfg.enable { + launchd.daemons.kresd = { + command = "${package}/bin/kresd -c ${configFile}"; + + serviceConfig = { + ProcessType = "Interactive"; + # Sockets = { + # Listeners = { + # SockServiceName = "dns"; + # SockFamily = "IPv4"; + # }; + # }; + }; + }; + + environment.systemPackages = [ package ]; + }; +} diff --git a/system/modules/darwin/stubby.nix b/system/modules/darwin/stubby.nix new file mode 100644 index 00000000..b3b67755 --- /dev/null +++ b/system/modules/darwin/stubby.nix @@ -0,0 +1,218 @@ +{ config, lib, pkgs, ...}: + +with lib; + +let + cfg = config.services.stubby; + package = pkgs.stubby; + + fallbacks = concatMapStringsSep "\n " (x: "- ${x}") cfg.fallbackProtocols; + listeners = concatMapStringsSep "\n " (x: "- ${x}") cfg.listenAddresses; + + # By default, the recursive resolvers maintained by the getdns + # project itself are enabled. More information about both getdns's servers, + # as well as third party options for upstream resolvers, can be found here: + # https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers + # + # You can override these values by supplying a yaml-formatted array of your + # preferred upstream resolvers in the following format: + # + # 106 # - address_data: IPv4 or IPv6 address of the upstream + # port: Port for UDP/TCP (default is 53) + # tls_auth_name: Authentication domain name checked against the server + # certificate + # tls_pubkey_pinset: An SPKI pinset verified against the keys in the server + # certificate + # - digest: Only "sha256" is currently supported + # value: Base64 encoded value of the sha256 fingerprint of the public + # key + # tls_port: Port for TLS (default is 853) + + defaultUpstream = '' + - address_data: 145.100.185.15 + tls_auth_name: "dnsovertls.sinodun.com" + tls_pubkey_pinset: + - digest: "sha256" + value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4= + - address_data: 145.100.185.16 + tls_auth_name: "dnsovertls1.sinodun.com" + tls_pubkey_pinset: + - digest: "sha256" + value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA= + - address_data: 185.49.141.37 + tls_auth_name: "getdnsapi.net" + tls_pubkey_pinset: + - digest: "sha256" + value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q= + - address_data: 2001:610:1:40ba:145:100:185:15 + tls_auth_name: "dnsovertls.sinodun.com" + tls_pubkey_pinset: + - digest: "sha256" + value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4= + - address_data: 2001:610:1:40ba:145:100:185:16 + tls_auth_name: "dnsovertls1.sinodun.com" + tls_pubkey_pinset: + - digest: "sha256" + value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA= + - address_data: 2a04:b900:0:100::38 + tls_auth_name: "getdnsapi.net" + tls_pubkey_pinset: + - digest: "sha256" + value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q= + ''; + + # Resolution type is not changeable here because it is required per the + # stubby documentation: + # + # "resolution_type: Work in stub mode only (not recursive mode) - required for Stubby + # operation." + # + # https://dnsprivacy.org/wiki/display/DP/Configuring+Stubby + + confFile = pkgs.writeText "stubby.yml" '' + resolution_type: GETDNS_RESOLUTION_STUB + dns_transport_list: + ${fallbacks} + tls_authentication: ${cfg.authenticationMode} + tls_query_padding_blocksize: ${toString cfg.queryPaddingBlocksize} + edns_client_subnet_private: ${if cfg.subnetPrivate then "1" else "0"} + idle_timeout: ${toString cfg.idleTimeout} + listen_addresses: + ${listeners} + round_robin_upstreams: ${if cfg.roundRobinUpstreams then "1" else "0"} + ${cfg.extraConfig} + upstream_recursive_servers: + ${cfg.upstreamServers} + ''; +in + +{ + options = { + services.stubby = { + + enable = mkEnableOption "Stubby DNS resolver"; + + fallbackProtocols = mkOption { + default = [ "GETDNS_TRANSPORT_TLS" ]; + type = with types; listOf (enum [ + "GETDNS_TRANSPORT_TLS" + "GETDNS_TRANSPORT_TCP" + "GETDNS_TRANSPORT_UDP" + ]); + description = '' + Ordered list composed of one or more transport protocols. + Strict mode should only use GETDNS_TRANSPORT_TLS. + Other options are GETDNS_TRANSPORT_UDP and + GETDNS_TRANSPORT_TCP. + ''; + }; + + authenticationMode = mkOption { + default = "GETDNS_AUTHENTICATION_REQUIRED"; + type = types.enum [ + "GETDNS_AUTHENTICATION_REQUIRED" + "GETDNS_AUTHENTICATION_NONE" + ]; + description = '' + Selects the Strict or Opportunistic usage profile. + For strict, set to GETDNS_AUTHENTICATION_REQUIRED. + for opportunistic, use GETDNS_AUTHENTICATION_NONE. + ''; + }; + + queryPaddingBlocksize = mkOption { + default = 128; + type = types.int; + description = '' + EDNS0 option to pad the size of the DNS query to the given blocksize. + ''; + }; + + subnetPrivate = mkOption { + default = true; + type = types.bool; + description = '' + EDNS0 option for ECS client privacy. Default is + true. If set, this option prevents the client + subnet from being sent to authoritative nameservers. + ''; + }; + + idleTimeout = mkOption { + default = 10000; + type = types.int; + description = "EDNS0 option for keepalive idle timeout expressed in + milliseconds."; + }; + + listenAddresses = mkOption { + default = [ "127.0.0.1" "0::1" ]; + type = with types; listOf str; + description = '' + Sets the listen address for the stubby daemon. + Uses port 53 by default. + Ise IP@port to specify a different port. + ''; + }; + + roundRobinUpstreams = mkOption { + default = true; + type = types.bool; + description = '' + Instructs stubby to distribute queries across all available name + servers. Default is true. Set to + false in order to use the first available. + ''; + }; + + upstreamServers = mkOption { + default = defaultUpstream; + type = types.lines; + description = '' + Replace default upstreams. See stubby + 1 for an + example of the entry formatting. In Strict mode, at least one of the + following settings must be supplied for each nameserver: + tls_auth_name or + tls_pubkey_pinset. + ''; + }; + + debugLogging = mkOption { + default = false; + type = types.bool; + description = "Enable or disable debug level logging."; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + description = '' + Add additional configuration options. see + stubby1 + for more options. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + launchd.daemons.stubby = { + command = "${package}/bin/stubby -C ${confFile} ${optionalString cfg.debugLogging "-l"}"; + + serviceConfig = { + ProcessType = "Interactive"; + RunAtLoad = true; + KeepAlive = true; + # Sockets = { + # Listeners = { + # SockServiceName = "dns"; + # SockFamily = "IPv4"; + # }; + # }; + }; + }; + + environment.systemPackages = [ package ]; + }; +} diff --git a/system/modules/nextdns.nix b/system/modules/nextdns.nix new file mode 100644 index 00000000..6de4acdb --- /dev/null +++ b/system/modules/nextdns.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + inherit (pkgs) stdenv; + + cfg = config.networking.nextdns; + + identifyingPrefix = if cfg.identifyDevice then "${config.networking.hostName}-" else ""; +in +{ + options = { + networking.nextdns.enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable DNS resolution via NextDNS"; + }; + + networking.nextdns.configID = mkOption { + type = types.str; + default = ""; + example = literalExample "abcdef"; + description = "NextDNS configuration ID"; + }; + + networking.nextdns.identifyDevice = mkOption { + type = types.bool; + default = false; + description = "Whether to send hostname for identifying in your logs"; + }; + + networking.nextdns.resolver = mkOption { + type = types.enum [ "kresd" "stubby" ]; + default = if stdenv.isDarwin then "stubby" else "kresd"; + description = "Resolver to use"; + }; + }; + + config = mkIf cfg.enable { + + assertions = [ + { + assertion = !(stdenv.isDarwin && cfg.resolver == "kresd"); + message = "kresd is not supported on Darwin"; + } + ]; + networking = if stdenv.isDarwin then + { + dns = [ + "::1" + "127.0.0.1" + "2a07:a8c0::ab:d6e5" + "2a07:a8c1::ab:d6e5" + "45.90.28.25" + "45.90.30.25" + ]; + } else { + networking.networkmanager.dns = "none"; + resolvconf.useLocalResolver = true; + }; + services = if cfg.resolver == "kresd" then { + kresd = { + enable = true; + extraConfig = '' + policy.add(policy.all(policy.TLS_FORWARD({ + {'45.90.28.0', hostname='${identifyingPrefix}${cfg.configID}.dns1.nextdns.io'}, + {'2a07:a8c0::', hostname='${identifyingPrefix}${cfg.configID}.dns1.nextdns.io'}, + {'45.90.30.0', hostname='${identifyingPrefix}${cfg.configID}.dns2.nextdns.io'}, + {'2a07:a8c1::', hostname='${identifyingPrefix}${cfg.configID}.dns2.nextdns.io'} + }))) + ''; + }; + } else if cfg.resolver == "stubby" then { + stubby = { + enable = cfg.resolver == "stubby"; + fallbackProtocols = lib.mkDefault [ "GETDNS_TRANSPORT_TLS" ]; + roundRobinUpstreams = lib.mkDefault false; + upstreamServers = '' + - address_data: 45.90.28.0 + tls_auth_name: "${identifyingPrefix}${cfg.configID}.dns1.nextdns.io" + - address_data: 2a07:a8c0::0 + tls_auth_name: "${identifyingPrefix}${cfg.configID}.dns1.nextdns.io" + - address_data: 45.90.30.0 + tls_auth_name: "${identifyingPrefix}${cfg.configID}.dns2.nextdns.io" + - address_data: 2a07:a8c1::0 + tls_auth_name: "${identifyingPrefix}${cfg.configID}.dns2.nextdns.io" + ''; + }; + } else abort "Cannot configure resolver ${cfg.resolver}"; + }; +} -- cgit 1.4.1