diff options
author | Jakub Hrozek <jhrozek@redhat.com> | 2011-01-27 11:16:22 -0500 |
---|---|---|
committer | Simo Sorce <ssorce@redhat.com> | 2011-01-28 11:44:38 -0500 |
commit | 0a6b1c4bced35dc0943ae38fcea71586274395ba (patch) | |
tree | 6c232d4e0113ef1dcbc44e83ee94b00003ff4d62 | |
parent | 682ca8658aa3b1c517848bc72e6531fea782ed07 (diff) | |
download | freeipa-0a6b1c4bced35dc0943ae38fcea71586274395ba.tar.gz freeipa-0a6b1c4bced35dc0943ae38fcea71586274395ba.tar.xz freeipa-0a6b1c4bced35dc0943ae38fcea71586274395ba.zip |
Enforce that all NS records are resolvable
Bind cannot load a zone if any of its name server records is not
resolvable.
https://fedorahosted.org/freeipa/ticket/838
-rw-r--r-- | API.txt | 2 | ||||
-rw-r--r-- | ipalib/plugins/dns.py | 61 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_dns_plugin.py | 61 |
3 files changed, 123 insertions, 1 deletions
@@ -717,6 +717,8 @@ option: Str('idnsupdatepolicy', attribute=True, cli_name='update_policy', label= option: Flag('idnsallowdynupdate', attribute=True, autofill=True, cli_name='allow_dynupdate', default=False, label=Gettext('Dynamic update', domain='ipa', localedir=None), multivalue=False, required=True) option: Str('addattr*', validate_add_attribute, cli_name='addattr', exclude='webui') option: Str('setattr*', validate_set_attribute, cli_name='setattr', exclude='webui') +option: Flag('force', autofill=True, default=False,lag('force', autofill=True, default=False, doc=Gettext('force DNS zone even if name server not in DNS', domain='ipa', localedir=None)) +option: Str('ip_address?', _validate_ipaddr,tr('ip_address?', _validate_ipaddr, doc=Gettext('Add the nameserver to DNS with this IP address', domain='ipa', localedir=None)) option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui', flags=['no_output']) option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui', flags=['no_output']) option: Str('version?', exclude='webui', flags=['no_option', 'no_output']) diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py index f0c24ad56..f770af32c 100644 --- a/ipalib/plugins/dns.py +++ b/ipalib/plugins/dns.py @@ -151,6 +151,24 @@ def has_cli_options(entry, no_option_msg): raise errors.OptionError(no_option_msg) return entry +def is_ns_rec_resolvable(name): + try: + return api.Command['dns_resolve'](name) + except errors.NotFound: + raise errors.NotFound(reason=_('Nameserver \'%(host)s\' does not have a corresponding A/AAAA record' % {'host':name})) + +def add_forward_record(zone, name, str_address): + addr = netaddr.IPAddress(str_address) + try: + if addr.version == 4: + api.Command['dnsrecord_add'](zone, name, arecord=str_address) + elif addr.version == 6: + api.Command['dnsrecord_add'](zone, name, aaaarecord=str_address) + else: + raise ValueError('Invalid address family') + except errors.EmptyModlist: + pass # the entry already exists and matches + def dns_container_exists(ldap): try: ldap.get_entry(api.env.container_dns, []) @@ -266,6 +284,15 @@ class dnszone_add(LDAPCreate): """ Create new DNS zone (SOA record). """ + takes_options = LDAPCreate.takes_options + ( + Flag('force', + doc=_('force DNS zone even if name server not in DNS'), + ), + Str('ip_address?', _validate_ipaddr, + doc=_('Add the nameserver to DNS with this IP address'), + ), + ) + def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): if not dns_container_exists(self.api.Backend.ldap2): raise errors.NotFound(reason=_('DNS is not configured')) @@ -275,13 +302,29 @@ class dnszone_add(LDAPCreate): entry_attrs.get('idnsallowdynupdate', False) ).upper() + # Check nameserver has a forward record nameserver = entry_attrs['idnssoamname'] + + if not 'ip_address' in options and not options['force']: + is_ns_rec_resolvable(nameserver) + if nameserver[-1] != '.': nameserver += '.' + entry_attrs['nsrecord'] = nameserver entry_attrs['idnssoamname'] = nameserver return dn + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + if 'ip_address' in options: + nameserver = entry_attrs['idnssoamname'][0][:-1] # ends with a dot + nsparts = nameserver.split('.') + add_forward_record('.'.join(nsparts[1:]), + nsparts[0], + options['ip_address']) + + return dn + api.register(dnszone_add) @@ -468,6 +511,8 @@ class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options): entry_attrs = self.record_options_2_entry(**options) + dn = self.pre_callback(ldap, dn, entry_attrs, *keys, **options) + try: (dn, old_entry_attrs) = ldap.get_entry(dn, entry_attrs.keys()) except errors.NotFound: @@ -504,6 +549,9 @@ class dnsrecord_mod_record(LDAPQuery, dnsrecord_cmd_w_record_options): def update_old_entry_callback(self, entry_attrs, old_entry_attrs): pass + def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + return dn + def post_callback(self, keys, entry_attrs): pass @@ -540,6 +588,19 @@ class dnsrecord_add(LDAPCreate, dnsrecord_cmd_w_record_options): has_cli_options(options, self.no_option_msg) return super(dnsrecord_add, self).args_options_2_entry(*keys, **options) + def _nsrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + for ns in options['nsrecord']: + is_ns_rec_resolvable(ns) + return dn + + def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + for rtype in options: + rtype_cb = '_%s_pre_callback' % rtype + if hasattr(self, rtype_cb): + dn = getattr(self, rtype_cb)(ldap, dn, entry_attrs, *keys, **options) + + return dn + def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): if call_func.func_name == 'add_entry': if isinstance(exc, errors.DuplicateEntry): diff --git a/tests/test_xmlrpc/test_dns_plugin.py b/tests/test_xmlrpc/test_dns_plugin.py index 0be29ff86..b994a2383 100644 --- a/tests/test_xmlrpc/test_dns_plugin.py +++ b/tests/test_xmlrpc/test_dns_plugin.py @@ -26,6 +26,7 @@ from tests.test_xmlrpc import objectclasses from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid dnszone1 = u'dnszone.test' +dnszone2 = u'dnszone2.test' dnsres1 = u'testdnsres' class test_dns(Declarative): @@ -36,6 +37,7 @@ class test_dns(Declarative): api.Command['dnszone_add'](dnszone1, idnssoamname = u'ns1.%s' % dnszone1, idnssoarname = u'root.%s' % dnszone1, + force = True, ) api.Command['dnszone_del'](dnszone1) except errors.NotFound: @@ -77,6 +79,7 @@ class test_dns(Declarative): 'dnszone_add', [dnszone1], { 'idnssoamname': u'ns1.%s' % dnszone1, 'idnssoarname': u'root.%s' % dnszone1, + 'ip_address' : u'1.2.3.4', } ), expected={ @@ -107,11 +110,62 @@ class test_dns(Declarative): 'dnszone_add', [dnszone1], { 'idnssoamname': u'ns1.%s' % dnszone1, 'idnssoarname': u'root.%s' % dnszone1, + 'ip_address' : u'1.2.3.4', } ), expected=errors.DuplicateEntry(), ), + dict( + desc='Try to create a zone with nonexistent NS entry', + command=( + 'dnszone_add', [dnszone2], { + 'idnssoamname': u'ns1.%s' % dnszone2, + 'idnssoarname': u'root.%s' % dnszone2, + } + ), + expected=errors.NotFound(reason='Nameserver \'ns1.%s\' does not have a corresponding A/AAAA record' % (dnszone2)), + ), + + dict( + desc='Create a zone with nonexistent NS entry with --force', + command=( + 'dnszone_add', [dnszone2], { + 'idnssoamname': u'ns1.%s' % dnszone2, + 'idnssoarname': u'root.%s' % dnszone2, + 'force' : True, + } + ), + expected={ + 'value': dnszone2, + 'summary': None, + 'result': { + 'dn': u'idnsname=%s,cn=dns,%s' % (dnszone2, api.env.basedn), + 'idnsname': [dnszone2], + 'idnszoneactive': [u'TRUE'], + 'idnssoamname': [u'ns1.%s.' % dnszone2], + 'nsrecord': [u'ns1.%s.' % dnszone2], + 'idnssoarname': [u'root.%s.' % dnszone2], + 'idnssoaserial': [fuzzy_digits], + 'idnssoarefresh': [fuzzy_digits], + 'idnssoaretry': [fuzzy_digits], + 'idnssoaexpire': [fuzzy_digits], + 'idnssoaminimum': [fuzzy_digits], + 'idnsallowdynupdate': [u'FALSE'], + 'objectclass': [u'top', u'idnsrecord', u'idnszone'], + }, + }, + ), + + dict( + desc='Delete zone %r' % dnszone2, + command=('dnszone_del', [dnszone2], {}), + expected={ + 'value': dnszone2, + 'summary': None, + 'result': {'failed': u''}, + }, + ), dict( desc='Retrieve zone %r' % dnszone1, @@ -286,7 +340,7 @@ class test_dns(Declarative): command=('dnsrecord_find', [dnszone1], {}), expected={ 'summary': None, - 'count': 2, + 'count': 3, 'truncated': False, 'result': [ { @@ -295,6 +349,11 @@ class test_dns(Declarative): 'idnsname': [u'@'], }, { + 'dn': u'idnsname=ns1,idnsname=%s,cn=dns,%s' % (dnszone1, api.env.basedn), + 'idnsname': [u'ns1'], + 'arecord': [u'1.2.3.4'], + }, + { 'dn': u'idnsname=%s,idnsname=%s,cn=dns,%s' % (dnsres1, dnszone1, api.env.basedn), 'idnsname': [dnsres1], 'arecord': [u'127.0.0.1'], |