summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Kosek <mkosek@redhat.com>2012-02-24 09:30:39 +0100
committerMartin Kosek <mkosek@redhat.com>2012-02-24 09:40:43 +0100
commit860579022532ee4133fc74e8f916cb40dc3ea239 (patch)
tree475fa305e89561b10fcd3523d34acd7e8b981f5a
parent2cf58937615c28527d1c78f883dad8726331c6df (diff)
downloadfreeipa-860579022532ee4133fc74e8f916cb40dc3ea239.tar.gz
freeipa-860579022532ee4133fc74e8f916cb40dc3ea239.tar.xz
freeipa-860579022532ee4133fc74e8f916cb40dc3ea239.zip
Query and transfer ACLs for DNS zones
Provide a way to specify BIND allow-query and allow-transfer ACLs for DNS zones. IMPORTANT: new bind-dyndb-ldap adds a zone transfer ability. To avoid zone information leaks to unintended places, allow-transfer ACL for every zone is by default set to none and has to be explicitly enabled by an Administrator. This is done both for new DNS zones and old DNS zones during RPM update via new DNS upgrade plugin. https://fedorahosted.org/freeipa/ticket/1211
-rw-r--r--API.txt12
-rw-r--r--ipalib/plugins/dns.py87
-rw-r--r--ipapython/ipautil.py19
-rw-r--r--ipaserver/install/bindinstance.py8
-rw-r--r--ipaserver/install/plugins/Makefile.am1
-rw-r--r--ipaserver/install/plugins/dns.py65
-rw-r--r--tests/test_xmlrpc/test_dns_plugin.py86
7 files changed, 264 insertions, 14 deletions
diff --git a/API.txt b/API.txt
index 9942e630f..d57e1ba1b 100644
--- a/API.txt
+++ b/API.txt
@@ -1067,7 +1067,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('value', <type 'unicode'>, None)
command: dnszone_add
-args: 1,19,3
+args: 1,21,3
arg: Str('idnsname', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=True)
option: Str('name_from_ip', attribute=False, cli_name='name_from_ip', multivalue=False, required=False)
option: Str('idnssoamname', attribute=True, cli_name='name_server', multivalue=False, required=True)
@@ -1081,6 +1081,8 @@ option: Int('dnsttl', attribute=True, cli_name='ttl', multivalue=False, required
option: StrEnum('dnsclass', attribute=True, cli_name='class', multivalue=False, required=False, values=(u'IN', u'CS', u'CH', u'HS'))
option: Str('idnsupdatepolicy', attribute=True, cli_name='update_policy', multivalue=False, required=False)
option: Bool('idnsallowdynupdate', attribute=True, autofill=True, cli_name='dynamic_update', default=False, multivalue=False, required=False)
+option: Str('idnsallowquery', attribute=True, autofill=True, cli_name='allow_query', default=u'any;', multivalue=False, required=False)
+option: Str('idnsallowtransfer', attribute=True, autofill=True, cli_name='allow_transfer', default=u'none;', multivalue=False, required=False)
option: Str('setattr*', cli_name='setattr', exclude='webui')
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Flag('force', autofill=True, default=False)
@@ -1111,7 +1113,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Output('result', <type 'bool'>, None)
output: Output('value', <type 'unicode'>, None)
command: dnszone_find
-args: 1,21,4
+args: 1,23,4
arg: Str('criteria?', noextrawhitespace=False)
option: Str('idnsname', attribute=True, autofill=False, cli_name='name', multivalue=False, primary_key=True, query=True, required=False)
option: Str('name_from_ip', attribute=False, autofill=False, cli_name='name_from_ip', multivalue=False, query=True, required=False)
@@ -1127,6 +1129,8 @@ option: StrEnum('dnsclass', attribute=True, autofill=False, cli_name='class', mu
option: Str('idnsupdatepolicy', attribute=True, autofill=False, cli_name='update_policy', multivalue=False, query=True, required=False)
option: Bool('idnszoneactive', attribute=True, autofill=False, cli_name='zone_active', multivalue=False, query=True, required=False)
option: Bool('idnsallowdynupdate', attribute=True, autofill=False, cli_name='dynamic_update', default=False, multivalue=False, query=True, required=False)
+option: Str('idnsallowquery', attribute=True, autofill=False, cli_name='allow_query', default=u'any;', multivalue=False, query=True, required=False)
+option: Str('idnsallowtransfer', attribute=True, autofill=False, cli_name='allow_transfer', default=u'none;', multivalue=False, query=True, required=False)
option: Int('timelimit?', autofill=False, minvalue=0)
option: Int('sizelimit?', autofill=False, minvalue=0)
option: Flag('forward_only', autofill=True, cli_name='forward_only', default=False)
@@ -1139,7 +1143,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
output: Output('count', <type 'int'>, None)
output: Output('truncated', <type 'bool'>, None)
command: dnszone_mod
-args: 1,19,3
+args: 1,21,3
arg: Str('idnsname', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
option: Str('name_from_ip', attribute=False, autofill=False, cli_name='name_from_ip', multivalue=False, required=False)
option: Str('idnssoamname', attribute=True, autofill=False, cli_name='name_server', multivalue=False, required=False)
@@ -1153,6 +1157,8 @@ option: Int('dnsttl', attribute=True, autofill=False, cli_name='ttl', multivalue
option: StrEnum('dnsclass', attribute=True, autofill=False, cli_name='class', multivalue=False, required=False, values=(u'IN', u'CS', u'CH', u'HS'))
option: Str('idnsupdatepolicy', attribute=True, autofill=False, cli_name='update_policy', multivalue=False, required=False)
option: Bool('idnsallowdynupdate', attribute=True, autofill=False, cli_name='dynamic_update', default=False, multivalue=False, required=False)
+option: Str('idnsallowquery', attribute=True, autofill=False, cli_name='allow_query', default=u'any;', multivalue=False, required=False)
+option: Str('idnsallowtransfer', attribute=True, autofill=False, cli_name='allow_transfer', default=u'none;', multivalue=False, required=False)
option: Str('setattr*', cli_name='setattr', exclude='webui')
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Str('delattr*', cli_name='delattr', exclude='webui')
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index 495a21b1d..0b54aae04 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -30,7 +30,7 @@ from ipalib.plugins.baseldap import *
from ipalib import _, ngettext
from ipalib.util import validate_zonemgr, normalize_zonemgr, validate_hostname
from ipapython import dnsclient
-from ipapython.ipautil import valid_ip
+from ipapython.ipautil import valid_ip, CheckedIPAddress
from ldap import explode_dn
__doc__ = _("""
@@ -48,6 +48,9 @@ EXAMPLES:
ipa dnszone-mod example.com --dynamic-update=TRUE \\
--update-policy="grant EXAMPLE.COM krb5-self * A; grant EXAMPLE.COM krb5-self * AAAA;"
+ Modify the zone to allow zone transfers for local network only:
+ ipa dnszone-mod example.com --allow-transfer=10.0.0.0/8
+
Add new reverse zone specified by network IP address:
ipa dnszone-add --name-from-ip=80.142.15.0/24 \\
--name-server=nameserver.example.com
@@ -225,6 +228,68 @@ def _validate_ipnet(ugettext, ipnet):
return _('invalid IP network format')
return None
+def _validate_bind_aci(ugettext, bind_acis):
+ if not bind_acis:
+ return
+
+ bind_acis = bind_acis.split(';')
+ if bind_acis[-1]:
+ return _('each ACL element must be terminated with a semicolon')
+ else:
+ bind_acis.pop(-1)
+
+ for bind_aci in bind_acis:
+ if bind_aci in ("any", "none"):
+ continue
+
+ if bind_aci in ("localhost", "localnets"):
+ return _('ACL name "%s" is not supported') % bind_aci
+
+ if bind_aci.startswith('!'):
+ bind_aci = bind_aci[1:]
+
+ try:
+ ip = CheckedIPAddress(bind_aci, parse_netmask=True,
+ allow_network=True)
+ except (netaddr.AddrFormatError, ValueError), e:
+ return unicode(e)
+ except UnboundLocalError:
+ return _(u"invalid address format")
+
+def _normalize_bind_aci(bind_acis):
+ if not bind_acis:
+ return
+ bind_acis = bind_acis.split(';')
+ normalized = []
+ for bind_aci in bind_acis:
+ if not bind_aci:
+ continue
+ if bind_aci in ("any", "none", "localhost", "localnets"):
+ normalized.append(bind_aci)
+ continue
+
+ prefix = ""
+ if bind_aci.startswith('!'):
+ bind_aci = bind_aci[1:]
+ prefix = "!"
+
+ try:
+ ip = CheckedIPAddress(bind_aci, parse_netmask=True,
+ allow_network=True)
+ if '/' in bind_aci: # addr with netmask
+ netmask = "/%s" % ip.prefixlen
+ else:
+ netmask = ""
+ normalized.append(u"%s%s%s" % (prefix, str(ip), netmask))
+ continue
+ except:
+ normalized.append(bind_aci)
+ continue
+
+ acis = u';'.join(normalized)
+ acis += u';'
+ return acis
+
def _domain_name_validator(ugettext, value):
try:
# Allow domain name which is not fully qualified. These are supported
@@ -1150,7 +1215,7 @@ class dnszone(LDAPObject):
default_attributes = [
'idnsname', 'idnszoneactive', 'idnssoamname', 'idnssoarname',
'idnssoaserial', 'idnssoarefresh', 'idnssoaretry', 'idnssoaexpire',
- 'idnssoaminimum'
+ 'idnssoaminimum', 'idnsallowquery', 'idnsallowtransfer'
] + _record_attributes
label = _('DNS Zones')
label_singular = _('DNS Zone')
@@ -1254,6 +1319,24 @@ class dnszone(LDAPObject):
default=False,
autofill=True
),
+ Str('idnsallowquery?',
+ _validate_bind_aci,
+ normalizer=_normalize_bind_aci,
+ cli_name='allow_query',
+ label=_('Allow query'),
+ doc=_('Semicolon separated list of IP addresses or networks which are allowed to issue queries'),
+ default=u'any;', # anyone can issue queries by default
+ autofill=True,
+ ),
+ Str('idnsallowtransfer?',
+ _validate_bind_aci,
+ normalizer=_normalize_bind_aci,
+ cli_name='allow_transfer',
+ label=_('Allow transfer'),
+ doc=_('Semicolon separated list of IP addresses or networks which are allowed to transfer the zone'),
+ default=u'none;', # no one can issue queries by default
+ autofill=True,
+ ),
)
api.register(dnszone)
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index d9b0455e5..596787ff4 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -77,7 +77,9 @@ class CheckedIPAddress(netaddr.IPAddress):
# and don't allow IP addresses such as '1.1.1' in the same time
netaddr_ip_flags = netaddr.INET_PTON
- def __init__(self, addr, match_local=False, parse_netmask=True):
+ def __init__(self, addr, match_local=False, parse_netmask=True,
+ allow_network=False, allow_loopback=False,
+ allow_broadcast=False, allow_multicast=False):
if isinstance(addr, CheckedIPAddress):
super(CheckedIPAddress, self).__init__(addr, flags=self.netaddr_ip_flags)
self.prefixlen = addr.prefixlen
@@ -98,20 +100,23 @@ class CheckedIPAddress(netaddr.IPAddress):
try:
addr = netaddr.IPAddress(addr, flags=self.netaddr_ip_flags)
except ValueError:
- net = netaddr.IPNetwork(addr)
+ net = netaddr.IPNetwork(addr, flags=self.netaddr_ip_flags)
if not parse_netmask:
raise ValueError("netmask and prefix length not allowed here")
addr = net.ip
if addr.version not in (4, 6):
raise ValueError("unsupported IP version")
- if addr.is_loopback():
+
+ if not allow_loopback and addr.is_loopback():
raise ValueError("cannot use loopback IP address")
- if addr.is_reserved() or addr in netaddr.ip.IPV4_6TO4:
+ if (not addr.is_loopback() and addr.is_reserved()) \
+ or addr in netaddr.ip.IPV4_6TO4:
raise ValueError("cannot use IANA reserved IP address")
+
if addr.is_link_local():
raise ValueError("cannot use link-local IP address")
- if addr.is_multicast():
+ if not allow_multicast and addr.is_multicast():
raise ValueError("cannot use multicast IP address")
if match_local:
@@ -143,9 +148,9 @@ class CheckedIPAddress(netaddr.IPAddress):
elif addr.version == 6:
net = netaddr.IPNetwork(str(addr) + '/64')
- if addr == net.network:
+ if not allow_network and addr == net.network:
raise ValueError("cannot use IP network address")
- if addr.version == 4 and addr == net.broadcast:
+ if not allow_broadcast and addr.version == 4 and addr == net.broadcast:
raise ValueError("cannot use broadcast IP address")
super(CheckedIPAddress, self).__init__(addr, flags=self.netaddr_ip_flags)
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 2fa12565f..9dc12e276 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -214,7 +214,9 @@ def add_zone(name, zonemgr=None, dns_backup=None, ns_hostname=None, ns_ip_addres
idnssoarname=unicode(zonemgr),
ip_address=unicode(ns_ip_address),
idnsallowdynupdate=True,
- idnsupdatepolicy=unicode(update_policy))
+ idnsupdatepolicy=unicode(update_policy),
+ idnsallowquery=u'any',
+ idnsallowtransfer=u'none',)
except (errors.DuplicateEntry, errors.EmptyModlist):
pass
@@ -252,7 +254,9 @@ def add_reverse_zone(zone, ns_hostname=None, ns_ip_address=None,
idnssoamname=unicode(ns_main+'.'),
idnsallowdynupdate=True,
ip_address=unicode(ns_ip_address),
- idnsupdatepolicy=unicode(update_policy))
+ idnsupdatepolicy=unicode(update_policy),
+ idnsallowquery=u'any',
+ idnsallowtransfer=u'none',)
except (errors.DuplicateEntry, errors.EmptyModlist):
pass
diff --git a/ipaserver/install/plugins/Makefile.am b/ipaserver/install/plugins/Makefile.am
index cfa84c36d..e3b2e989b 100644
--- a/ipaserver/install/plugins/Makefile.am
+++ b/ipaserver/install/plugins/Makefile.am
@@ -6,6 +6,7 @@ app_PYTHON = \
baseupdate.py \
fix_replica_memberof.py \
rename_managed.py \
+ dns.py \
updateclient.py \
$(NULL)
diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py
new file mode 100644
index 000000000..6d72db43c
--- /dev/null
+++ b/ipaserver/install/plugins/dns.py
@@ -0,0 +1,65 @@
+# Authors:
+# Martin Kosek <mkosek@redhat.com>
+#
+# Copyright (C) 2012 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipaserver.install.plugins import MIDDLE
+from ipaserver.install.plugins.baseupdate import PostUpdate
+from ipaserver.install.plugins import baseupdate
+from ipalib import api, errors
+
+class update_dnszone_acls(PostUpdate):
+ """
+ Set AllowQuery and AllowTransfer ACLs in all zones that may be configured
+ in an upgraded FreeIPA instance.
+
+ Upgrading to new version of bind-dyndb-ldap and having these ACLs empty
+ would result in a leak of potentially sensitive DNS information as
+ zone transfers are enabled for all hosts if not disabled in named.conf
+ or LDAP.
+
+ This plugin disables the zone transfer by default so that it needs to be
+ explicitly enabled by FreeIPA Administrator.
+ """
+ order=MIDDLE
+
+ def execute(self, **options):
+ ldap = self.obj.backend
+
+ try:
+ zones = api.Command.dnszone_find()['result']
+ except errors.NotFound:
+ self.log.info('No DNS zone to update found')
+ return (False, False, [])
+
+ for zone in zones:
+ update = {}
+ if not zone.get('idnsallowquery'):
+ # allow query from any client by default
+ update['idnsallowquery'] = u'any;'
+
+ if not zone.get('idnsallowtransfer'):
+ # do not open zone transfers by default
+ update['idnsallowtransfer'] = u'none;'
+
+ if update:
+ api.Command.dnszone_mod(zone[u'idnsname'][0], **update)
+
+
+ return (False, False, [])
+
+api.register(update_dnszone_acls)
diff --git a/tests/test_xmlrpc/test_dns_plugin.py b/tests/test_xmlrpc/test_dns_plugin.py
index 4ad67ce8c..5d05d3af0 100644
--- a/tests/test_xmlrpc/test_dns_plugin.py
+++ b/tests/test_xmlrpc/test_dns_plugin.py
@@ -115,6 +115,8 @@ class test_dns(Declarative):
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
'idnsallowdynupdate': [u'FALSE'],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
'objectclass': [u'top', u'idnsrecord', u'idnszone'],
},
},
@@ -169,6 +171,8 @@ class test_dns(Declarative):
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
'idnsallowdynupdate': [u'FALSE'],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
'objectclass': [u'top', u'idnsrecord', u'idnszone'],
},
},
@@ -202,6 +206,8 @@ class test_dns(Declarative):
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
},
},
),
@@ -224,6 +230,8 @@ class test_dns(Declarative):
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
},
},
),
@@ -254,6 +262,8 @@ class test_dns(Declarative):
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
'idnsallowdynupdate': [u'FALSE'],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
'objectclass': [u'top', u'idnsrecord', u'idnszone'],
},
},
@@ -279,6 +289,8 @@ class test_dns(Declarative):
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
},
{
'dn': unicode(dnszone1_dn),
@@ -292,6 +304,8 @@ class test_dns(Declarative):
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
}],
},
),
@@ -316,6 +330,8 @@ class test_dns(Declarative):
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
}],
},
),
@@ -361,6 +377,8 @@ class test_dns(Declarative):
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
},
},
),
@@ -395,6 +413,8 @@ class test_dns(Declarative):
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
},
},
),
@@ -746,6 +766,8 @@ class test_dns(Declarative):
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
'idnsallowdynupdate': [u'FALSE'],
+ 'idnsallowtransfer': [u'none;'],
+ 'idnsallowquery': [u'any;'],
'objectclass': [u'top', u'idnsrecord', u'idnszone'],
},
},
@@ -788,6 +810,70 @@ class test_dns(Declarative):
dict(
+ desc='Try to add invalid allow-query to zone %r' % dnszone1,
+ command=('dnszone_mod', [dnszone1], {'idnsallowquery': u'localhost'}),
+ expected=errors.ValidationError(name='idnsallowquery', error=''),
+ ),
+
+ dict(
+ desc='Add allow-query ACL to zone %r' % dnszone1,
+ command=('dnszone_mod', [dnszone1], {'idnsallowquery': u'!10/8;any'}),
+ expected={
+ 'value': dnszone1,
+ 'summary': None,
+ 'result': {
+ 'idnsname': [dnszone1],
+ 'idnszoneactive': [u'TRUE'],
+ 'nsrecord': [dnszone1_mname],
+ 'mxrecord': [u'0 ns1.dnszone.test.'],
+ 'locrecord': [u"49 11 42.400 N 16 36 29.600 E 227.64"],
+ 'idnssoamname': [dnszone1_mname],
+ 'idnssoarname': [dnszone1_rname],
+ 'idnssoaserial': [fuzzy_digits],
+ 'idnssoarefresh': [u'5478'],
+ 'idnssoaretry': [fuzzy_digits],
+ 'idnssoaexpire': [fuzzy_digits],
+ 'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowquery': [u'!10.0.0.0/8;any;'],
+ 'idnsallowtransfer': [u'none;'],
+ },
+ },
+ ),
+
+
+ dict(
+ desc='Try to add invalid allow-transfer to zone %r' % dnszone1,
+ command=('dnszone_mod', [dnszone1], {'idnsallowtransfer': u'10.'}),
+ expected=errors.ValidationError(name='idnsallowtransfer', error=''),
+ ),
+
+ dict(
+ desc='Add allow-transer ACL to zone %r' % dnszone1,
+ command=('dnszone_mod', [dnszone1], {'idnsallowtransfer': u'80.142.15.80'}),
+ expected={
+ 'value': dnszone1,
+ 'summary': None,
+ 'result': {
+ 'idnsname': [dnszone1],
+ 'idnszoneactive': [u'TRUE'],
+ 'nsrecord': [dnszone1_mname],
+ 'mxrecord': [u'0 ns1.dnszone.test.'],
+ 'locrecord': [u"49 11 42.400 N 16 36 29.600 E 227.64"],
+ 'idnssoamname': [dnszone1_mname],
+ 'idnssoarname': [dnszone1_rname],
+ 'idnssoaserial': [fuzzy_digits],
+ 'idnssoarefresh': [u'5478'],
+ 'idnssoaretry': [fuzzy_digits],
+ 'idnssoaexpire': [fuzzy_digits],
+ 'idnssoaminimum': [fuzzy_digits],
+ 'idnsallowquery': [u'!10.0.0.0/8;any;'],
+ 'idnsallowtransfer': [u'80.142.15.80;'],
+ },
+ },
+ ),
+
+
+ dict(
desc='Delete zone %r' % dnszone1,
command=('dnszone_del', [dnszone1], {}),
expected={