Package: dnstemple
DNS Temple • A DNS TEMPLating EngineMaintaining several domains ("zones") can be a hassle. Often, a lot of basic
information is shared between domains (name, mail or web servers, anti-spam
configuration etc.).CNAMEs can help to keep information in a single location,
but are not compatible e.g. with information stored at the apex (the domain name
itelf), and thus are of limited or no use for the above-mentioned shared
information. When creating a new zone, a previous zone can be copied; however,
if later things have to change for these domains, a lot of manual labor is
required, prone to errors. DNS Temple tries to simplify this.Motivating exampleTraditional setupMain site:$ORIGIN		example.com.
$TTL		86400
@		SOA	(ns1.example.com. hostmaster.example.com. 2020121901 3600 1800 2419200 300)
		NS	ns1.example.com.
		NS	ns2.example.com.
		MX	10 mail.example.com.
		TXT	"v=spf1 mx ~all"
		TXT	"google-site-verification=zeePiegahyeiVoh4SheiK5ootees2Uy4DaiKawoh"
		A	192.0.2.80
		AAAA	2001:db8:1234:5678::80
		CAA	0 issue "letsencrypt.org"
ns1		A	203.0.113.53
		AAAA	2001:db8:1:2::53
ns2		A	198.51.100.53
		AAAA	2001:db8:3:4::53
# Web
www		A	192.0.2.80
		AAAA	2001:db8:1234:5678::80
# Mail
autodiscover	CNAME	mail
autoconfig	CNAME	mail
mail		A	192.0.2.25
		AAAA	2001:db8:1234:5678::25
smtp		CNAME	mail
imap		CNAME	mail
dkim._domainkey	TXT	"v=DKIM1;k=rsa;t=s;s=email;p=MII..."
_dmarc		TXT	"v=DMARC1; p=none; rua=mailto:dkim@example.com; fo=1:d:s"
# Cloud
cloud		A	192.0.2.90
		AAAA	2001:db8:1234:5678::90One of many secondary sites:$ORIGIN		example.ch.
$TTL		86400
@		SOA	(ns1.example.com. hostmaster.example.com. 2020121901 3600 1800 2419200 300)
		NS	ns1.example.com.
		NS	ns2.example.com.
		MX	10 mail.example.com.
		TXT	"v=spf1 mx ~all"
		TXT	"google-site-verification=isei8oox1gahc7oox1ezith9eith2ki8aigh9aiD"
		A	192.0.2.80
		AAAA	2001:db8:1234:5678::80
		CAA	0 issue "letsencrypt.org"
# Web
www		A	192.0.2.80
		AAAA	2001:db8:1234:5678::80
# Mail
autodiscover	CNAME	mail
autoconfig	CNAME	mail
mail		A	192.0.2.25
		AAAA	2001:db8:1234:5678::25
smtp		CNAME	mail
imap		CNAME	mail
dkim._domainkey	TXT	"v=DKIM1;k=rsa;t=s;s=email;p=MII..."
_dmarc		TXT	"v=DMARC1; p=none; rua=mailto:dkim@example.com; fo=1:d:s"Themail,_dkim, andwwwentries could be changed toCNAMEs and the SPF
entry could useinclude:, but many problems would still remain, such as:Changing the address of the web server would require changing all the files
anyway.Changing the list of name servers, certificate authorities, or mail servers
cause modification of every file.SOAserials need to be incremented carefully to avoid hard-to-diagnose
problems.Adding an additional service, such as a secondary mail server, CardDAV,
CalDAV, or XMPP would require touching every file.Over time, files will diverge, due to some services (such as cloud) appearing
in some zones only or authentication entries (such asgoogle-site-verification, maybe_domainkey) differing between domains.You will lose oversight because of all the clutter and differences.Configuration with DNS TempleCreate a central configuration where shared information is collected:addresses:ns1:203.0.113.53  2001:db8:1:2::53ns2:198.51.100.53 2001:db8:3:4::53mail:192.0.2.25    2001:db8:1234:5678::25web:192.0.2.80    2001:db8:1234:5678::80cloud:192.0.2.90    2001:db8:1234:5678::90variables:ca:letsencrypt.orgdkimkey:|"MII..."Create a set of templates using{variable}references,$ADDRESSexplosions
(expanding dynamically to a set ofAandAAAArecords),$INCLUDEs, and$DEFAULTvalues for some variables using magic{_domain}and{_serial}variables:header.t:$ORIGIN		{_domain}
$TTL		86400
@		SOA	(ns1.example.com. hostmaster.example.com. {_serial} 3600 1800 2419200 300)
		NS	ns1.example.com.
		NS	ns2.example.com.
		CAA	0 issue "{ca}"mail.t:$DEFAULT			domain	{_domain}.
$ADDRESS			mail	mail

{domain}			MX	10 mail.example.com.
				TXT	"v=spf1 mx ~all"
