On 05/05/15 15:00, Martin Basti wrote:
On 30/04/15 15:37, David Kupka wrote:
On 04/24/2015 02:56 PM, Martin Basti wrote:
Patches attached.




Hi,
thanks for patches.

1. You changed message in DNSServerNotRespondingWarning class but not the test in ipatest/test_xmlrpc/test_dns_plugin.py

nitpick. Please spell 'edns' correctly. I've seen several instances of 'ends'.

Thank you,

updated patches attached:
* new error messages
* logging to debug log server output if exception was raised
* fixed test
* fixed spelling



Fixed tests (again)

Updated patches attached

--
Martin Basti

From acde762eb941e61991f9ec3d8069d723d1021bd8 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Wed, 22 Apr 2015 15:29:21 +0200
Subject: [PATCH 1/2] DNSSEC: Improve global forwarders validation

Validation now provides more detailed information and less false
positives failures.

https://fedorahosted.org/freeipa/ticket/4657
---
 ipalib/messages.py                      |  23 +++++-
 ipalib/plugins/dns.py                   |  64 +++++++++-------
 ipalib/util.py                          | 130 ++++++++++++++++++++++++++------
 ipaserver/install/bindinstance.py       |  30 +++++---
 ipatests/test_xmlrpc/test_dns_plugin.py |   5 +-
 5 files changed, 186 insertions(+), 66 deletions(-)

diff --git a/ipalib/messages.py b/ipalib/messages.py
index b44beca729f5483a7241e4c98a9f724ed663e70f..236b683b30692d88e5257d9189c559dd9f848885 100644
--- a/ipalib/messages.py
+++ b/ipalib/messages.py
@@ -179,14 +179,14 @@ class OptionSemanticChangedWarning(PublicMessage):
                u"%(hint)s")
 
 
-class DNSServerNotRespondingWarning(PublicMessage):
+class DNSServerValidationWarning(PublicMessage):
     """
-    **13006**  Used when a DNS server is not responding to queries
+    **13006**  Used when a DNS server is not to able to resolve query
     """
 
     errno = 13006
     type = "warning"
-    format = _(u"DNS server %(server)s not responding.")
+    format = _(u"DNS server %(server)s: %(error)s.")
 
 
 class DNSServerDoesNotSupportDNSSECWarning(PublicMessage):
@@ -196,10 +196,11 @@ class DNSServerDoesNotSupportDNSSECWarning(PublicMessage):
 
     errno = 13007
     type = "warning"
