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
services.tinydns: Extend module with typed DNS records #50891
Conversation
This pull request has been mentioned on Nix community. There might be relevant details there: https://discourse.nixos.org/t/nixos-19-03-feature-freeze/1950/24 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, any chance for a nixos test?
exec ./run | ||
reload = '' | ||
cd $ROOT | ||
ln -sf ${pkgs.writeText "tinydns-data" configTxt} data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're linking to a build time text file which has already been linked. For what purpose? This file won't change during the life of a nixos generation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file data
is merely the source input to the actual DNS database, which is constructed at the start of the service. The reason for this construction is threefold:
- High availability. If for some reason,
nixos-rebuild
on the DNS server is not working but the database requires a change, the user can turndata
into a separate copy, alter its content and update the database without disrupting DNS service. - Failover. If for some reason, the authoritative DNS server fails and a sub-ordinate DNS server (which would have received its database via some other means) is promoted, the user can turn
data
into a separate copy, etc. - To ensure continous service, the user must be able to recreate database while the service is running. The protocol of the database compiler mandates that the source file is in the same directory as the result, making it necessary to (at least) provide a link in the database's final location.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yeah that all makes sense. Thanks for the answer.
@@ -32,23 +220,48 @@ with lib; | |||
config = mkIf config.services.tinydns.enable { | |||
environment.systemPackages = [ pkgs.djbdns ]; | |||
|
|||
users.users.tinydns = {}; | |||
users.users.tinydns = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is tinydns generating runtime data which a normal system user needs to access? I've never used this software, but from simply looking at this change it appears as if you could use a DynamicUser instead of creating a new static user with static ids.
See: #11908 (comment) and #20186 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would not be necessary in this PR, but I have an extension in the pipeline (which only makes sense to introduce after this change has been approved) that would make use of a named user for sending authenticated DNS database updates to sub-ordinate tinydns instances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, OK.
Do you have a relevant example I could peek from? |
Nothing specifically in mind, just always good to have a test in nixos/tests for every module. |
f9e3477
to
979b5fc
Compare
Updated. I added the test in the second commit and, while I was at it, added support for subordinate servers in the third commit (properly extending the test fixture). |
Glad to see a test. Looks like a new issue or two to work through, though. |
979b5fc
to
d44b8a0
Compare
Okay, I think, I satisfied the build bot. |
I'm not a fan of this representation, because
So instead I propose this: { lib, ... }: {
services.tinydns.records = with lib.dns; [
(raw ".example.com:1.2.3.4:a")
(srv "_some.service.example.com" 1 1 123 "server.example.com.")
];
} Where these functions are defined somewhat like this in {
raw = data: {
type = "raw";
value = data;
};
srv = service: priority: weight: port: target: {
type = "srv";
inherit service priority weight port target;
};
# ...
} Namely, these functions should just capture the arguments, leaving the interpretation and encoding to tinydns. Therefore these functions should be compatible and usable by other dns servers as well. Like this, the user is still free to use full attribute names (which will also be in the docs). |
It is an error, nix will tell you with a clear message that only one record type is allowed and yes, this is an oddity. I use it to have proper named NixOS options.
That is true in general and I am not proposing this format without proper thought. The reason I still chose functions was, that the data needs pre-computation (e.g. transformation of var-length strings to a list of fixed-length strings). This is a complexity level that cannot be easily hidden without hurting other properties badly.
I'm sorry, but this is not the reason for it. This notation was the only way I found to feed nix an un-evaluated lambda function. If you look closely, you will notice that you are allowed to do the following: {
services.tinydns.records = [
{ SRV = f: f "_service.example.com" 1 1 23 "server.example.com"; }
{ SRV = f: f "_service.example.com" 1 1 23 "replacement-server.example.com" "" "future-timestamp"; }
];
} With this construct, you can supply a variable amount of arguments to a lambda notation. I have not found another way to do this with nix. Why all the effort? As I wrote in my initial PR, on a medium-sized DNS Server you have to maintain thousands of records. It is not these one or two records that might look weird in the beginning but screenfuls of it. Interspersing data with named arguments or supplying fake argument due to the lack of support for variable amount of arguments did not seem to cut it. Reducing line noise was a top priority.
I cannot follow your argument. When you look closely you see that the record type is distinguished by an attribute name on the left. This is, of course, implemented as a NixOS option and, thus, has proper documentation in the right place. Please have a look at the generated documentation to see if that fits together.
This would have the following drawbacks:
This can also be done in the current PR by using the Having said all this: This is not my first PR to this project and I am very open to changes. Maintaining DNS records is a very special task that only hits very few users (but hits them hard). And I am proposing this non-obvious module with good reason and a lot of thought. So, if this does not find consent, I will gladly change it, but I beg to not ask for changes lightly or to just streamline it with the rest of the NixOS modules. |
Well, personally I'm not convinced this representation of DNS RRs is a good approach. I believe the zonefile format is nice – to write by hand, easy to generate from whatever tools you like (including nix), and you can run a checker inside a derivation on it (e.g. load it into a server) – I see little motivation for such checks to be evaluation-time. Also it's been widely understood for decades. EDIT: hmm, perhaps for some kinds of use cases, like overriding particular RRs 🤔 Still, I'm not tidnydns kind of person anyway, so I'll probably just leave the above comment here and not interfere anymore :-) I do DNS for job, though I'm on the resolver side. |
This still seems fairly contentious; I'm not very confident these issues will be resolved within the next week, so I'd suggest postponing this for 19.09. Unless of course everyone's happy with this by branch-off :) |
True, but this is not my position, here. I am convinced by the performance of the software and try to provide a reasonable means of configuring it via NixOS. I just take the input data format as a necessary thing this NixOS module has to provide. So, I am not making the rules, just reacting on them. |
What would be the procedure for progress? I worry that this PR is just forgotten for good in a month or so. (Given that it was initially open for 3 months without any action.) |
NixPkgs merges over a thousand PRs per month, so it's quite common that "less interesting" things just fall through the cracks, unless e.g. the package has maintainers that react when you ping them, etc. |
I'd suggest (as an option which I can see leading to this getting merged before the 19.03 branch-off) reworking this to take out the controversial data generation and focus on the other improvements — enabling reload instead of restart and the high-availability mechanism. The data generation could be moved into its own module in NUR or similar. |
Okay. I will rework this to just feature the pure (already encoded) data file and the rest of the improvements. I will add the data transformation functions to the library as @infinisil originally suggested. |
d44b8a0
to
cf3a797
Compare
@lheckemann, @infinisil I hope this is in acceptable form, now. Thanks for the review and hints! |
Oh actually regarding the vararg thing, there is a slightly unorthodox way to get that:
|
825a124
to
57d1dea
Compare
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
please remove the merge commit, and use |
974cdb8
to
8463002
Compare
Fixed. |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/nix-dns-a-nix-dsl-for-dns-zone-files/2466/4 |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/nix-dns-a-nix-dsl-for-dns-zone-files/2466/5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for your pull request. While I personally don't use djbdns
(most of my deployments are either using knot or nsd), I decided to do a more detailed review prompting your comment on Discourse.
Since I wrote one of the other implementations of Nix DSLs mentioned on Discourse for DNS I'm biased, so I tried to not commented a whole lot on lib/tinydns.nix
(plus others before me already did that). That said however, I don't think it's generic enough for this to be residing in lib/
.
However - apart from the comments already made - the changes here are backwards-incompatible with the current module options with no changelog entry and no deprecation path.
Additionally the way you've implemented zone transfers is also highly subjective and opinionated, so I'd also leave that up to the user on deciding how to do zone transfers (if they're even needed at all, eg. if you're not deploying your DNS servers synchronously). Maybe even just allow to specify custom commands - but then again - this is already possible using systemd.services.foo.preStart
&c.
So maybe @jerith666 (who did the initial implementation of the NixOS module and package), @flokli and @Mic92 can chime in here as well in order to have some more (possibly unbiased) input on this.
lib/tinydns.nix
Outdated
# Capture the varargs parser and its varargs into a set containing all | ||
# arguments as a list. Run the captured parser on the args when coercing the | ||
# set to string. | ||
captureParser = parser: { args = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this indented specially here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I messed up, fixed.
lib/tinydns.nix
Outdated
inherit (builtins) attrNames genList head isFunction isString length | ||
removeAttrs replaceStrings stringLength substring tail; | ||
|
||
_lib = rec { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for not indenting the following lines by 4 spaces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The content looked too long to be bothered with correct indentation and rather take the real estate of the line length; in the image of all-packages.nix. Changed to correct indentation, as it doesn't matter much.
requested service, a priority (int), a weight (int), a port (int), and | ||
a target.</para> | ||
|
||
<para><literal>TXT</literal> has two mandatory arguments; the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need all these <para/>
tags, since they're inserted automatically in text surrounded by two newlines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I didn't know. Changed.
type = types.bool; | ||
description = "Whether to run the tinydns dns server"; | ||
}; | ||
services.tinydns = mkOption { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This essentially changes the option semantics in a non-backwards-compatible way, so this needs a changelog entry for users of the existing implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I already wondered how this must be handled. Didn't find documentation regarding this point. Who is responsible for this changelog entry?
} | ||
''; | ||
default = {}; | ||
type = with types; attrsOf (submodule ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I consider this to be pretty dangerous, since it essentially makes attributes in types
over a very large scope, please consider doing this instead:
type = with types; attrsOf (submodule ( | |
type = types.attrsOf (types.submodule ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen this a lot but, sure. Changed it to explicit references.
# Expects tinydns data on standard input | ||
tinydnsReceive = pkgs.writeScript "tinydns-receive" '' | ||
#!${pkgs.stdenv.shell} -e | ||
cd ${cfg.rootPath} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cd ${cfg.rootPath} | |
cd ${lib.escapeShellArg cfg.rootPath} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Changed.
serviceConfig = { | ||
# Drops privileges and chroots to ROOT by itself | ||
ExecStart = "${getBin pkgs.djbdns}/bin/tinydns"; | ||
LimitDATA = mkDefault "40000000"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for this default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For tinydns, I know that this is a good working minimum. In case you do something funny with your zone transfers (run larger scripts or other sanity checking programs), you might need more memory.
# Drops privileges and chroots to ROOT by itself | ||
ExecStart = "${getBin pkgs.djbdns}/bin/tinydns"; | ||
LimitDATA = mkDefault "40000000"; | ||
RestartSec = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is because of the one second sleep in the script of the receiving end of the zone transfer, so please add a comment here to make sure this is clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it's not. The default value was just needlessly slow and there's no point in losing 1000 requests just because your server sleeps for 5 seconds (I believe that's the default).
@@ -1,26 +1,71 @@ | |||
import ./make-test-python.nix ({ lib, ...} : { | |||
import ./make-test.nix ({ pkgs, lib, ... }: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please note that Perl tests will be removed in NixOS 20.09.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll address that. Wasn't the case, when I first submitted this PR in Feb 2019. :)
StandardInput = "socket"; | ||
StandardError = "journal"; | ||
ExecStart = "${getBin pkgs.djbdns}/bin/axfrdns"; | ||
LimitDATA = mkDefault "15000000"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, why 15000000
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security, see above.
I would be gladly moving this out of
I do use synchronously deployed DNS servers. I implemented zone transfers as recommended by the author of tinydns. AXFR zone transfers have many known security issues, so while they are supported by tinydns, I didn't implement them. In fact, the only reason that the axfrdns is also running is to serve DNS records larger than 512kB and to accommodate certain clients that refuse to do UDP DNS queries. |
IMHO this is waaay too complex and specialized to have in the nixpkgs repository. I think it would be better to have this kind of module in a separate repository (e.g. as a flake). Also, reminder that Nix is not a general-purpose language and it's definitely not an appropriate language in which to write parsers. That sort of thing is likely to be very slow and memory-hungry. (BTW, |
I'll reduce this to take a list of entries, then, and put the EDIT: The consequence of removing this functionality is, though, that the record database is practically forced to be monolithic. Every other nixos module would have to pull in the record-building functions from the outside, which would clearly not be compatible with upstream. No other nixos module could then supply DNS records. This is maybe acceptable, but I wanted to make that point known.
Just to be clear, I did not write a parser anywhere. It is just a generator that takes nix attributes and outputs the data formatted for tinydns. The tinydns database is never parsed or interpreted. |
Referring to other flakes from inside nixpkgs probably isn't an option for quite some time. Mainline nix doesn't support flakes yet, and Adding config format generators to nixpkgs, not parsers, is probably fine, and has been done for many formats (see I agree the |
8463002
to
c39443e
Compare
…records Module improvements: - Multiple instances: - Create one instance per listening IP address - Availability: - Generate database at startup and not at evaluation time. In case of a hazard, the database can be modified while tinydns is running - Service is no longer stopped on configuration change but just its database is reloaded - Add support to answer long queries via TCP using axfrdns - Add support for database propagation and secondary DNS servers: - Propagate DNS updates via SSH - Failover: - In case of a hazard a secondary DNS server can be momentarily promoted to primary maintaining uninterrupted service without the need for a working NixOS infrastructure Extend the nixos test; also tests database propagation via SSH and TCP queries. Add functions that generate DNS records suitable for tinydns-data(8), i.e. converting null-terminated strings to 256-byte short strings, octal encoding of typed data fields and special characters, TAI64 timestamp generation.
c39443e
to
90f0ce0
Compare
I could greatly simplify the approach without sacrificing functionality. Internally, the tinydns."xxx".data option is now a list of attribute sets. The verbatim data record type, which contains opaque server-specific data:
Any other record type:
e.g.
Records can be specified as strings (become DATA), lists with the record type as the first element (terse, by-order-of-arguments specification), or as attribute sets:
Things, I'd like to point out:
|
@spacefrogg: Okay, this is going into a similar direction of Nix-dns now, except that you're using "list of X" versus "attribute set of X" and a few other semantic differences. However, I'm not even sure if anyone would even want Nix-dns or even my implementation for that matter, so maybe I should have made it more clear earlier: Please stop wasting your time going forward with this until at least one of the reviewers here thinks this is heading into the right direction. I personally had lots of pull request going into the trash bin as well, even ones that were less controversial than this one, and if I parse the comments here correctly I think this is leading nowhere. |
For reference, for opinionated packages/modules that are not general enough for nixpkgs/NixOS, we have the NUR, the Nix User Repository, to make those a bit easier to use. |
Thanks for tagging me, I hadn't seen this yet. I'm generally in favor of having a nix DSL for specifying DNS information. I think it'd be best if that DSL were server-agnostic, and let each server (nsd, tinydns, etc.) implement a translation layer to output its particular config file format. I don't think I'm qualified to comment on much else here. My use case for tinydns is super simple, I just use it for SOA, NS, and A records for machines on my local network. I'm confident I could adapt to whatever others agree on! :) |
Closing this as too opinionated and narrow scoped. |
Improvements:
reloaded
256-byte short strings.
Motivation for this change
More complicated record types, like URI or SRV records, need to be supplied as encoded strings to tinydns-data. This is hard to maintain, as it requires knowledge of the payload format as well as encoding integers in
\000
octal notation. Additionally, timestamps are kept in TAI64 format and there is very little tooling available to tranform dates into TAI64 timestamps. The module is extended by arecords
option that provides types of DNS records which take various numbers of arguments and convert them into tinydns-data records.Option format
The format of the DNS records is unusual as some have to be provided as functions, like this:
as opposed to a more conventional format like:
The reason is simple: Even a medium site needs hundreds of records, which makes the first format, although harder to write at first, much easier to maintain and read in long lists of records.
Things done
sandbox
innix.conf
on non-NixOS)nix-shell -p nox --run "nox-review wip"
./result/bin/
)nix path-info -S
before and after)