-
-
Notifications
You must be signed in to change notification settings - Fork 18.1k
nixos/unbound: add settings option, deprecate extraConfig #89572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,51 +4,28 @@ with lib; | |
| let | ||
| cfg = config.services.unbound; | ||
|
|
||
| stateDir = "/var/lib/unbound"; | ||
|
|
||
| access = concatMapStringsSep "\n " (x: "access-control: ${x} allow") cfg.allowedAccess; | ||
|
|
||
| interfaces = concatMapStringsSep "\n " (x: "interface: ${x}") cfg.interfaces; | ||
|
|
||
| isLocalAddress = x: substring 0 3 x == "::1" || substring 0 9 x == "127.0.0.1"; | ||
|
|
||
| forward = | ||
| optionalString (any isLocalAddress cfg.forwardAddresses) '' | ||
| do-not-query-localhost: no | ||
| '' | ||
| + optionalString (cfg.forwardAddresses != []) '' | ||
| forward-zone: | ||
| name: . | ||
| '' | ||
| + concatMapStringsSep "\n" (x: " forward-addr: ${x}") cfg.forwardAddresses; | ||
|
|
||
| rootTrustAnchorFile = "${stateDir}/root.key"; | ||
|
|
||
| trustAnchor = optionalString cfg.enableRootTrustAnchor | ||
| "auto-trust-anchor-file: ${rootTrustAnchorFile}"; | ||
|
|
||
| confFile = pkgs.writeText "unbound.conf" '' | ||
| server: | ||
| ip-freebind: yes | ||
| directory: "${stateDir}" | ||
| username: unbound | ||
| chroot: "" | ||
| pidfile: "" | ||
| # when running under systemd there is no need to daemonize | ||
| do-daemonize: no | ||
| ${interfaces} | ||
| ${access} | ||
| ${trustAnchor} | ||
| ${lib.optionalString (cfg.localControlSocketPath != null) '' | ||
| remote-control: | ||
| control-enable: yes | ||
| control-interface: ${cfg.localControlSocketPath} | ||
| ''} | ||
| ${cfg.extraConfig} | ||
| ${forward} | ||
| ''; | ||
| in | ||
| { | ||
| yesOrNo = v: if v then "yes" else "no"; | ||
|
|
||
| toOption = indent: n: v: "${indent}${toString n}: ${v}"; | ||
|
|
||
| toConf = indent: n: v: | ||
| if builtins.isFloat v then (toOption indent n (builtins.toJSON v)) | ||
| else if isInt v then (toOption indent n (toString v)) | ||
| else if isBool v then (toOption indent n (yesOrNo v)) | ||
| else if isString v then (toOption indent n v) | ||
| else if isList v then (concatMapStringsSep "\n" (toConf indent n) v) | ||
| else if isAttrs v then (concatStringsSep "\n" ( | ||
| ["${indent}${n}:"] ++ ( | ||
| mapAttrsToList (toConf "${indent} ") v | ||
| ) | ||
| )) | ||
| else throw (traceSeq v "services.unbound.settings: unexpected type"); | ||
|
|
||
| confFile = pkgs.writeText "unbound.conf" (concatStringsSep "\n" ((mapAttrsToList (toConf "") cfg.settings) ++ [""])); | ||
|
|
||
| rootTrustAnchorFile = "${cfg.stateDir}/root.key"; | ||
|
|
||
| in { | ||
|
|
||
| ###### interface | ||
|
|
||
|
|
@@ -64,25 +41,30 @@ in | |
| description = "The unbound package to use"; | ||
| }; | ||
|
|
||
| allowedAccess = mkOption { | ||
| default = [ "127.0.0.0/24" ]; | ||
| type = types.listOf types.str; | ||
| description = "What networks are allowed to use unbound as a resolver."; | ||
| user = mkOption { | ||
| type = types.str; | ||
| default = "unbound"; | ||
| description = "User account under which unbound runs."; | ||
| }; | ||
|
|
||
| interfaces = mkOption { | ||
| default = [ "127.0.0.1" ] ++ optional config.networking.enableIPv6 "::1"; | ||
| type = types.listOf types.str; | ||
| description = '' | ||
| What addresses the server should listen on. This supports the interface syntax documented in | ||
| <citerefentry><refentrytitle>unbound.conf</refentrytitle><manvolnum>8</manvolnum></citerefentry>. | ||
| ''; | ||
| group = mkOption { | ||
| type = types.str; | ||
| default = "unbound"; | ||
| description = "Group under which unbound runs."; | ||
| }; | ||
|
|
||
| forwardAddresses = mkOption { | ||
| default = []; | ||
| type = types.listOf types.str; | ||
| description = "What servers to forward queries to."; | ||
| stateDir = mkOption { | ||
| default = "/var/lib/unbound"; | ||
| description = "Directory holding all state for unbound to run."; | ||
| }; | ||
|
|
||
| resolveLocalQueries = mkOption { | ||
| type = types.bool; | ||
| default = true; | ||
| description = '' | ||
| Whether unbound should resolve local queries (i.e. add 127.0.0.1 to | ||
| /etc/resolv.conf). | ||
| ''; | ||
| }; | ||
|
|
||
| enableRootTrustAnchor = mkOption { | ||
|
|
@@ -106,47 +88,123 @@ in | |
| and group will be <literal>nogroup</literal>. | ||
|
|
||
| Users that should be permitted to access the socket must be in the | ||
| <literal>unbound</literal> group. | ||
| <literal>config.services.unbound.group</literal> group. | ||
|
|
||
| If this option is <literal>null</literal> remote control will not be | ||
| configured at all. Unbounds default values apply. | ||
| enabled. Unbounds default values apply. | ||
| ''; | ||
| }; | ||
|
|
||
| extraConfig = mkOption { | ||
| default = ""; | ||
| type = types.lines; | ||
| settings = mkOption { | ||
| default = {}; | ||
| type = with types; submodule { | ||
|
|
||
| freeformType = let | ||
| validSettingsPrimitiveTypes = oneOf [ int str bool float ]; | ||
| validSettingsTypes = oneOf [ validSettingsPrimitiveTypes (listOf validSettingsPrimitiveTypes) ]; | ||
| settingsType = (attrsOf validSettingsTypes); | ||
| in attrsOf (oneOf [ string settingsType (listOf settingsType) ]) | ||
| // { description = '' | ||
| unbound.conf configuration type. The format consist of an attribute | ||
| set of settings. Each settings can be either one value, a list of | ||
| values or an attribute set. The allowed values are integers, | ||
| strings, booleans or floats. | ||
| ''; | ||
| }; | ||
|
|
||
| options = { | ||
| remote-control.control-enable = mkOption { | ||
| type = bool; | ||
| default = false; | ||
| internal = true; | ||
| }; | ||
| }; | ||
| }; | ||
| example = literalExample '' | ||
rissson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| server = { | ||
| interface = [ "127.0.0.1" ]; | ||
| }; | ||
| forward-zone = [ | ||
| { | ||
| name = "."; | ||
| forward-addr = "1.1.1.1@853#cloudflare-dns.com"; | ||
| } | ||
| { | ||
| name = "example.org."; | ||
| forward-addr = [ | ||
| "1.1.1.1@853#cloudflare-dns.com" | ||
| "1.0.0.1@853#cloudflare-dns.com" | ||
| ]; | ||
| } | ||
| ]; | ||
| remote-control.control-enable = true; | ||
| }; | ||
| ''; | ||
| description = '' | ||
| Extra unbound config. See | ||
| <citerefentry><refentrytitle>unbound.conf</refentrytitle><manvolnum>8 | ||
| </manvolnum></citerefentry>. | ||
| Declarative Unbound configuration | ||
| See the <citerefentry><refentrytitle>unbound.conf</refentrytitle> | ||
| <manvolnum>5</manvolnum></citerefentry> manpage for a list of | ||
| available options. | ||
| ''; | ||
| }; | ||
|
|
||
| }; | ||
| }; | ||
|
|
||
| ###### implementation | ||
|
|
||
| config = mkIf cfg.enable { | ||
|
|
||
| services.unbound.settings = { | ||
| server = { | ||
| directory = mkDefault cfg.stateDir; | ||
| username = cfg.user; | ||
| chroot = ''""''; | ||
| pidfile = ''""''; | ||
| # when running under systemd there is no need to daemonize | ||
| do-daemonize = false; | ||
| interface = mkDefault ([ "127.0.0.1" ] ++ (optional config.networking.enableIPv6 "::1")); | ||
| access-control = mkDefault ([ "127.0.0.0/8 allow" ] ++ (optional config.networking.enableIPv6 "::1/128 allow")); | ||
| auto-trust-anchor-file = mkIf cfg.enableRootTrustAnchor rootTrustAnchorFile; | ||
rissson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| tls-cert-bundle = mkDefault "/etc/ssl/certs/ca-certificates.crt"; | ||
| # prevent race conditions on system startup when interfaces are not yet | ||
| # configured | ||
| ip-freebind = mkDefault true; | ||
| }; | ||
| remote-control = { | ||
| control-enable = mkDefault false; | ||
| control-interface = mkDefault ([ "127.0.0.1" ] ++ (optional config.networking.enableIPv6 "::1")); | ||
| server-key-file = mkDefault "${cfg.stateDir}/unbound_server.key"; | ||
| server-cert-file = mkDefault "${cfg.stateDir}/unbound_server.pem"; | ||
| control-key-file = mkDefault "${cfg.stateDir}/unbound_control.key"; | ||
| control-cert-file = mkDefault "${cfg.stateDir}/unbound_control.pem"; | ||
| } // optionalAttrs (cfg.localControlSocketPath != null) { | ||
| control-enable = true; | ||
| control-interface = cfg.localControlSocketPath; | ||
| }; | ||
| }; | ||
|
|
||
| environment.systemPackages = [ cfg.package ]; | ||
|
|
||
| users.users.unbound = { | ||
| description = "unbound daemon user"; | ||
| isSystemUser = true; | ||
| group = lib.mkIf (cfg.localControlSocketPath != null) (lib.mkDefault "unbound"); | ||
| users.users = mkIf (cfg.user == "unbound") { | ||
| unbound = { | ||
| description = "unbound daemon user"; | ||
| isSystemUser = true; | ||
| group = cfg.group; | ||
| }; | ||
| }; | ||
|
|
||
| # We need a group so that we can give users access to the configured | ||
| # control socket. Unbound allows access to the socket only to the unbound | ||
| # user and the primary group. | ||
| users.groups = lib.mkIf (cfg.localControlSocketPath != null) { | ||
| users.groups = mkIf (cfg.group == "unbound") { | ||
| unbound = {}; | ||
| }; | ||
|
|
||
| networking.resolvconf.useLocalResolver = mkDefault true; | ||
| networking = mkIf cfg.resolveLocalQueries { | ||
| resolvconf = { | ||
| useLocalResolver = mkDefault true; | ||
| }; | ||
|
|
||
| networkmanager.dns = "unbound"; | ||
| }; | ||
|
|
||
| environment.etc."unbound/unbound.conf".source = confFile; | ||
|
|
||
|
|
@@ -156,8 +214,15 @@ in | |
| before = [ "nss-lookup.target" ]; | ||
| wantedBy = [ "multi-user.target" "nss-lookup.target" ]; | ||
|
|
||
| preStart = lib.mkIf cfg.enableRootTrustAnchor '' | ||
| ${cfg.package}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!" | ||
| path = mkIf cfg.settings.remote-control.control-enable [ pkgs.openssl ]; | ||
|
|
||
| preStart = '' | ||
| ${optionalString cfg.enableRootTrustAnchor '' | ||
| ${cfg.package}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!" | ||
| ''} | ||
| ${optionalString cfg.settings.remote-control.control-enable '' | ||
| ${cfg.package}/bin/unbound-control-setup -d ${cfg.stateDir} | ||
|
||
| ''} | ||
| ''; | ||
|
|
||
| restartTriggers = [ | ||
|
|
@@ -181,8 +246,8 @@ in | |
| "CAP_SYS_RESOURCE" | ||
| ]; | ||
|
|
||
| User = "unbound"; | ||
| Group = lib.mkIf (cfg.localControlSocketPath != null) (lib.mkDefault "unbound"); | ||
| User = cfg.user; | ||
| Group = cfg.group; | ||
|
|
||
| MemoryDenyWriteExecute = true; | ||
| NoNewPrivileges = true; | ||
|
|
@@ -211,9 +276,29 @@ in | |
| RestrictNamespaces = true; | ||
| LockPersonality = true; | ||
| RestrictSUIDSGID = true; | ||
|
|
||
| Restart = "on-failure"; | ||
| RestartSec = "5s"; | ||
| }; | ||
| }; | ||
| # If networkmanager is enabled, ask it to interface with unbound. | ||
| networking.networkmanager.dns = "unbound"; | ||
| }; | ||
|
|
||
| imports = [ | ||
| (mkRenamedOptionModule [ "services" "unbound" "interfaces" ] [ "services" "unbound" "settings" "server" "interface" ]) | ||
| (mkChangedOptionModule [ "services" "unbound" "allowedAccess" ] [ "services" "unbound" "settings" "server" "access-control" ] ( | ||
|
||
| config: map (value: "${value} allow") (getAttrFromPath [ "services" "unbound" "allowedAccess" ] config) | ||
| )) | ||
| (mkRemovedOptionModule [ "services" "unbound" "forwardAddresses" ] '' | ||
| Add a new setting: | ||
| services.unbound.settings.forward-zone = [{ | ||
| name = "."; | ||
| forward-addr = [ # Your current services.unbound.forwardAddresses ]; | ||
| }]; | ||
| If any of those addresses are local addresses (127.0.0.1 or ::1), you must | ||
| also set services.unbound.settings.server.do-not-query-localhost to false. | ||
| '') | ||
| (mkRemovedOptionModule [ "services" "unbound" "extraConfig" ] '' | ||
rissson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| You can use services.unbound.settings to add any configuration you want. | ||
| '') | ||
| ]; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.