-    format = _(u"DNS server %(server)s does not support DNSSEC. "
+    format = _(u"DNS server %(server)s does not support DNSSEC: %(error)s.\n"
                u"If DNSSEC validation is enabled on IPA server(s), "
                u"please disable it.")
 
+
 class ForwardzoneIsNotEffectiveWarning(PublicMessage):
     """
     **13008** Forwardzone is not effective, forwarding will not work because
@@ -214,6 +215,20 @@ class ForwardzoneIsNotEffectiveWarning(PublicMessage):
                u"\"%(ns_rec)s\" to parent zone \"%(authzone)s\".")
 
 
+class DNSServerDoesNotSupportEDNS0Warning(PublicMessage):
+    """
+    **13009** Used when a DNS server does not support EDNS0, required for
+    DNSSEC support
+    """
+
+    errno = 13009
+    type = "warning"
+    format = _(u"DNS server %(server)s does not support EDNS0 (RFC 6891): "
+               u"%(error)s.\n"
+               u"If DNSSEC validation is enabled on IPA server(s), "
+               u"please disable it.")
+
+
 def iter_messages(variables, base):
     """Return a tuple with all subclasses
     """
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index f589ab5b77a918b75fe6c48b465ecd9f02cb6d42..d2dcff9084ddf0a2f91b32812e670eb747392b05 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -43,7 +43,10 @@ from ipalib.util import (normalize_zonemgr,
                          get_dns_forward_zone_update_policy,
                          get_dns_reverse_zone_update_policy,
                          get_reverse_zone_default, REVERSE_DNS_ZONES,
-                         normalize_zone, validate_dnssec_forwarder)
+                         normalize_zone, validate_dnssec_global_forwarder,
+                         DNSSECSignatureMissingError, UnresolvableRecordError,
+                         EDNS0UnsupportedError)
+
 from ipapython.ipautil import CheckedIPAddress, is_host_resolvable
 from ipapython.dnsutil import DNSName
 
@@ -4262,40 +4265,43 @@ class dnsconfig_mod(LDAPUpdate):
 
     def interactive_prompt_callback(self, kw):
         if kw.get('idnsforwarders', False):
-            self.Backend.textui.print_plain("Server will check forwarder(s).")
-            self.Backend.textui.print_plain("This may take some time, please wait ...")
+            self.Backend.textui.print_plain(
+                _("Server will check DNS forwarder(s)."))
+            self.Backend.textui.print_plain(
+                _("This may take some time, please wait ..."))
 
     def execute(self, *keys, **options):
         # test dnssec forwarders
-        non_dnssec_forwarders = []
-        not_responding_forwarders = []
         forwarders = options.get('idnsforwarders')
+
+        result = super(dnsconfig_mod, self).execute(*keys, **options)
+        self.obj.postprocess_result(result)
+
         if forwarders:
             for forwarder in forwarders:
-                dnssec_status = validate_dnssec_forwarder(forwarder)
-                if dnssec_status is None:
-                    not_responding_forwarders.append(forwarder)
-                elif dnssec_status is False:
-                    non_dnssec_forwarders.append(forwarder)
-
-        result = super(dnsconfig_mod, self).execute(*keys, **options)
-        self.obj.postprocess_result(result)
-
-        # add messages
-        for forwarder in not_responding_forwarders:
-            messages.add_message(
-                options['version'],
-                result, messages.DNSServerNotRespondingWarning(
-                    server=forwarder,
-                )
-            )
-        for forwarder in non_dnssec_forwarders:
-            messages.add_message(
-                options['version'],
-                result, messages.DNSServerDoesNotSupportDNSSECWarning(
-                    server=forwarder,
-                )
-            )
+                try:
+                    validate_dnssec_global_forwarder(forwarder, log=self.log)
+                except DNSSECSignatureMissingError as e:
+                    messages.add_message(
+                        options['version'],
+                        result, messages.DNSServerDoesNotSupportDNSSECWarning(
+                            server=forwarder, error=e,
+                        )
+                    )
+                except EDNS0UnsupportedError as e:
+                    messages.add_message(
+                        options['version'],
+                        result, messages.DNSServerDoesNotSupportEDNS0Warning(
+                            server=forwarder, error=e,
+                        )
+                    )
+                except UnresolvableRecordError as e:
+                    messages.add_message(
+                        options['version'],
+                        result, messages.DNSServerValidationWarning(
+                            server=forwarder, error=e
+                        )
+                    )
 
         return result
 
diff --git a/ipalib/util.py b/ipalib/util.py
index 2c17d80a0427a5c7e45a6a0b64fa1f4d39fffa8a..f120404b6e48d86462f987496293a1a9b1b35304 100644
--- a/ipalib/util.py
+++ b/ipalib/util.py
@@ -36,7 +36,7 @@ from dns import resolver, rdatatype
 from dns.exception import DNSException
 from netaddr.core import AddrFormatError
 
-from ipalib import errors
+from ipalib import errors, messages
 from ipalib.text import _
 from ipapython.ssh import SSHPublicKey
 from ipapython.dn import DN, RDN
@@ -559,38 +559,126 @@ def validate_hostmask(ugettext, hostmask):
         return _('invalid hostmask')
 
 
-def validate_dnssec_forwarder(ip_addr):
-    """Test DNS forwarder properties.
+class ForwarderValidationError(Exception):
+    format = None
 
-    :returns:
-     True if forwarder works as expected and supports DNSSEC.
-     False if forwarder does not support DNSSEC.
-     None if forwarder does not respond.
+    def __init__(self, format=None, message=None, **kw):
+        messages.process_message_arguments(self, format, message, **kw)
+        super(ForwarderValidationError, self).__init__(self.msg)
+
+
+class UnresolvableRecordError(ForwarderValidationError):
+    format = _("query '%(owner)s %(rtype)s': %(error)s")
+
+
+class EDNS0UnsupportedError(ForwarderValidationError):
+    format = _("query '%(owner)s %(rtype)s' with EDNS0: %(error)s")
+
+
+class DNSSECSignatureMissingError(ForwarderValidationError):
+    format = _("answer to query '%(owner)s %(rtype)s' is missing DNSSEC "
+               "signatures (no RRSIG data)")
+
+
+def _log_response(log, e):
+    """
+    If exception contains response from server, log this response to debug log
+    :param log: if log is None, do not log
+    :param e: DNSException
     """
-    ip_addr = str(ip_addr)
+    assert isinstance(e, DNSException)
+    if log is not None:
+        response = e.kwargs.get('response')
+        if response:
+            log.debug("DNSException: %s; server response: %s", e, response)
+
+
+def _resolve_record(owner, rtype, nameserver_ip=None, edns0=False,
+                    dnssec=False, timeout=10):
+    """
+    :param nameserver_ip: if None, default resolvers will be used
+    :param edns0: enables EDNS0
+    :param dnssec: enabled EDNS0, flags: DO
+    :raise DNSException: if error occurs
+    """
+    assert isinstance(nameserver_ip, basestring)
+    assert isinstance(rtype, basestring)
+
     res = dns.resolver.Resolver()
-    res.nameservers = [ip_addr]
-    res.lifetime = 10  # wait max 10 seconds for reply
+    if nameserver_ip:
+        res.nameservers = [nameserver_ip]
+    res.lifetime = timeout
 
-    # enable Authenticated Data + Checking Disabled flags
-    res.set_flags(dns.flags.AD | dns.flags.CD)
+    # Recursion Desired,
+    # this option prevents to get answers in authority section instead of answer
+    res.set_flags(dns.flags.RD)
 
-    # enable EDNS v0 + enable DNSSEC-Ok flag
-    res.use_edns(0, dns.flags.DO, 0)
+    if dnssec:
+        res.use_edns(0, dns.flags.DO, 4096)
+        res.set_flags(dns.flags.RD)
+    elif edns0:
+        res.use_edns(0, 0, 4096)
+
+    return res.query(owner, rtype)
+
+
+def _validate_edns0_forwarder(owner, rtype, ip_addr, log=None, timeout=10):
+    """
+    Validate if forwarder supports EDNS0
+
+    :raise UnresolvableRecordError: record cannot be resolved
+    :raise EDNS0UnsupportedError: EDNS0 is not supported by forwarder
+    """
+
+    try:
+        _resolve_record(owner, rtype, nameserver_ip=ip_addr, timeout=timeout)
+    except DNSException as e:
+        _log_response(log, e)
+        raise UnresolvableRecordError(owner=owner, rtype=rtype, ip=ip_addr,
+                                      error=e)
+
+    try:
+        _resolve_record(owner, rtype, nameserver_ip=ip_addr, edns0=True,
+                        timeout=timeout)
+    except DNSException as e:
+        _log_response(log, e)
+        raise EDNS0UnsupportedError(owner=owner, rtype=rtype, ip=ip_addr,
+                                    error=e)
+
+
+def validate_dnssec_global_forwarder(ip_addr, log=None, timeout=10):
+    """Test DNS forwarder properties. against root zone.
+
+    Global forwarders should be able return signed root zone
+
+    :raise UnresolvableRecordError: record cannot be resolved
+    :raise EDNS0UnsupportedError: EDNS0 is not supported by forwarder
+    :raise DNSSECSignatureMissingError: did not receive RRSIG for root zone
+    """
+
+    ip_addr = str(ip_addr)
+    owner = "."
+    rtype = "SOA"
+
+    _validate_edns0_forwarder(owner, rtype, ip_addr, log=log, timeout=timeout)
 
     # DNS root has to be signed
     try:
-        ans = res.query('.', 'NS')
-    except DNSException:
-        return None
+        ans = _resolve_record(owner, rtype, nameserver_ip=ip_addr, dnssec=True,
+                              timeout=timeout)
+    except DNSException as e:
+        _log_response(log, e)
+        raise UnresolvableRecordError(owner=owner, rtype=rtype, ip=ip_addr,
+                                      error=e)
 
     try:
-        ans.response.find_rrset(ans.response.answer, dns.name.root,
-                dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NS)
+        ans.response.find_rrset(
+            ans.response.answer, dns.name.root, dns.rdataclass.IN,
+            dns.rdatatype.RRSIG, dns.rdatatype.SOA
+        )
     except KeyError:
-        return False
+        raise DNSSECSignatureMissingError(owner=owner, rtype=rtype, ip=ip_addr)
 
-    return True
 
 
 def validate_idna_domain(value):
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 4c1bfa600fc887e401bbe818121e3bf5115a3785..725324c831c24b3ef54f378705e21cf43ff0b041 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -42,7 +42,8 @@ from ipaplatform.tasks import tasks
 from ipalib.util import (validate_zonemgr_str, normalize_zonemgr,
         get_dns_forward_zone_update_policy, get_dns_reverse_zone_update_policy,
         normalize_zone, get_reverse_zone_default, zone_is_reverse,
-        validate_dnssec_forwarder)
+        validate_dnssec_global_forwarder, DNSSECSignatureMissingError,
+        EDNS0UnsupportedError, UnresolvableRecordError)
 from ipalib.constants import CACERT
 
 NAMED_CONF = paths.NAMED_CONF
