Git Product home page Git Product logo

dns.nix's Introduction

dns.nix

A Nix DSL for defining DNS zones

This repository provides:

  1. NixOS-style module definitions that describe DNS zones and records in them.
  2. A DSL that simplifies describing your DNS zones.

An example of a zone

with dns.lib.combinators; {
  SOA = {  # Human readable names for fields
    nameServer = "ns.test.com.";
    adminEmail = "[email protected]";  # Email address with a real `@`!
    serial = 2019030800;
    # Sane defaults for the remaining ones
  };

  NS = [  # A zone consists of lists of records grouped by type
    "ns.test.com."
    "ns2.test.com."
  ];

  A = [
    { address = "203.0.113.1"; }  # Generic A record
    { address = "203.0.113.2"; ttl = 60 * 60; }  # Generic A with TTL
    (a "203.0.113.3")  # Simple a record created with the `a` combinator
    (ttl (60 * 60) (a "203.0.113.4"))  # Equivalent to the second one
  ];

  AAAA = [
    "4321:0:1:2:3:4:567:89ab"  # For simple records you can use a plain string
  ];

  CAA = letsEncrypt "[email protected]";  # Common template combinators included

  MX = mx.google;  # G Suite mail servers;

  TXT = [
    (with spf; strict [google])  # SPF: only allow gmail
  ];

  subdomains = {
    www.A = [ "203.0.114.1" ];

    staging = delegateTo [  # Another shortcut combinator
      "ns1.another.com."
      "ns2.another.com."
    ];
  };
}

You can build an example zone in example.nix by running nix build -f ./example.nix or nix-build ./example.nix.

Why?

  • The DNS zone syntax is crazy. Nix is nice and structured.
  • Having the full power of a Turing-complete functional language (let, if, map and other things you are used to).
  • All kinds of shortcuts and useful combinators.
  • Strong typing provded by the NixOS module system.
  • Modularity: records defined in different modules get magically merged.

Use

Importing

There are two ways to import dns.nix.

As a flake

Add it as an input to your flake:

# flake.nix

{
  inputs = {
    # ...

    dns = {
      url = "github:kirelagin/dns.nix";
      inputs.nixpkgs.follows = "nixpkgs";  # (optionally)
    };
  };

  outputs = { self, nixpkgs, dns }: {
    # Most functions from `dns.nix` are available in `dns.lib`.
    # Functions that require `stdenv` (e.g. `writeZone`) are in
    # `dns.util.<system>`.
    # ...
  };
}

Importing directly (legacy)

Always get the latest version from GitHub:

let
  dns = import (builtins.fetchTarball "https://github.com/kirelagin/dns.nix/archive/master.zip");
in {
  # ...
}

To make your setup more reproducible, you should pin the version used by specifying a commit hash or using a submodule. This is all a little clumsy and nowadays it is probably best to simply switch to flakes.

In your NixOS configuration

There is a chance that in the future we will either integrate this into existing NixOS modules for different DNS servers, or will provide a separate NixOS module that will configure DNS servers for you.

# example.com.nix

{ dns }:

with dns.lib.combinators;

{
  SOA = {
    nameServer = "ns1";
    adminEmail = "[email protected]";
    serial = 2019030800;
  };

  NS = [
    "ns1.example.com."
    "ns2.example.com."
  ];

  A = [ "203.0.113.1" ];
  AAAA = [ "4321:0:1:2:3:4:567:89ab" ];

  subdomains = rec {
    foobar = host "203.0.113.2" "4321:0:1:2:3:4:567:89bb";

    ns1 = foobar;
    ns2 = host "203.0.113.3" "4321:0:1:2:3:4:567:89cc";
  };
}

These example assume nsd, but it should be pretty much the same for other daemons.

When your system is defined as a flake:

{

# Add `dns.nix` to `inputs` (see above).
# ...

# In `outputs`:

  nixosConfigurations.yourSystem = nixpkgs.lib.nixosSystem {

    # ...

    services.nsd = {
      enable = true;
      zones = {
        "example.com" = {
          # provideXFR = [ ... ];
          # notify = [ ... ];
          data = dns.lib.toString "example.com" (import ./example.com.nix { inherit dns; });
        };
      };
    };

  };

}

If your system configuration is not a flake, everything will be essentially the same, you will just import it differently.

In modules you develop

dns.lib provides the types attribute, which contains DNS-related types to be used in the NixOS module system. Using them you can define an option in your module such as this one:

# dns = ...

{

yourModule = {
  options = {
    # <...>

    zones = lib.mkOption {
      type = lib.types.attrsOf dns.lib.types.zone;
      description = "DNS zones";
    };
  };

  config = {
    # You can call `toString` on a zone from the `zones` attrset and get
    # a string suitable, for example, for writing with `writeTextFile`.
  };
};

}

