Skip to content
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

Add stubby DNS-over-TLS service module #38586

Closed
wants to merge 323 commits into from
Closed

Add stubby DNS-over-TLS service module #38586

wants to merge 323 commits into from

Conversation

baroncharlus
Copy link
Contributor

@baroncharlus baroncharlus commented Apr 8, 2018

Motivation for this change

This change provides a module wrapper for the stubby package maintained by @leenaars. it allows the user to customize the service configuration. This was prompted by my wish to run unbound and stubby in tandem on my NixOS machine.

Things done

This change implements the systemd service and ability to easily declare certain common stubby settings from configuration.nix.

  • Tested using sandboxing (nix.useSandbox on NixOS, or option build-use-sandbox in nix.conf on non-NixOS)
  • Built on platform(s)
    • NixOS
    • macOS
    • other Linux distributions
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all pkgs that depend on this change using nix-shell -p nox --run "nox-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • Fits CONTRIBUTING.md.

I tested this by building and running the service successfully on my system. In particular, I tested passing in custom values the listening addresses.

Example configuration with unbound:

{
   services.stubby.enable = true;
   services.stubby.listenAddresses = [ "127.0.0.1@8053" "0::1@8053" ];

   services.unbound = { 
     enable = true;
     allowedAccess = [ 
       "192.168.1.1/24"
     ];  
     extraConfig = ''
       hide-identity: yes
       hide-version: yes
       qname-minimisation: yes
       harden-short-bufsize: yes
       harden-large-queries: yes
       harden-glue: yes
       harden-dnssec-stripped: yes
       harden-below-nxdomain: yes
       harden-referral-path: yes
       use-caps-for-id: yes
       do-not-query-localhost: no
     forward-zone:
       name: "."
         forward-addr: 127.0.0.1@8053
     '';
   };  
}

@baroncharlus baroncharlus changed the title WIP: Add stubby service module Add stubby DNS-over-TLS service module Apr 8, 2018
tls_pubkey_pinset:
- digest: "sha256"
value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
'';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make a comment on where to find these values?