@@ -466,20 +467,29 @@ def check_forwarders(dns_forwarders, logger):
     print "Checking forwarders, please wait ..."
     forwarders_dnssec_valid = True
     for forwarder in dns_forwarders:
-        logger.debug("Checking forwarder: %s", forwarder)
-        result = validate_dnssec_forwarder(forwarder)
-        if result is None:
-            logger.error("Forwarder %s does not work", forwarder)
-            raise RuntimeError("Forwarder %s does not respond" % forwarder)
-        elif result is False:
+        logger.debug("Checking DNS server: %s", forwarder)
+        try:
+            validate_dnssec_global_forwarder(forwarder, log=logger)
+        except DNSSECSignatureMissingError as e:
             forwarders_dnssec_valid = False
-            logger.warning("DNS forwarder %s does not return DNSSEC signatures in answers", forwarder)
+            logger.warning("DNS server %s does not support DNSSEC: %s",
+                           forwarder, e)
             logger.warning("Please fix forwarder configuration to enable DNSSEC support.\n"
                 "(For BIND 9 add directive \"dnssec-enable yes;\" to \"options {}\")")
-            print ("WARNING: DNS forwarder %s does not return DNSSEC "
-                   "signatures in answers" % forwarder)
+            print "DNS server %s: %s" % (forwarder, e)
             print "Please fix forwarder configuration to enable DNSSEC support."
             print "(For BIND 9 add directive \"dnssec-enable yes;\" to \"options {}\")"
+        except EDNS0UnsupportedError as e:
+            forwarders_dnssec_valid = False
+            logger.warning("DNS server %s does not support ENDS0 "
+                           "(RFC 6891): %s", forwarder, e)
+            logger.warning("Please fix forwarder configuration. "
+                           "DNSSEC support cannot be enabled without EDNS0")
+            print ("WARNING: DNS server %s does not support EDNS0 "
+                   "(RFC 6891): %s" % (forwarder, e))
+        except UnresolvableRecordError as e:
+            logger.error("DNS server %s: %s", forwarder, e)
+            raise RuntimeError("DNS server %s: %s" % (forwarder, e))
 
     return forwarders_dnssec_valid
 
diff --git a/ipatests/test_xmlrpc/test_dns_plugin.py b/ipatests/test_xmlrpc/test_dns_plugin.py
index a226c80486e4d44a44714a2f7d03e1049d4d37a8..50f46bc52a1646320628e792d72d8686ffa47d24 100644
--- a/ipatests/test_xmlrpc/test_dns_plugin.py
+++ b/ipatests/test_xmlrpc/test_dns_plugin.py
@@ -1751,10 +1751,11 @@ class test_dns(Declarative):
                 'value': None,
                 'summary': None,
                 u'messages': (
-                    {u'message': u'DNS server 172.16.31.80 not responding.',
+                    {u'message': lambda x: x.startswith(
+                        u"DNS server %s: query '. SOA':" % fwd_ip),
                      u'code': 13006,
                      u'type':u'warning',
-                     u'name': u'DNSServerNotRespondingWarning'},
+                     u'name': u'DNSServerValidationWarning'},
                 ),
                 'result': {
                     'idnsforwarders': [fwd_ip],
-- 
2.1.0

From f23aad8b1c625667d93ed8d4cbc8ba553f60f2ee Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Fri, 24 Apr 2015 13:37:07 +0200
Subject: [PATCH 2/2] DNSSEC: validate forward zone forwarders

Show warning messages if DNSSEC validation is failing for particular FW
zone or if the specified forwarders do not work

https://fedorahosted.org/freeipa/ticket/4657
---
 ipalib/messages.py                      | 12 +++++
 ipalib/plugins/dns.py                   | 96 ++++++++++++++++++++++++++++++++-
 ipalib/util.py                          | 54 ++++++++++++++++++-
 ipatests/test_xmlrpc/test_dns_plugin.py | 20 +++++++
 4 files changed, 179 insertions(+), 3 deletions(-)

diff --git a/ipalib/messages.py b/ipalib/messages.py
index 236b683b30692d88e5257d9189c559dd9f848885..84f0a722d3c82a9626a9b404a7134305fcd71dfa 100644
--- a/ipalib/messages.py
+++ b/ipalib/messages.py
@@ -229,6 +229,18 @@ class DNSServerDoesNotSupportEDNS0Warning(PublicMessage):
                u"please disable it.")
 
 
+class DNSSECValidationFailingWarning(PublicMessage):
+    """
+    **13010** Used when a DNSSEC validation failed on IPA DNS server
+    """
+
+    errno = 13010
+    type = "warning"
+    format = _(u"DNSSEC validation failed: %(error)s.\n"
+               u"Please verify your DNSSEC signatures or disable DNSSEC "
+               u"validation on all IPA servers.")
+
+
 def iter_messages(variables, base):
     """Return a tuple with all subclasses
     """
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index d2dcff9084ddf0a2f91b32812e670eb747392b05..5e67867356728e359602309e6bab7733ff3fd69b 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -26,6 +26,7 @@ import re
 import binascii
 import dns.name
 import dns.exception
+import dns.rdatatype
 import dns.resolver
 import encodings.idna
 
@@ -45,7 +46,9 @@ from ipalib.util import (normalize_zonemgr,
                          get_reverse_zone_default, REVERSE_DNS_ZONES,
                          normalize_zone, validate_dnssec_global_forwarder,
                          DNSSECSignatureMissingError, UnresolvableRecordError,
-                         EDNS0UnsupportedError)
+                         EDNS0UnsupportedError, DNSSECValidationError,
+                         validate_dnssec_zone_forwarder_step1,
+                         validate_dnssec_zone_forwarder_step2)
 
 from ipapython.ipautil import CheckedIPAddress, is_host_resolvable
 from ipapython.dnsutil import DNSName
@@ -4337,11 +4340,86 @@ class dnsforwardzone(DNSZoneBase):
         _add_warning_fw_zone_is_not_effective(result, fwzone,
                                               options['version'])
 
+    def _warning_if_forwarders_do_not_work(self, result, *keys, **options):
+        fwzone = keys[-1]
+        forwarders = options.get('idnsforwarders', [])
+        any_forwarder_work = False
+
+        for forwarder in forwarders:
+            try:
+                validate_dnssec_zone_forwarder_step1(forwarder, fwzone,
+                                                     log=self.log)
+            except UnresolvableRecordError as e:
+                messages.add_message(
+                    options['version'],
+                    result, messages.DNSServerValidationWarning(
+                        server=forwarder, error=e
+                    )
+                )
+            except EDNS0UnsupportedError as e:
+                messages.add_message(
+                    options['version'],
+                    result, messages.DNSServerDoesNotSupportEDNS0Warning(
+                        server=forwarder, error=e
+                    )
+                )
+            else:
+                any_forwarder_work = True
+
+        if not any_forwarder_work:
+            # do not test DNSSEC validation if there is no valid forwarder
+            return
+
+        # resolve IP address of any DNS replica
+        # FIXME: https://fedorahosted.org/bind-dyndb-ldap/ticket/143
+        # we currenly should to test all IPA DNS replica, because DNSSEC
+        # validation is configured just in named.conf per replica
+
+        ipa_dns_masters = [normalize_zone(x) for x in
+                           api.Object.dnsrecord.get_dns_masters()]
+
+        if not ipa_dns_masters:
+            # something very bad happened, DNS is installed, but no IPA DNS
+            # servers available
+            self.log.error("No IPA DNS server can be found, but integrated DNS "
+                           "is installed")
+            return
+
+        ipa_dns_ip = None
+        for rdtype in (dns.rdatatype.A, dns.rdatatype.AAAA):
+            try:
+                ans = dns.resolver.query(ipa_dns_masters[0], rdtype)
+            except dns.exception.DNSException:
+                continue
+            else:
+                ipa_dns_ip = ans.rrset.items[0]
+                break
+
+        if not ipa_dns_ip:
+            self.log.error("Cannot resolve %s hostname", ipa_dns_masters[0])
+            return
+
+        # Test if IPA is able to receive replies from forwarders
+        try:
+            validate_dnssec_zone_forwarder_step2(ipa_dns_ip, fwzone,
+                                                 log=self.log)
+        except DNSSECValidationError as e:
+            messages.add_message(
+                options['version'],
+                result, messages.DNSSECValidationFailingWarning(error=e)
+            )
 
 @register()
 class dnsforwardzone_add(DNSZoneBase_add):
     __doc__ = _('Create new DNS forward zone.')
 
+    def interactive_prompt_callback(self, kw):
+        if kw.get('idnsforwarders', False):
+            self.Backend.textui.print_plain(
+                _("Server will check DNS forwarder(s)."))
+            self.Backend.textui.print_plain(
+                _("This may take some time, please wait ..."))
+
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -4361,6 +4439,9 @@ class dnsforwardzone_add(DNSZoneBase_add):
     def execute(self, *keys, **options):
         result = super(dnsforwardzone_add, self).execute(*keys, **options)
         self.obj._warning_fw_zone_is_not_effective(result, *keys, **options)
+        if options.get('idnsforwarders'):
+            self.obj._warning_if_forwarders_do_not_work(result, *keys,
+                                                        **options)
         return result
 
 
@@ -4375,6 +4456,13 @@ class dnsforwardzone_del(DNSZoneBase_del):
 class dnsforwardzone_mod(DNSZoneBase_mod):
     __doc__ = _('Modify DNS forward zone.')
 
+    def interactive_prompt_callback(self, kw):
+        if kw.get('idnsforwarders', False):
+            self.Backend.textui.print_plain(
+                _("Server will check DNS forwarder(s)."))
+            self.Backend.textui.print_plain(
+                _("This may take some time, please wait ..."))
+
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         try:
             entry = ldap.get_entry(dn)
@@ -4403,6 +4491,12 @@ class dnsforwardzone_mod(DNSZoneBase_mod):
 
         return dn
 
+    def execute(self, *keys, **options):
+        result = super(dnsforwardzone_mod, self).execute(*keys, **options)
+        if options.get('idnsforwarders'):
+            self.obj._warning_if_forwarders_do_not_work(result, *keys,
+                                                        **options)
+        return result
 
 @register()
 class dnsforwardzone_find(DNSZoneBase_find):
diff --git a/ipalib/util.py b/ipalib/util.py
index f120404b6e48d86462f987496293a1a9b1b35304..4ee2d9dce07a31eecef8f386d1b5d6e7f02d1509 100644
--- a/ipalib/util.py
+++ b/ipalib/util.py
@@ -580,6 +580,11 @@ class DNSSECSignatureMissingError(ForwarderValidationError):
                "signatures (no RRSIG data)")
 
 
+class DNSSECValidationError(ForwarderValidationError):
+    format = _("requested record '%(owner)s %(rtype)s' was refused by IPA "
+               "server %(ip)s because DNSSEC signature is not valid")
+
+
 def _log_response(log, e):
     """
     If exception contains response from server, log this response to debug log
@@ -594,11 +599,12 @@ def _log_response(log, e):
 
 
 def _resolve_record(owner, rtype, nameserver_ip=None, edns0=False,
-                    dnssec=False, timeout=10):
+                    dnssec=False, flag_cd=False, timeout=10):
     """
     :param nameserver_ip: if None, default resolvers will be used
     :param edns0: enables EDNS0
     :param dnssec: enabled EDNS0, flags: DO
+    :param flag_cd: requires dnssec=True, adds flag CD
     :raise DNSException: if error occurs
     """
     assert isinstance(nameserver_ip, basestring)
@@ -615,7 +621,10 @@ def _resolve_record(owner, rtype, nameserver_ip=None, edns0=False,
 
     if dnssec:
         res.use_edns(0, dns.flags.DO, 4096)
-        res.set_flags(dns.flags.RD)
+        flags = dns.flags.RD
+        if flag_cd:
+            flags = flags | dns.flags.CD
+        res.set_flags(flags)
     elif edns0:
         res.use_edns(0, 0, 4096)
 
@@ -680,6 +689,47 @@ def validate_dnssec_global_forwarder(ip_addr, log=None, timeout=10):
         raise DNSSECSignatureMissingError(owner=owner, rtype=rtype, ip=ip_addr)
 
 
+def validate_dnssec_zone_forwarder_step1(ip_addr, fwzone, log=None, timeout=10):
+    """
+    Only forwarders in forward zones can be validated in this way
+    :raise UnresolvableRecordError: record cannot be resolved
+    :raise EDNS0UnsupportedError: ENDS0 is not supported by forwarder
+    """
+    _validate_edns0_forwarder(fwzone, "SOA", ip_addr, log=log, timeout=timeout)
+
+
+def validate_dnssec_zone_forwarder_step2(ipa_ip_addr, fwzone, log=None,
+                                         timeout=10):
+    """
+    This step must be executed after forwarders is added into LDAP, and only
+    when we are sure the forwarders work.
+    Query will be send to IPA DNS server, to verify if reply passed,
+    or DNSSEC validation failed.
+    Only forwarders in forward zones can be validated in this way
+    :raise UnresolvableRecordError: record cannot be resolved
+    :raise DNSSECValidationError: response from forwarder is not DNSSEC valid
+    """
+    rtype = "SOA"
+    try:
+        _resolve_record(fwzone, rtype, nameserver_ip=ipa_ip_addr, edns0=True,
+                        timeout=timeout)
+    except DNSException as e:
+        _log_response(log, e)
+    else:
+        return
+
+    try:
+        _resolve_record(fwzone, rtype, nameserver_ip=ipa_ip_addr, dnssec=True,
+                        flag_cd=True, timeout=timeout)
+    except DNSException as e:
+        _log_response(log, e)
+        raise UnresolvableRecordError(owner=fwzone, rtype=rtype, ip=ipa_ip_addr,
+                                      error=e)
+    else:
+        # record is not DNSSEC valid, because it can be received with CD flag
+        # only
+        raise DNSSECValidationError(owner=fwzone, rtype=rtype, ip=ipa_ip_addr)
+
 
 def validate_idna_domain(value):
     """