As another example, take a look at the evalZone function in dns/default.nix, which takes a name for a zone and a zone definition, defines a “fake” module similar to the one above, and then evaluates it.

dns.utils provides writeZone, which is a helper function that additionally calls toString and writes the resulting string to a file.

Contributing

If you encounter any issues when using this library or have improvement ideas, please open an issue on GitHub.

You are also very welcome to submit pull requests.

Please, note that in this repository we are making effort to track the authorship information for all contributions. In particular, we are following the REUSE practices. The tl;dr is: please, add information about yourself to the headers of each of the files that you edited if your change was substantial (you get to judge what is substantial and what is not).

License

MPL-2.0 © Kirill Elagin and contributors (see headers in the files).

Additionally, all code in this repository is dual-licensed under the MIT license for direct compatibility with nixpkgs.

dns.nix's People

Contributors

aluisioasg avatar davidtwco avatar kirelagin avatar ncfavier avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

dns.nix's Issues

Long DKIM Keys (or other long TXT record) lead to syntax error in bind9

Bind9 has a length limit for TXT records.

When creating e.g. a DKIM record with the following key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0D9oTjw3GT1mQY7oOb9k7oEqxwFzpH3x0+5I3BzPiahiuhUdXgW5pt1KOddwVLzxsKkqTkTACyLRAaJVX4yKq06GeiIKYE8NU1Tt+N4/cUVjLqWQ8q80u8UkLdgrdIlwBb+p079OXogSnpg0N762bqyB1uEADhUNjRP6lQMYfBAzqVJNkUo4ABm+GsWlcPuhOBc0Sp6F5IhvBx/uyyzz46f/50kHOQgYWCblGwglrX9awEEBiMtWtGNBH7iO0DqL4AiJJC8PBvj2kS5sCdZRfCHRBGPczClmvCWf2JA6pL+PFqwtne35KGFIRHOluv3cn6YzQU3jhTaMMOWrgXHcFQIDAQAB

the record is too long and leads to a syntax error in the zone.

This behavior is confirm to RFC 1035 Section 3.3:

<character-string> is treated as binary information, and can be up to 256 characters in length (including the length octet).

A fix is available over here: https://serverfault.com/questions/571720/publishing-long-domain-key-records-in-bind9

Support for unqualified domain names

dns.nix appears to unconditionally append a period to the name component of all records.

This rules out creating records such as

subdomain 60 IN A 1.2.3.4

i.e. ones where the domain name is considered relative to the zone origin.
This is frequently useful, for example to use the same zone file for multiple origins.

Note: I haven't looked at the code too closely, so it's possible that I'm missing something and it's already possible to do this.

Have some tests

Currently we only have example.nix, which incorporates almost all kinds of records, but it looks like it’s time to get an actual test suite, that will not only cover all of the functionality in terms of supported record types, but will also cover some of the logic, which the code is starting to accumulate.

"error: cannot coerce a set to a string" when eval lib.types

Looks like something related to types is wrong, but the laziness of Nix comes to rescue, which made it unnoticed until this issue.

Error message

error: cannot coerce a set to a string

       at /nix/store/lbv7b80mmv8cb7ihq9ksj4bwkdk23biw-source/dns/types/zone.nix:40:22:

           39|       example = [ t.example ];
           40|       description = "List of ${t} records for this zone/subzone";
             |                      ^
           41|     }) rsubtypes';

Steps to reproduce

nix eval .#lib

Environment

  • nix: 2.5pre20211007_844dd90
  • nixpkgs: (flake.lock)

Full trace stack

error: cannot coerce a set to a string

       at /nix/store/lbv7b80mmv8cb7ihq9ksj4bwkdk23biw-source/dns/types/zone.nix:40:22:

           39|       example = [ t.example ];
           40|       description = "List of ${t} records for this zone/subzone";
             |                      ^
           41|     }) rsubtypes';

       … while evaluating the attribute 'description'

       at /nix/store/lbv7b80mmv8cb7ihq9ksj4bwkdk23biw-source/dns/types/zone.nix:40:7:

           39|       example = [ t.example ];
           40|       description = "List of ${t} records for this zone/subzone";
             |       ^
           41|     }) rsubtypes';

       … while evaluating the attribute 'A'

       … while evaluating the attribute 'options'

       at /nix/store/lbv7b80mmv8cb7ihq9ksj4bwkdk23biw-source/dns/types/zone.nix:44:5:

           43|   subzone = types.submodule {
           44|     options = subzoneOptions;
             |     ^
           45|   };

       … while evaluating the attribute 'modules'

       at /nix/store/xj7afnfszb4a883wjxv9xnlpqj352cis-source/lib/types.nix:567:13:

          566|           payload = {
          567|             modules = modules;
             |             ^
          568|             specialArgs = specialArgs;

       … while evaluating the attribute 'payload'

       at /nix/store/xj7afnfszb4a883wjxv9xnlpqj352cis-source/lib/types.nix:566:11:

          565|           type = types.submoduleWith;
          566|           payload = {
             |           ^
          567|             modules = modules;

       … while evaluating the attribute 'functor'

       at /nix/store/xj7afnfszb4a883wjxv9xnlpqj352cis-source/lib/types.nix:156:14:

          155|     { _type = "option-type";
          156|       inherit name check merge emptyValue getSubOptions getSubModules substSubModules typeMerge functor deprecationMessage nestedTypes;
             |              ^
          157|       description = if description == null then name else description;

       … while evaluating the attribute 'subzone'

       at /nix/store/lbv7b80mmv8cb7ihq9ksj4bwkdk23biw-source/dns/types/default.nix:11:47:

           10|   inherit (import ./simple.nix { inherit lib; }) domain-name;
           11|   inherit (import ./zone.nix { inherit lib; }) zone subzone;
             |                                               ^
           12|   record = import ./record.nix { inherit lib; };

       … while evaluating the attribute 'types'

       at /nix/store/lbv7b80mmv8cb7ihq9ksj4bwkdk23biw-source/flake.nix:22:22:

           21|         inherit (dns) combinators;
           22|         inherit (dns) types;
             |                      ^
           23|         toString = name: zone: builtins.toString (dns.evalZone name zone);

provide a hostTTL function

Currently its a little annoying to create a subdomain host with a specific TTL. I would suggest something like the following:

{
  hostTTL = ttl: ipv4: ipv6:
     lib.optionalAttrs (ipv4 != null) { A = [{ address = ipv4; inherit ttl; }]; } //
     lib.optionalAttrs (ipv6 != null) { AAAA = [{ address = ipv6; inherit ttl; }]; };
}

Is there a way to import this module to have it available in all imported files?

Currently i have to put this codeblock in all imported files to have dns.* available:

# https://git.dotya.ml/RiXotStudio/system-management/src/commit/bb1a8104567c95c3502dab31a5854010d7441d7f/services/named/bind.nix#L4
dns = import (pkgs.fetchFromGitHub {
	owner = "kirelagin";
	repo = "dns.nix";
	rev = "v1.1.0";
	sha256 = "0zvg92fjrfmdylk8ic3b2srsrqc8ii94a1ir0v5sknjyxvy5f3rf";
});

Is there a way to define this as a module to have it available in all imported files to avoid duplicate code e.g.

{ config, lib, dns, ... }:
	let
		zoneFile = dns.util.${builtins.currentSystem}.writeZone "${config.networking.fqdn}" (import (../../domains + "/${config.networking.domain}" + /machines + "/${config.networking.hostName}" + /zones/main.nix) { inherit dns; inherit config; });
	in {
		networking.firewall.allowedTCPPorts = lib.optional config.services.bind.enable 53;

		services.bind = {
			# FIXME(Krey): Research as tor's DNS is only resolving A and AAAA ?
			# forwarders = if(config.services.tor.client.dns.enable == true )
			# 	then [
			# 		# BUG(Krey): https://github.com/NixOS/nixpkgs/issues/129714
			# 		"127.0.0.1 port 9053"
			# 	]
			# 	else [ ];
			zones = {
				"${config.networking.fqdn}" = {
					file = zoneFile;
					master = true;
					masters = [ "${config.networking.interfaces.enp7s0.ipv4.addresses}" ];
				};
			};
		};
	}

Notice the { config, lib, dns, ... }:

Context

I am trying to solve this for 4 days while none in matrix channel for nixOS knows how to do this without causing an infinite recursion as highlighted in NixOS/nix#5007

writeZone does not work when importing as flake

writeZone relies on pkgs.writeTextFile, which is only available when the full nixpkgs is instantiated (import "${nixpkgs}" { system = "some-system"; }). It's simple enough a wrapper that I can just call writeTextFile myself, but I guess it should be documented somewhere along with writeZone itself.

Provide more detailed examples

I think it would not be a bad idea to have detailed examples of various options for integrating the zones into NixOS configurations of various styles (e.g. #19).

DMARC: add support for external destination verification

See DMARC specification, Section 7.1.

We should provide a combinator that will allow one to create a record that says “I am ok receiving reports from ” and optionally override the destination. Don’t forget support for wildcards.

This is going to be a rather simple TXT record.

Difficult to use ACME/Let's Encrypt with dns.nix

First off this library is great, definitely a much nicer way to define DNS records, and I'd be thrilled if it was in core NixOS.

I see a mention of Let's Encrypt in the library; do you use DNS-based authentication with Let's Encrypt? I'm finding it rather difficult to set up with nsd and dns.nix, and if you do use DNS-based authentication, it would be useful to have an example of it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.