-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 01/27/2011 07:02 PM, Jakub Hrozek wrote:
> Bind cannot load a zone if any of its name server records is not
> resolvable.
> 
> https://fedorahosted.org/freeipa/ticket/838

Rebased on top of new version of my patch 039
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAk1C6swACgkQHsardTLnvCUQtQCfSA84SU5QeN5CvOrRhM5TuqPB
x5IAnRm83L51eKZPQpqf8qMa9Ol08xQt
=YvIL
-----END PGP SIGNATURE-----
From b04c536a6e9b58ce96250c21c27ee99508906ad2 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhro...@redhat.com>
Date: Thu, 27 Jan 2011 11:16:22 -0500
Subject: [PATCH 2/2] 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
---
 API.txt                              |    2 +
 ipalib/plugins/dns.py                |   61 ++++++++++++++++++++++++++++++++++
 tests/test_xmlrpc/test_dns_plugin.py |   61 +++++++++++++++++++++++++++++++++-
 3 files changed, 123 insertions(+), 1 deletions(-)

diff --git a/API.txt b/API.txt
index 2f7016d..93ab0c8 100644
--- a/API.txt
+++ b/API.txt
@@ -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 f0c24ad..f770af3 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 0be29ff..b994a23 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'],
-- 
1.7.3.5

Attachment: jhrozek-freeipa-042-02-ns-records.patch.sig
Description: PGP signature

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to