diff --git a/ipatests/test_xmlrpc/test_dns_plugin.py b/ipatests/test_xmlrpc/test_dns_plugin.py
index 50f46bc52a1646320628e792d72d8686ffa47d24..e38ea424db6b9216af35a172b7ffc12a3bf890f3 100644
--- a/ipatests/test_xmlrpc/test_dns_plugin.py
+++ b/ipatests/test_xmlrpc/test_dns_plugin.py
@@ -3375,6 +3375,14 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone2_dnsname,
                 'summary': None,
+                u'messages': (
+                    {u'message': lambda x: x.startswith(
+                        u"DNS server %s: query '%s SOA':" %
+                        (forwarder1, fwzone2)),
+                     u'code': 13006,
+                     u'type':u'warning',
+                     u'name': u'DNSServerValidationWarning'},
+                ),
                 'result': {
                     'dn': fwzone2_dn,
                     'idnsname': [fwzone2_dnsname],
@@ -3409,6 +3417,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone2_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'dn': fwzone2_dn,
                     'idnsname': [fwzone2_dnsname],
@@ -3442,6 +3451,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone2_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'dn': fwzone2_dn,
                     'idnsname': [fwzone2_dnsname],
@@ -3465,6 +3475,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone3_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'dn': fwzone3_dn,
                     'idnsname': [fwzone3_dnsname],
@@ -3498,6 +3509,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone3_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'dn': fwzone3_dn,
                     'idnsname': [fwzone3_dnsname],
@@ -3521,6 +3533,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone3_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'idnsname': [fwzone3_dnsname],
                     'idnszoneactive': [u'TRUE'],
@@ -3541,6 +3554,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone3_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'idnsname': [fwzone3_dnsname],
                     'idnszoneactive': [u'TRUE'],
@@ -3561,6 +3575,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone3_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'idnsname': [fwzone3_dnsname],
                     'idnszoneactive': [u'TRUE'],
@@ -3581,6 +3596,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone3_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'idnsname': [fwzone3_dnsname],
                     'idnszoneactive': [u'TRUE'],
@@ -3602,6 +3618,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone1_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'idnsname': [fwzone1_dnsname],
                     'idnszoneactive': [u'TRUE'],
@@ -3663,6 +3680,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone1_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'idnsname': [fwzone1_dnsname],
                     'idnszoneactive': [u'TRUE'],
@@ -3704,6 +3722,7 @@ class test_forward_zones(Declarative):
             expected={
                 'value': fwzone1_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'idnsname': [fwzone1_dnsname],
                     'idnszoneactive': [u'TRUE'],
@@ -4616,6 +4635,7 @@ class test_forward_master_zones_mutual_exlusion(Declarative):
             expected={
                 'value': zone_findtest_forward_dnsname,
                 'summary': None,
+                'messages': lambda x: True,  # fake forwarders - ignore message
                 'result': {
                     'dn': zone_findtest_forward_dn,
                     'idnsname': [zone_findtest_forward_dnsname],
-- 
2.1.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to