forked from mirrors/nixpkgs
2e751c0772
the conversion procedure is simple: - find all things that look like options, ie calls to either `mkOption` or `lib.mkOption` that take an attrset. remember the attrset as the option - for all options, find a `description` attribute who's value is not a call to `mdDoc` or `lib.mdDoc` - textually convert the entire value of the attribute to MD with a few simple regexes (the set from mdize-module.sh) - if the change produced a change in the manual output, discard - if the change kept the manual unchanged, add some text to the description to make sure we've actually found an option. if the manual changes this time, keep the converted description this procedure converts 80% of nixos options to markdown. around 2000 options remain to be inspected, but most of those fail the "does not change the manual output check": currently the MD conversion process does not faithfully convert docbook tags like <code> and <package>, so any option using such tags will not be converted at all.
315 lines
10 KiB
Nix
315 lines
10 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
let
|
|
cfg = config.services.unbound;
|
|
|
|
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");
|
|
|
|
confNoServer = concatStringsSep "\n" ((mapAttrsToList (toConf "") (builtins.removeAttrs cfg.settings [ "server" ])) ++ [""]);
|
|
confServer = concatStringsSep "\n" (mapAttrsToList (toConf " ") (builtins.removeAttrs cfg.settings.server [ "define-tag" ]));
|
|
|
|
confFile = pkgs.writeText "unbound.conf" ''
|
|
server:
|
|
${optionalString (cfg.settings.server.define-tag != "") (toOption " " "define-tag" cfg.settings.server.define-tag)}
|
|
${confServer}
|
|
${confNoServer}
|
|
'';
|
|
|
|
rootTrustAnchorFile = "${cfg.stateDir}/root.key";
|
|
|
|
in {
|
|
|
|
###### interface
|
|
|
|
options = {
|
|
services.unbound = {
|
|
|
|
enable = mkEnableOption "Unbound domain name server";
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = pkgs.unbound-with-systemd;
|
|
defaultText = literalExpression "pkgs.unbound-with-systemd";
|
|
description = lib.mdDoc "The unbound package to use";
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.str;
|
|
default = "unbound";
|
|
description = lib.mdDoc "User account under which unbound runs.";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "unbound";
|
|
description = lib.mdDoc "Group under which unbound runs.";
|
|
};
|
|
|
|
stateDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/unbound";
|
|
description = lib.mdDoc "Directory holding all state for unbound to run.";
|
|
};
|
|
|
|
resolveLocalQueries = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = lib.mdDoc ''
|
|
Whether unbound should resolve local queries (i.e. add 127.0.0.1 to
|
|
/etc/resolv.conf).
|
|
'';
|
|
};
|
|
|
|
enableRootTrustAnchor = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = lib.mdDoc "Use and update root trust anchor for DNSSEC validation.";
|
|
};
|
|
|
|
localControlSocketPath = mkOption {
|
|
default = null;
|
|
# FIXME: What is the proper type here so users can specify strings,
|
|
# paths and null?
|
|
# My guess would be `types.nullOr (types.either types.str types.path)`
|
|
# but I haven't verified yet.
|
|
type = types.nullOr types.str;
|
|
example = "/run/unbound/unbound.ctl";
|
|
description = lib.mdDoc ''
|
|
When not set to `null` this option defines the path
|
|
at which the unbound remote control socket should be created at. The
|
|
socket will be owned by the unbound user (`unbound`)
|
|
and group will be `nogroup`.
|
|
|
|
Users that should be permitted to access the socket must be in the
|
|
`config.services.unbound.group` group.
|
|
|
|
If this option is `null` remote control will not be
|
|
enabled. Unbounds default values apply.
|
|
'';
|
|
};
|
|
|
|
settings = mkOption {
|
|
default = {};
|
|
type = with types; submodule {
|
|
|
|
freeformType = let
|
|
validSettingsPrimitiveTypes = oneOf [ int str bool float ];
|
|
validSettingsTypes = oneOf [ validSettingsPrimitiveTypes (listOf validSettingsPrimitiveTypes) ];
|
|
settingsType = oneOf [ str (attrsOf validSettingsTypes) ];
|
|
in attrsOf (oneOf [ 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 = literalExpression ''
|
|
{
|
|
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 = ''
|
|
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;
|
|
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;
|
|
define-tag = mkDefault "";
|
|
};
|
|
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 = mkIf (cfg.user == "unbound") {
|
|
unbound = {
|
|
description = "unbound daemon user";
|
|
isSystemUser = true;
|
|
group = cfg.group;
|
|
};
|
|
};
|
|
|
|
users.groups = mkIf (cfg.group == "unbound") {
|
|
unbound = {};
|
|
};
|
|
|
|
networking = mkIf cfg.resolveLocalQueries {
|
|
resolvconf = {
|
|
useLocalResolver = mkDefault true;
|
|
};
|
|
|
|
networkmanager.dns = "unbound";
|
|
};
|
|
|
|
environment.etc."unbound/unbound.conf".source = confFile;
|
|
|
|
systemd.services.unbound = {
|
|
description = "Unbound recursive Domain Name Server";
|
|
after = [ "network.target" ];
|
|
before = [ "nss-lookup.target" ];
|
|
wantedBy = [ "multi-user.target" "nss-lookup.target" ];
|
|
|
|
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 = [
|
|
confFile
|
|
];
|
|
|
|
serviceConfig = {
|
|
ExecStart = "${cfg.package}/bin/unbound -p -d -c /etc/unbound/unbound.conf";
|
|
ExecReload = "+/run/current-system/sw/bin/kill -HUP $MAINPID";
|
|
|
|
NotifyAccess = "main";
|
|
Type = "notify";
|
|
|
|
# FIXME: Which of these do we actualy need, can we drop the chroot flag?
|
|
AmbientCapabilities = [
|
|
"CAP_NET_BIND_SERVICE"
|
|
"CAP_NET_RAW"
|
|
"CAP_SETGID"
|
|
"CAP_SETUID"
|
|
"CAP_SYS_CHROOT"
|
|
"CAP_SYS_RESOURCE"
|
|
];
|
|
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
|
|
MemoryDenyWriteExecute = true;
|
|
NoNewPrivileges = true;
|
|
PrivateDevices = true;
|
|
PrivateTmp = true;
|
|
ProtectHome = true;
|
|
ProtectControlGroups = true;
|
|
ProtectKernelModules = true;
|
|
ProtectSystem = "strict";
|
|
RuntimeDirectory = "unbound";
|
|
ConfigurationDirectory = "unbound";
|
|
StateDirectory = "unbound";
|
|
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ];
|
|
RestrictRealtime = true;
|
|
SystemCallArchitectures = "native";
|
|
SystemCallFilter = [
|
|
"~@clock"
|
|
"@cpu-emulation"
|
|
"@debug"
|
|
"@keyring"
|
|
"@module"
|
|
"mount"
|
|
"@obsolete"
|
|
"@resources"
|
|
];
|
|
RestrictNamespaces = true;
|
|
LockPersonality = true;
|
|
RestrictSUIDSGID = true;
|
|
|
|
Restart = "on-failure";
|
|
RestartSec = "5s";
|
|
};
|
|
};
|
|
};
|
|
|
|
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" ] ''
|
|
You can use services.unbound.settings to add any configuration you want.
|
|
'')
|
|
];
|
|
}
|