summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLucas Christian <lucas@lucasec.com>2021-10-07 20:41:02 -0700
committerLucas Christian <lucas@lucasec.com>2021-10-12 22:33:38 -0700
commit36e5f07f8dda51cc5bb0a105077e751f1c851435 (patch)
tree0c02c0746360525f73ceeec30d06905bbb9f4e04
parent044e9dc8bc7e3d946b0ba1f1edfe06b5323aeadd (diff)
downloadvyos-1x-36e5f07f8dda51cc5bb0a105077e751f1c851435.tar.gz
vyos-1x-36e5f07f8dda51cc5bb0a105077e751f1c851435.zip
T562: Config syntax for defining DNS forward authoritative zones
-rw-r--r--data/templates/dns-forwarding/recursor.conf.tmpl3
-rw-r--r--data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl6
-rw-r--r--data/templates/dns-forwarding/recursor.zone.conf.tmpl7
-rw-r--r--interface-definitions/dns-forwarding.xml.in452
-rw-r--r--interface-definitions/include/dns/time-to-live.xml.i15
-rw-r--r--python/vyos/hostsd_client.py12
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py206
-rwxr-xr-xsrc/services/vyos-hostsd36
8 files changed, 730 insertions, 7 deletions
diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl
index d44f756e8..02efe903b 100644
--- a/data/templates/dns-forwarding/recursor.conf.tmpl
+++ b/data/templates/dns-forwarding/recursor.conf.tmpl
@@ -31,5 +31,8 @@ dnssec={{ dnssec }}
# serve rfc1918 records
serve-rfc1918={{ 'no' if no_serve_rfc1918 is defined else 'yes' }}
+# zones
+auth-zones={% for z in authoritative_zones %}{{ z.name }}={{ z.file }}{{- "," if not loop.last -}}{% endfor %}
+
forward-zones-file=recursor.forward-zones.conf
diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
index 784d5c360..7f29c387e 100644
--- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
+++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
@@ -22,3 +22,9 @@ addNTA("{{ zone }}", "static")
{% endfor %}
{% endif %}
+{% if authoritative_zones is defined %}
+-- from 'service dns forwarding authoritative-domain'
+{% for zone in authoritative_zones %}
+addNTA("{{ zone }}", "static")
+{% endfor %}
+{% endif %}
diff --git a/data/templates/dns-forwarding/recursor.zone.conf.tmpl b/data/templates/dns-forwarding/recursor.zone.conf.tmpl
new file mode 100644
index 000000000..758871bef
--- /dev/null
+++ b/data/templates/dns-forwarding/recursor.zone.conf.tmpl
@@ -0,0 +1,7 @@
+;
+; Autogenerated by dns_forwarding.py
+;
+;
+{% for r in records %}
+{{ r.name }} {{ r.ttl }} {{ r.type }} {{ r.value }}
+{% endfor %}
diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in
index 5b0c87597..ced138bff 100644
--- a/interface-definitions/dns-forwarding.xml.in
+++ b/interface-definitions/dns-forwarding.xml.in
@@ -105,6 +105,456 @@
</leafNode>
</children>
</tagNode>
+ <tagNode name="authoritative-domain">
+ <properties>
+ <help>Domain to host authoritative records for</help>
+ <valueHelp>
+ <format>text</format>
+ <description>An absolute DNS name</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[-_a-zA-Z0-9.]{1,63}$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <node name="records">
+ <properties>
+ <help>DNS zone records</help>
+ </properties>
+ <children>
+ <tagNode name="a">
+ <properties>
+ <help>"A" record</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IPv4 address [REQUIRED]</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <multi/>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="aaaa">
+ <properties>
+ <help>"AAAA" record</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IPv6 address [REQUIRED]</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <multi/>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="cname">
+ <properties>
+ <help>"CNAME" record</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="target">
+ <properties>
+ <help>Target DNS name [REQUIRED]</help>
+ <valueHelp>
+ <format>name.example.com</format>
+ <description>An absolute DNS name</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[-_a-zA-Z0-9.]{1,63}(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="mx">
+ <properties>
+ <help>"MX" record</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <tagNode name="server">
+ <properties>
+ <help>Mail server [REQUIRED]</help>
+ <valueHelp>
+ <format>name.example.com</format>
+ <description>An absolute DNS name</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[-_a-zA-Z0-9.]{1,63}(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="priority">
+ <properties>
+ <help>Server priority</help>
+ <valueHelp>
+ <format>u32:1-999</format>
+ <description>Server priority (lower numbers are higher priority)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="ptr">
+ <properties>
+ <help>"PTR" record</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="target">
+ <properties>
+ <help>Target DNS name [REQUIRED]</help>
+ <valueHelp>
+ <format>name.example.com</format>
+ <description>An absolute DNS name</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[-_a-zA-Z0-9.]{1,63}(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="txt">
+ <properties>
+ <help>"TXT" record</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="value">
+ <properties>
+ <help>Record contents [REQUIRED]</help>
+ <valueHelp>
+ <format>text</format>
+ <description>Record contents</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="spf">
+ <properties>
+ <help>"SPF" record (type=SPF)</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="value">
+ <properties>
+ <help>Record contents [REQUIRED]</help>
+ <valueHelp>
+ <format>text</format>
+ <description>Record contents</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="srv">
+ <properties>
+ <help>Host an DNS "SRV" record</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <tagNode name="entry">
+ <properties>
+ <help>Service entry [REQUIRED]</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Entry number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="hostname">
+ <properties>
+ <help>Server hostname [REQUIRED]</help>
+ <valueHelp>
+ <format>name.example.com</format>
+ <description>An absolute DNS name</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[-_a-zA-Z0-9.]{1,63}(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Port number [REQUIRED]</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>TCP/UDP port number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65536"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="priority">
+ <properties>
+ <help>Entry priority</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Entry priority (lower numbers are higher priority)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>10</defaultValue>
+ </leafNode>
+ <leafNode name="weight">
+ <properties>
+ <help>Entry weight</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Entry weight</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>0</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ <tagNode name="naptr">
+ <properties>
+ <help>"NAPTR" record</help>
+ <valueHelp>
+ <format>text</format>
+ <description>A DNS name relative to the root record</description>
+ </valueHelp>
+ <valueHelp>
+ <format>@</format>
+ <description>Root record</description>
+ </valueHelp>
+ <constraint>
+ <regex>^([-_a-zA-Z0-9.]{1,63}|@)(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>NAPTR rule [REQUIRED]</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Rule number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="order">
+ <properties>
+ <help>Rule order</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Rule order (lower order is evaluated first)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="preference">
+ <properties>
+ <help>Rule preference</help>
+ <valueHelp>
+ <format>u32:0-65535</format>
+ <description>Rule preference</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>0</defaultValue>
+ </leafNode>
+ <leafNode name="lookup-srv">
+ <properties>
+ <help>"S" flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="lookup-a">
+ <properties>
+ <help>"A" flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="resolve-uri">
+ <properties>
+ <help>"U" flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="protocol-specific">
+ <properties>
+ <help>"P" flag</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="service">
+ <properties>
+ <help>Service type</help>
+ <constraint>
+ <regex>^[a-zA-Z][a-zA-Z0-9]{0,31}(\+[a-zA-Z][a-zA-Z0-9]{0,31})?$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="regexp">
+ <properties>
+ <help>Regular expression</help>
+ </properties>
+ </leafNode>
+ <leafNode name="replacement">
+ <properties>
+ <help>Replacement DNS name</help>
+ <valueHelp>
+ <format>name.example.com</format>
+ <description>An absolute DNS name</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[-_a-zA-Z0-9.]{1,63}(?&lt;!\.)$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ #include <include/dns/time-to-live.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ #include <include/generic-disable-node.xml.i>
+ </children>
+ </tagNode>
<leafNode name="ignore-hosts-file">
<properties>
<help>Do not use local /etc/hosts file in name resolution</help>
@@ -114,7 +564,7 @@
<leafNode name="no-serve-rfc1918">
<properties>
<help>Makes the server authoritatively not aware of RFC1918 addresses</help>
- <valueless/>
+ <valueless/>
</properties>
</leafNode>
<leafNode name="allow-from">
diff --git a/interface-definitions/include/dns/time-to-live.xml.i b/interface-definitions/include/dns/time-to-live.xml.i
new file mode 100644
index 000000000..5c1a1472d
--- /dev/null
+++ b/interface-definitions/include/dns/time-to-live.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from dns/time-to-live.xml.i -->
+<leafNode name="ttl">
+ <properties>
+ <help>Time-to-live (TTL)</help>
+ <valueHelp>
+ <format>u32:0-2147483647</format>
+ <description>TTL in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-2147483647"/>
+ </constraint>
+ </properties>
+ <defaultValue>300</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/python/vyos/hostsd_client.py b/python/vyos/hostsd_client.py
index 303b6ea47..f31ef51cf 100644
--- a/python/vyos/hostsd_client.py
+++ b/python/vyos/hostsd_client.py
@@ -79,6 +79,18 @@ class Client(object):
msg = {'type': 'forward_zones', 'op': 'get'}
return self._communicate(msg)
+ def add_authoritative_zones(self, data):
+ msg = {'type': 'authoritative_zones', 'op': 'add', 'data': data}
+ self._communicate(msg)
+
+ def delete_authoritative_zones(self, data):
+ msg = {'type': 'authoritative_zones', 'op': 'delete', 'data': data}
+ self._communicate(msg)
+
+ def get_authoritative_zones(self):
+ msg = {'type': 'authoritative_zones', 'op': 'get'}
+ return self._communicate(msg)
+
def add_search_domains(self, data):
msg = {'type': 'search_domains', 'op': 'add', 'data': data}
self._communicate(msg)
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 06366362a..c2816589c 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -17,6 +17,7 @@
import os
from sys import exit
+from glob import glob
from vyos.config import Config
from vyos.configdict import dict_merge
@@ -50,10 +51,12 @@ def get_config(config=None):
if not conf.exists(base):
return None
- dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
# We have gathered the dict representation of the CLI, but there are default
- # options which we need to update into the dictionary retrived.
+ # options which we need to update into the dictionary retrieved.
default_values = defaults(base)
+ # T2665 due to how defaults under tag nodes work, we must clear these out before we merge
+ del default_values['authoritative_domain']
dns = dict_merge(default_values, dns)
# some additions to the default dictionary
@@ -66,6 +69,183 @@ def get_config(config=None):
if conf.exists(base_nameservers_dhcp):
dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)})
+ if 'authoritative_domain' in dns:
+ dns['authoritative_zones'] = []
+ dns['authoritative_zone_errors'] = []
+ for node in dns['authoritative_domain']:
+ zonedata = dns['authoritative_domain'][node]
+ if ('disable' in zonedata) || (not 'records' in zonedata):
+ continue
+ zone = {
+ 'name': node,
+ 'file': "{}/zone.{}.conf".format(pdns_rec_run_dir, node),
+ 'records': [],
+ }
+
+ recorddata = zonedata['records']
+
+ for rtype in [ 'a', 'aaaa', 'cname', 'mx', 'ptr', 'txt', 'spf', 'srv', 'naptr' ]:
+ if rtype not in recorddata:
+ continue
+ for subnode in recorddata[rtype]:
+ if 'disable' in recorddata[rtype][subnode]:
+ continue
+
+ rdata = recorddata[rtype][subnode]
+
+ if rtype in [ 'a', 'aaaa' ]:
+ rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665
+ rdata = dict_merge(rdefaults, rdata)
+
+ if not 'address' in rdata:
+ dns['authoritative_zone_errors'].append('{}.{}: at least one address is required'.format(subnode, node))
+ continue
+
+ for address in rdata['address']:
+ zone['records'].append({
+ 'name': subnode,
+ 'type': rtype.upper(),
+ 'ttl': rdata['ttl'],
+ 'value': address
+ })
+ elif rtype in ['cname', 'ptr']:
+ rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665
+ rdata = dict_merge(rdefaults, rdata)
+
+ if not 'target' in rdata:
+ dns['authoritative_zone_errors'].append('{}.{}: target is required'.format(subnode, node))
+ continue
+
+ zone['records'].append({
+ 'name': subnode,
+ 'type': rtype.upper(),
+ 'ttl': rdata['ttl'],
+ 'value': '{}.'.format(rdata['target'])
+ })
+ elif rtype == 'mx':
+ rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665
+ del rdefaults['server']
+ rdata = dict_merge(rdefaults, rdata)
+
+ if not 'server' in rdata:
+ dns['authoritative_zone_errors'].append('{}.{}: at least one server is required'.format(subnode, node))
+ continue
+
+ for servername in rdata['server']:
+ serverdata = rdata['server'][servername]
+ serverdefaults = defaults(base + ['authoritative-domain', rtype, 'server']) # T2665
+ serverdata = dict_merge(serverdefaults, serverdata)
+ zone['records'].append({
+ 'name': subnode,
+ 'type': rtype.upper(),
+ 'ttl': rdata['ttl'],
+ 'value': '{} {}.'.format(serverdata['priority'], servername)
+ })
+ elif rtype == 'txt':
+ rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665
+ rdata = dict_merge(rdefaults, rdata)
+
+ if not 'value' in rdata:
+ dns['authoritative_zone_errors'].append('{}.{}: at least one value is required'.format(subnode, node))
+ continue
+
+ for value in rdata['value']:
+ zone['records'].append({
+ 'name': subnode,
+ 'type': rtype.upper(),
+ 'ttl': rdata['ttl'],
+ 'value': "\"{}\"".format(value.replace("\"", "\\\""))
+ })
+ elif rtype == 'spf':
+ rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665
+ rdata = dict_merge(rdefaults, rdata)
+
+ if not 'value' in rdata:
+ dns['authoritative_zone_errors'].append('{}.{}: value is required'.format(subnode, node))
+ continue
+
+ zone['records'].append({
+ 'name': subnode,
+ 'type': rtype.upper(),
+ 'ttl': rdata['ttl'],
+ 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\""))
+ })
+ elif rtype == 'srv':
+ rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665
+ del rdefaults['entry']
+ rdata = dict_merge(rdefaults, rdata)
+
+ if not 'entry' in rdata:
+ dns['authoritative_zone_errors'].append('{}.{}: at least one entry is required'.format(subnode, node))
+ continue
+
+ for entryno in rdata['entry']:
+ entrydata = rdata['entry'][entryno]
+ entrydefaults = defaults(base + ['authoritative-domain', rtype, 'entry']) # T2665
+ entrydata = dict_merge(entrydefaults, entrydata)
+
+ if not 'hostname' in entrydata:
+ dns['authoritative_zone_errors'].append('{}.{}: hostname is required for entry {}'.format(subnode, node, entryno))
+ continue
+
+ if not 'port' in entrydata:
+ dns['authoritative_zone_errors'].append('{}.{}: port is required for entry {}'.format(subnode, node, entryno))
+ continue
+
+ zone['records'].append({
+ 'name': subnode,
+ 'type': rtype.upper(),
+ 'ttl': rdata['ttl'],
+ 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname'])
+ })
+ elif rtype == 'naptr':
+ rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665
+ del rdefaults['rule']
+ rdata = dict_merge(rdefaults, rdata)
+
+
+ if not 'rule' in rdata:
+ dns['authoritative_zone_errors'].append('{}.{}: at least one rule is required'.format(subnode, node))
+ continue
+
+ for ruleno in rdata['rule']:
+ ruledata = rdata['rule'][ruleno]
+ ruledefaults = defaults(base + ['authoritative-domain', rtype, 'rule']) # T2665
+ ruledata = dict_merge(ruledefaults, ruledata)
+ flags = ""
+ if 'lookup-srv' in ruledata:
+ flags += "S"
+ if 'lookup-a' in ruledata:
+ flags += "A"
+ if 'resolve-uri' in ruledata:
+ flags += "U"
+ if 'protocol-specific' in ruledata:
+ flags += "P"
+
+ if 'order' in ruledata:
+ order = ruledata['order']
+ else:
+ order = ruleno
+
+ if 'regexp' in ruledata:
+ regexp= ruledata['regexp'].replace("\"", "\\\"")
+ else:
+ regexp = ''
+
+ if ruledata['replacement']:
+ replacement = '{}.'.format(ruledata['replacement'])
+ else:
+ replacement = ''
+
+ zone['records'].append({
+ 'name': subnode,
+ 'type': rtype.upper(),
+ 'ttl': rdata['ttl'],
+ 'value': '{} {} "{}" "{}" "{}" {}'.format(order, ruledata['preference'], flags, ruledata['service'], regexp, replacement)
+ })
+
+ dns['authoritative_zones'].append(zone)
+
return dns
def verify(dns):
@@ -86,6 +266,11 @@ def verify(dns):
if 'server' not in dns['domain'][domain]:
raise ConfigError(f'No server configured for domain {domain}!')
+ if dns['authoritative_zone_errors']:
+ for error in dns['authoritative_zone_errors']:
+ print(error)
+ raise ConfigError('Invalid authoritative records have been defined')
+
if 'system' in dns:
if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns):
print("Warning: No 'system name-server' or 'system " \
@@ -104,6 +289,14 @@ def generate(dns):
render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl',
dns, user=pdns_rec_user, group=pdns_rec_group)
+ for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'):
+ os.unlink(zone_filename)
+
+ for zone in dns['authoritative_zones']:
+ render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl',
+ zone, user=pdns_rec_user, group=pdns_rec_group)
+
+
# if vyos-hostsd didn't create its files yet, create them (empty)
for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]:
with open(file, 'a'):
@@ -119,6 +312,9 @@ def apply(dns):
if os.path.isfile(pdns_rec_config_file):
os.unlink(pdns_rec_config_file)
+
+ for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'):
+ os.unlink(zone_filename)
else:
### first apply vyos-hostsd config
hc = hostsd_client()
@@ -153,6 +349,12 @@ def apply(dns):
if 'domain' in dns:
hc.add_forward_zones(dns['domain'])
+ # hostsd generates NTAs for the authoritative zones
+ # the list and keys() are required as get returns a dict, not list
+ hc.delete_authoritative_zones(list(hc.get_authoritative_zones()))
+ if 'authoritative_domain' in dns:
+ hc.add_authoritative_zones(list(dns['authoritative_domain'].keys()))
+
# call hostsd to generate forward-zones and its lua-config-file
hc.apply()
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index 4c4bb036e..52753faa5 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -139,6 +139,27 @@
# }
#
#
+### authoritative_zones
+## Additional zones hosted authoritatively by pdns-recursor.
+## We add NTAs for these zones but do not do much else here.
+#
+# { 'type': 'authoritative_zones',
+# 'op': 'add',
+# 'data': ['<str zone>', ...]
+# }
+#
+# { 'type': 'authoritative_zones',
+# 'op': 'delete',
+# 'data': ['<str zone>', ...]
+# }
+#
+# { 'type': 'authoritative_zones',
+# 'op': 'get',
+# }
+# response:
+# { 'data': ['<str zone>', ...] }
+#
+#
### search_domains
#
# { 'type': 'search_domains',
@@ -255,6 +276,7 @@ STATE = {
"name_server_tags_recursor": [],
"name_server_tags_system": [],
"forward_zones": {},
+ "authoritative_zones": [],
"hosts": {},
"host_name": "vyos",
"domain_name": "",
@@ -267,7 +289,8 @@ base_schema = Schema({
Required('op'): Any('add', 'delete', 'set', 'get', 'apply'),
'type': Any('name_servers',
'name_server_tags_recursor', 'name_server_tags_system',
- 'forward_zones', 'search_domains', 'hosts', 'host_name'),
+ 'forward_zones', 'authoritative_zones' 'search_domains',
+ 'hosts', 'host_name'),
'data': Any(list, dict),
'tag': str,
'tag_regex': str
@@ -347,6 +370,11 @@ msg_schema_map = {
'delete': data_list_schema,
'get': op_type_schema
},
+ 'authoritative_zones': {
+ 'add': data_list_schema,
+ 'delete': data_list_schema,
+ 'get': op_type_schema
+ },
'search_domains': {
'add': data_dict_list_schema,
'delete': data_list_schema,
@@ -522,7 +550,7 @@ def handle_message(msg):
data = get_option(msg, 'data')
if _type in ['name_servers', 'forward_zones', 'search_domains', 'hosts']:
delete_items_from_dict(STATE[_type], data)
- elif _type in ['name_server_tags_recursor', 'name_server_tags_system']:
+ elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'authoritative_zones']:
delete_items_from_list(STATE[_type], data)
else:
raise ValueError(f'Operation "{op}" unknown data type "{_type}"')
@@ -534,7 +562,7 @@ def handle_message(msg):
elif _type in ['forward_zones', 'hosts']:
add_items_to_dict(STATE[_type], data)
# maybe we need to rec_control clear-nta each domain that was removed here?
- elif _type in ['name_server_tags_recursor', 'name_server_tags_system']:
+ elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'authoritative_zones']:
add_items_to_list(STATE[_type], data)
else:
raise ValueError(f'Operation "{op}" unknown data type "{_type}"')
@@ -550,7 +578,7 @@ def handle_message(msg):
if _type in ['name_servers', 'search_domains', 'hosts']:
tag_regex = get_option(msg, 'tag_regex')
result = get_items_from_dict_regex(STATE[_type], tag_regex)
- elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones']:
+ elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones', 'authoritative_zones']:
result = STATE[_type]
else:
raise ValueError(f'Operation "{op}" unknown data type "{_type}"')