autodiscover.{domain}		CNAME	mail
autoconfig.{domain}		CNAME	mail
smtp.{domain}			CNAME	mail
imap.{domain}			CNAME	mail
dkim._domainkey.{domain}	TXT	"v=DKIM1;k=rsa;t=s;s=email;p={dkimkey}"
_dmarc.{domain}			TXT	"v=DMARC1; p=none; rua=mailto:dkim@example.com; fo=1:d:s"web.t:$ADDRESS	web	@
$ADDRESS	web	wwwcommon.t:$INCLUDE	header.t
$INCLUDE	mail.t
$INCLUDE	web.tThese configuration templates will be shared among the zones, resulting in much
more compact files:$INCLUDE	common.t
$INCLUDE	mail.t	domain=noreply
$ADDRESS	ns1	ns1
$ADDRESS	ns2	ns2
$ADDRESS	cloud	cloud
@		TXT	"google-site-verification=zeePiegahyeiVoh4SheiK5ootees2Uy4DaiKawoh"$INCLUDE	common.t
$ADDRESS	ns1	ns1
$ADDRESS	ns2	ns2
$ADDRESS	cloud	cloud
@		TXT	"google-site-verification=isei8oox1gahc7oox1ezith9eith2ki8aigh9aiD"SyntaxThe BIND syntax is augmented to achieve the power of variables, modules, and
functions. The changes, however, are minimal, to retain familiarity.Behind the scenes,scoped variablesandinclude parametersdo all the work:
Each$INCLUDEcreates a new context, initialized with the variables of the
parent file and the parameters passed in the$INCLUDEstatement.The new or extended statements are:$DEFAULT: Setting default values for variables,$INCLUDE: Create a child context and include the file, and$ADDRESS: Simplify the handling of addresses, whether IPv4, IPv6 or groups
thereof.Configuration fileThe configuration file is structured as follows:config:serial:<`online`|`dateserial`|`unixtime`|any integer>skip:<comma-separated list of `empty`, `comment`>extensions:in:<extension on input file names>out:<extension for output file names>addresses:<name1>:<addresses1…><name2>:<addresses2…>...variables:<name1>:<value1><name2>:<value2>...skipsuppresses the output of empty and/or comment lines (startingwith;).Theinextension is removed from the input file names, if present, and theoutextension is appended. The basename is also used to set the_domainvariable, below.Serial number modes are as follows:online: Queries the name servers and increments; but uses at least the value
thatdateserialwould produce. For this, existingSOAserials are obtained
by querying the local resolver and an authoritative name server. Highest
priority for authoritative name server is given to themaster name(MNAME)
extracted from theSOArecord returned by the local resolver, with the
remainingNSentries as fallbacks. The minimum SOA corresponds to whatdate -u +%Y%m%d00would return.dateserial: Uses theYYYYMMDD00formatunixtime(default): Uses the current second since start of the epochAn integer, e.g.1: Use this integer as the serial numberVariablesVariable names in curly brackets ({}) are expanded directly when reading the
line from the input file (the entire Pythonformat()specification is
supported). Only after that, lines are parsed; i.e., whitespace or quotes have
no special significance todnstempleand cannot be escaped (at least as of
now).The following special variables are automatically set:_config: The file name the YAML configuration has been read from._domain: The domain name, as determined by the basename of the top-level
file, i.e., the file specified on the command line, after removing theinextension._serial: A serial number usable for theSOArecord.All variable names starting with_are reserved.AddressesOne of the important reasons for usingdnstempleis to make addressing more
dynamic. This is similar to a variable, but with extra possibilities:Be able to change addresses in multiple zone files, even ifCNAMEs are not
possible (e.g., at the apex)Reduce inter-zone dependencies: Have the same address in multiple zones,
without causing multiple lookups (i.e., follow theCNAME) when resolving or
requiring the other zone to be ready.Handle IPv4 and IPv6 addresses using the same syntax (no deciding betweenAandAAAA)Support multiple addresses for a name (IPv4+IPv6, but also multiple of each)
without having to add/drop lines in the zone fileAddresses are defined similar to variables, but with the following distinctions:They are defined in theaddressblockThey are used with the$ADDRESSdirective, which expands them into any
number ofAandAAAArecords, as appropriateThey can be defined recursively, e.g. for fail-over/load-balancing setups:addresses:server1:192.0.2.11 2001:db8:1234:5678::11server2:192.0.2.12farm:server1 server2There is no need to use variable notation. Anything not an IPv4 or IPv6
address will be recursively looked up as an address, up to 5 levels deep.Of course, variables could also be included using the standard{}variable
notation, but then the reference is to thevariablessection.Directives$INCLUDE <file> [<var>=<value>…]Include the given file. The set of variables available to the included file is
the set of variables available to the parent, plus the variables passed as
parameters. This is similar to how parameters would be passed to functions.Separation is done at all whitespace, after variable substitution, without
consideration of quotes or escape characters. So do not try to include
whitespace into variable values.$ADDRESS <address> <prefix>Create one line for each of the addresses listed for the named address parameter
specified in the configuration file. Both IPv4 and IPv6 addresses can be mixed
arbitrarily and will be prefixed withAorAAAA, as appropriate. Typically,
the prefix will just be a name, but can also contain TTL information; in fact,
anything that could precede theAorAAAA.$DEFAULT <variable> <value>If the specified variable is undefined or empty, set it to the value given.