users.extraUsers.stubby = {
description = "stubby daemon user";
isSystemUser = true;
group = "root";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a system user and in group root?

Copy link
Member

@Mic92 Mic92 Apr 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This service could also use systemd's DynamicUser, but using stubby as a group would be also a good start.


systemd.services.stubby = {
description = "Stubby local DNS resolver";
wantedBy = [ "multi-user.target" ];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think after = [ "network.target" ] should be here as well, not entirely sure though, since this module does DNS..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

network.target is correct, since there might be static ip addresses, the service should bind to.

preStart = ''
if [ ! -d ${stateDir} ]; then
mkdir ${stateDir}
fi
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you set the users home directory and createHome = true then you won't need this, the tmpfiles. and the WorkingDirectory attribute. Default permissions for a home directory are 700 though. Why did you use 750 with the root group? root can always read everything anyways.

'';

serviceConfig = {
ExecStart = "${pkgs.stubby}/bin/stubby -C ${stateDir}/stubby.yml";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the man page it seems that stateDir is not even needed actually. Just using ${pkgs.stubby}/bin/stubby -C ${confFile} should work too, without any of the directory set up commands.

${cfg.extraConfig}
upstream_recursive_servers:
${cfg.upstreamServers}
'';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a good case to use the toYAML function in lib. You could have a single option services.stubby.config of type attrsOf unspecified, set a default value of { resolution_type = "GETDNS_RESOLUTION_STUB"; ... } and then users can override this config with their own values without removing all others (not 100% sure about that though). Then you can use toYAML to generate the config. This has the benefit of not messing up string formatting in any, being easier to enter in Nix, and also being able to override all default settings (the edns_client_subnet_private: 1 can't be overridden here, and every overridable option would need its own option). extraConfig also becomes irrelevant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To still enable the user to pass their own config file without having to convert it to Nix, you could have another option configFile which would be set to the generated yaml file from config by default.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and you could even still keep all the options with the descriptions if you want, using such a config option backed by toYAML can do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I seem to be having some trouble using toYAML for this. Right now, toYAML just calls builtin.toJSON, which produces a JSON configuration file. The trouble is, stubby has an altogether different (and more complex) JSON format that it also accepts, and so they don't line up quite right. It also seems to expect a complete configuration file in the JSON case, rather than merging explicitly set values with the defaults the way it does with YAML.

I will keep experimenting. It's possible I'm still doing something wrong. Are there plans to produce proper YAML with toYAML in the future?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well that's not good what they're doing.. Because YAML is a superset of JSON, every JSON is also a valid YAML.

From their source I found that they use the YAML processing when the file ends with .yml or .yaml though (see https://github.com/getdnsapi/stubby/blob/4b9b0362f97d8db61cd0294c57f583437a5f47b4/src/stubby.c#L280-L281), so I think it should work if you do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, so it still seems something about their parser isn't happy with JSON. I took a confirmed-valid YAML configuration (the default), yaml.loaded and json.dumped to JSON, and started the daemon by pointing it at that config file, and I still got the following:

Apr 08 18:30:28 nixos stubby[14459]: Error parsing config file "/var/lib/stubby/.stubby.yml": Did not update the context

To be fair, their parser also occasionally has trouble with even valid YAML (IPv6 addresses with leading and trailing colons).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh god, they're rolling their own YAML parser and it doesn't even work correctly :/ (yes YAML parsers are hard but JSON compatibility is the least they could do)

Can you file an issue at their repo for this? If they don't fix this soon this module will just have to roll with the inflexible configuration method. I don't think adjusting our toYAML function for this is reasonable, as it does indeed work correctly (and since it outputs JSON, Nix can easily parse it again with builtins.fromJSON).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totally agree regarding the functionality of toYAML vs them rolling their own parser. I will open that issue. In the meantime, I add in your suggestions to the previous way.

In any case, thank you for making me aware of toYAML , as it's bound to come in handy sooner than later.

@baroncharlus
Copy link
Contributor Author

baroncharlus commented Apr 8, 2018

Hey @infinisil, thanks for all the great feedback! I'll incorporate your suggestions. In particular I hadn't seen the toYAML function in the wild before, and this definitely seems like a good application for it.

Copy link
Member

@infinisil infinisil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2, still some stuff left, but it's getting there :D

ExecStart = "${pkgs.stubby}/bin/stubby -C ${confFile}";
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
CapabilitiesBoundingSet = "CAP_NET_BIND_SERVICE";
User = "stubby";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the user now doesn't require any directory, you can remove the whole users.extraUsers.. part, and as suggested by @Mic92 use systemd's DynamicUser feature (documented in man systemd.exec). Just setting serviceConfig.DynamicUser = true; should do it. Then this User = can also be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works very well, thanks.

serviceConfig = {
ExecStart = "${pkgs.stubby}/bin/stubby -C ${confFile}";
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
CapabilitiesBoundingSet = "CAP_NET_BIND_SERVICE";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never played around with capabilities in systemd, are both of these required for the unprivileged user to bind to ports < 1024?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, stubby (and DNS-over-TLS in general, I think) binds by default to 853. It's possible to listen on higher ports, however. I am working on a function in the let expression using lib.splitString that should allow this capability to be set only when absolutely necessary (i.e., if the listening port is < 1024).

stateDir = "/var/lib/stubby";
fallbacks = concatMapStringsSep "\n " (x: "- ${x}") cfg.fallbackProtocols;
listeners = concatMapStringsSep "\n " (x: "- ${x}") cfg.listenAddresses;
extraConfig = optionalString (cfg.extraConfig != "") ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value doesn't seem to be used anywhere


queryPaddingBlocksize = mkOption {
default = "128";
type = types.str;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option and a few others should probably be of types.int instead of types.str

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this results in a coercion error, because these values are interpolated into the configuration file string. Does it make more sense to coerce them beforehand in the let expression, or should we just declare them as types.str?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use the function toString to convert them: tls_query_padding_blocksize: ${toString cfg.queryPaddingBlocksize}


roundRobinUpstreams = mkOption {
default = "1";
type = types.str;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be types.bool, also for subnetPrivate (and update option descriptions accordingly)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And for booleans you can use a simple if cfg.roundRobinUpstreams then "1" else "0"

};

upstreamServers = mkOption {
default = "${defaultUpstream}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the same as default = defaultUpstream;

listeners = concatMapStringsSep "\n " (x: "- ${x}") cfg.listenAddresses;

# split string at @ to get the listening port.
getPort = x: toInt (builtins.elemAt (splitString "@" x) 1);
Copy link
Member

@Mic92 Mic92 Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I only specify:

{}: {
services.stubby.enable = true;
}

I get:

error: list index 1 is out of bounds, at /home/joerg/git/nixpkgs/nixos/modules/services/networking/stubby.nix:13:23
(use '--show-trace' to show detailed location information)
make: *** [Makefile:7: test] Error 1
make: Leaving directory '/home/joerg/git/nixos-shell'

};

listenAddresses = mkOption {
default = [ "127.0.0.1" "0::1" ];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the default values still work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof! good catch. thanks.

@Mic92
Copy link
Member

Mic92 commented Apr 9, 2018

You have opened the pull request against NixOS:release-18.03. It should be opened against master and backported after merge.

@Mic92
Copy link
Member

Mic92 commented Apr 9, 2018

@baroncharlus you should rebase your changes onto the master branch with git-rebase. If you don't know how to do this, just readd your changes on the checkout master branch and open a new pull request.

svanderburg and others added 2 commits April 9, 2018 09:11
Closes #36887.

(cherry picked from commit d7470c1)
Robert Schütz and others added 23 commits April 9, 2018 09:14
(cherry picked from commit ee1896d)
(cherry picked from commit b9bde5b)
(cherry picked from commit 804789e)
(cherry picked from commit c8e2312)
(cherry picked from commit 32e47b1)
(cherry picked from commit 6545d15)
(cherry picked from commit e0fbaaa)
service-runner had a backwards incompatible update, and parsoid 0.9.0
doesn't work with current stable MediaWiki. Instead use as a source
a repository with 0.8.0 and pinned service-runner version.

(cherry picked from commit 37546be)
Based on how ISO images are added to the release.

(cherry picked from commit 181e067)
because of a change in #36850.
spotted by @jtojnar b7a2333#commitcomment-28134992

(cherry picked from commit 33b0ad8)
https://hydra.nixos.org/build/70699663

This package is already dropped on master, after the 18.03 cut-off.
(cherry picked from commit c011843)
(cherry picked from commit 650aec3)
(cherry picked from commit 4df34f8)
 - Rectifies diverging CSS by combining
   nixos/nixpkgs docs CSS
 - Moves our custom Highlight.js loader in to
   the hljs package
 - Switches the nixos docs to use SVG
   callouts too

(cherry picked from commit 8f33464)
stubby

This change allows a user to declare a configuration for stubby in
/etc/nixos/configuration.nix.

nixos/modules/module-list.nix: adding stubby.nix to module-list

Enabling module.

nixos/modules/services/networking/stubby.nix: Adding documentation

Adding documentation to each mkOption.

networking/stubby.nix: changing daemon user permissions

This change normalizes some of the directory permissions and conf file
creation processes.

networking/stubby.nix: Removing unneeded systemd cruft

WorkingDir and tempfile are no longer needed when the user has a
homedir.

networking/stubby.nix: adding more detailed resolver docs

Adding more detailed information on where to find your own upstream
resolver settings. Also adding wants = network.target to the systemd
configuration.

networking/stubby.nix: updating docs and adding edns option.

Fixing an issue where the default docs were breaking docs compilation.
Adding a mkOption for EDNS private subnet settings.

networking/stubby.nix: Boolean-izing string values

Changes the two 0|1 settings in stubby.yml to booleans from strings.

networking/stubby.nix: adding capabilities toggle for high/low ports

This change automatically applies or removes capabilities to the service
based upon the ports selected by the user.
@baroncharlus
Copy link
Contributor Author

Whoops, that didn't work. Closing and reopening a bit later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet