diff options
author | Martin Basti <mbasti@redhat.com> | 2014-10-16 16:27:00 +0200 |
---|---|---|
committer | Martin Kosek <mkosek@redhat.com> | 2014-10-21 12:23:03 +0200 |
commit | ca030a089f9e45a5dae5f6fb5993f4cc714f1ab2 (patch) | |
tree | f99b61a736b118ce42773cc1d9ab8769b28a6a79 | |
parent | 30bc3a55cf816cc5114ddbd102afa8b52f598dec (diff) | |
download | freeipa-ca030a089f9e45a5dae5f6fb5993f4cc714f1ab2.tar.gz freeipa-ca030a089f9e45a5dae5f6fb5993f4cc714f1ab2.tar.xz freeipa-ca030a089f9e45a5dae5f6fb5993f4cc714f1ab2.zip |
DNSSEC: validate forwarders
Tickets:
https://fedorahosted.org/freeipa/ticket/3801
https://fedorahosted.org/freeipa/ticket/4417
Design:
https://fedorahosted.org/bind-dyndb-ldap/wiki/BIND9/Design/DNSSEC
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
Reviewed-By: David Kupka <dkupka@redhat.com>
-rw-r--r-- | install/share/bind.named.conf.template | 1 | ||||
-rwxr-xr-x | install/tools/ipa-dns-install | 13 | ||||
-rwxr-xr-x | install/tools/ipa-replica-install | 14 | ||||
-rwxr-xr-x | install/tools/ipa-server-install | 14 | ||||
-rw-r--r-- | ipalib/messages.py | 22 | ||||
-rw-r--r-- | ipalib/plugins/dns.py | 34 | ||||
-rw-r--r-- | ipalib/util.py | 35 | ||||
-rw-r--r-- | ipaserver/install/bindinstance.py | 31 |
8 files changed, 158 insertions, 6 deletions
diff --git a/install/share/bind.named.conf.template b/install/share/bind.named.conf.template index 2017cb796..3c19383c0 100644 --- a/install/share/bind.named.conf.template +++ b/install/share/bind.named.conf.template @@ -18,6 +18,7 @@ options { pid-file "$NAMED_PID"; dnssec-enable yes; + dnssec-validation yes; /* Path to ISC DLV key */ bindkeys-file "$BINDKEYS_FILE"; diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install index bf9d99080..be9930746 100755 --- a/install/tools/ipa-dns-install +++ b/install/tools/ipa-dns-install @@ -54,6 +54,8 @@ def parse_options(): help="The reverse DNS zone to use") parser.add_option("--no-reverse", dest="no_reverse", action="store_true", default=False, help="Do not create new reverse DNS zone") + parser.add_option("--no-dnssec-validation", dest="no_dnssec_validation", action="store_true", + default=False, help="Disable DNSSEC validation") parser.add_option("--zonemgr", action="callback", callback=bindinstance.zonemgr_callback, type="string", help="DNS zone manager e-mail address. Defaults to hostmaster@DOMAIN") @@ -142,6 +144,14 @@ def main(): dns_forwarders = options.forwarders else: dns_forwarders = read_dns_forwarders() + + # test DNSSEC forwarders + if dns_forwarders: + if (not bindinstance.check_forwarders(dns_forwarders, root_logger) + and not options.no_dnssec_validation): + options.no_dnssec_validation = True + print "WARNING: DNSSEC validation will be disabled" + root_logger.debug("will use dns_forwarders: %s\n", str(dns_forwarders)) if bind.dm_password: @@ -166,7 +176,8 @@ def main(): print "" bind.setup(api.env.host, ip_addresses, api.env.realm, api.env.domain, - dns_forwarders, conf_ntp, reverse_zones, zonemgr=options.zonemgr) + dns_forwarders, conf_ntp, reverse_zones, zonemgr=options.zonemgr, + no_dnssec_validation=options.no_dnssec_validation) bind.create_instance() # Restart http instance to make sure that python-dns has the right resolver diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install index df0e5d565..51809b140 100755 --- a/install/tools/ipa-replica-install +++ b/install/tools/ipa-replica-install @@ -116,6 +116,8 @@ def parse_options(): action="append", help="The reverse DNS zone to use") dns_group.add_option("--no-reverse", dest="no_reverse", action="store_true", default=False, help="Do not create new reverse DNS zone") + dns_group.add_option("--no-dnssec-validation", dest="no_dnssec_validation", action="store_true", + default=False, help="Disable DNSSEC validation") dns_group.add_option("--no-host-dns", dest="no_host_dns", action="store_true", default=False, help="Do not use DNS for hostname lookup during installation") @@ -138,6 +140,8 @@ def parse_options(): parser.error("You cannot specify a --reverse-zone option without the --setup-dns option") if options.no_reverse: parser.error("You cannot specify a --no-reverse option without the --setup-dns option") + if options.no_dnssec_validation: + parser.error("You cannot specify a --no-dnssec-validation option without the --setup-dns option") elif options.forwarders and options.no_forwarders: parser.error("You cannot specify a --forwarder option together with --no-forwarders") elif not options.forwarders and not options.no_forwarders: @@ -268,7 +272,8 @@ def install_bind(config, options): bind.setup(config.host_name, config.ips, config.realm_name, config.domain_name, forwarders, options.conf_ntp, - config.reverse_zones, ca_configured=options.setup_ca) + config.reverse_zones, ca_configured=options.setup_ca, + no_dnssec_validation=options.no_dnssec_validation) bind.create_instance() print "" @@ -471,6 +476,13 @@ def main(): if options.setup_dns: check_bind() + # test DNSSEC forwarders + if options.forwarders: + if (not bindinstance.check_forwarders(options.forwarders, root_logger) + and not options.no_dnssec_validation): + options.no_dnssec_validation = True + print "WARNING: DNSSEC validation will be disabled" + # Check to see if httpd is already configured to listen on 443 if httpinstance.httpd_443_configured(): sys.exit("Aborting installation") diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install index 3fa7bd72a..39662db0c 100755 --- a/install/tools/ipa-server-install +++ b/install/tools/ipa-server-install @@ -286,6 +286,8 @@ def parse_options(): action="append", default=[]) dns_group.add_option("--no-reverse", dest="no_reverse", action="store_true", default=False, help="Do not create reverse DNS zone") + dns_group.add_option("--no-dnssec-validation", dest="no_dnssec_validation", action="store_true", + default=False, help="Disable DNSSEC validation") dns_group.add_option("--zonemgr", action="callback", callback=bindinstance.zonemgr_callback, type="string", help="DNS zone manager e-mail address. Defaults to hostmaster@DOMAIN") @@ -331,6 +333,8 @@ def parse_options(): parser.error("You cannot specify a --reverse-zone option without the --setup-dns option") if options.no_reverse: parser.error("You cannot specify a --no-reverse option without the --setup-dns option") + if options.no_dnssec_validation: + parser.error("You cannot specify a --no-dnssec-validation option without the --setup-dns option") elif options.forwarders and options.no_forwarders: parser.error("You cannot specify a --forwarder option together with --no-forwarders") elif options.reverse_zones and options.no_reverse: @@ -1033,6 +1037,13 @@ def main(): else: dns_forwarders = read_dns_forwarders() + #test DNSSEC forwarders + if dns_forwarders: + if (not bindinstance.check_forwarders(dns_forwarders, root_logger) + and not options.no_dnssec_validation): + options.no_dnssec_validation = True + print "WARNING: DNSSEC validation will be disabled" + reverse_zones = bindinstance.check_reverse_zones(ip_addresses, options.reverse_zones, options, options.unattended) @@ -1267,7 +1278,8 @@ def main(): bind = bindinstance.BindInstance(fstore, dm_password) bind.setup(host_name, ip_addresses, realm_name, domain_name, dns_forwarders, options.conf_ntp, reverse_zones, zonemgr=options.zonemgr, - ca_configured=setup_ca) + ca_configured=setup_ca, + no_dnssec_validation=options.no_dnssec_validation) if options.setup_dns: api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password) diff --git a/ipalib/messages.py b/ipalib/messages.py index 6d871bc10..5eeab3c54 100644 --- a/ipalib/messages.py +++ b/ipalib/messages.py @@ -179,6 +179,28 @@ class OptionSemanticChangedWarning(PublicMessage): u"%(current_behavior)s.\n%(hint)s") +class DNSServerNotRespondingWarning(PublicMessage): + """ + **13006** Used when a DNS server is not responding to queries + """ + + errno = 13006 + type = "warning" + format = _(u"DNS server %(server)s not responding.") + + +class DNSServerDoesNotSupportDNSSECWarning(PublicMessage): + """ + **13007** Used when a DNS server does not support DNSSEC validation + """ + + errno = 13007 + type = "warning" + format = _(u"DNS server %(server)s does not support DNSSEC. " + 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 df42c6bfe..7fafd0d26 100644 --- a/ipalib/plugins/dns.py +++ b/ipalib/plugins/dns.py @@ -43,7 +43,7 @@ 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) + normalize_zone, validate_dnssec_forwarder) from ipapython.ipautil import CheckedIPAddress, is_host_resolvable from ipapython.dnsutil import DNSName @@ -3882,9 +3882,41 @@ class dnsconfig(LDAPObject): class dnsconfig_mod(LDAPUpdate): __doc__ = _('Modify global DNS configuration.') + 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 ...") + def execute(self, *keys, **options): + # test dnssec forwarders + non_dnssec_forwarders = [] + not_responding_forwarders = [] + for forwarder in options.get('idnsforwarders', []): + 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, + ) + ) + return result diff --git a/ipalib/util.py b/ipalib/util.py index a5eff582f..fcb2bab96 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -27,6 +27,7 @@ import time import socket import re import decimal +import dns import netaddr from types import NoneType from weakref import WeakKeyDictionary @@ -553,3 +554,37 @@ def validate_hostmask(ugettext, hostmask): netaddr.IPNetwork(hostmask) except (ValueError, AddrFormatError): return _('invalid hostmask') + + +def validate_dnssec_forwarder(ip_addr): + """Test DNS forwarder properties. + + :returns: + True if forwarder works as expected and supports DNSSEC. + False if forwarder does not support DNSSEC. + None if forwarder does not respond. + """ + ip_addr = str(ip_addr) + res = dns.resolver.Resolver() + res.nameservers = [ip_addr] + res.lifetime = 10 # wait max 10 seconds for reply + + # enable Authenticated Data + Checking Disabled flags + res.set_flags(dns.flags.AD | dns.flags.CD) + + # enable EDNS v0 + enable DNSSEC-Ok flag + res.use_edns(0, dns.flags.DO, 0) + + # DNS root has to be signed + try: + ans = res.query('.', 'NS') + except DNSException: + return None + + try: + ans.response.find_rrset(ans.response.answer, dns.name.root, + dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NS) + except KeyError: + return False + + return True diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py index d964daf22..6aa011d41 100644 --- a/ipaserver/install/bindinstance.py +++ b/ipaserver/install/bindinstance.py @@ -40,7 +40,8 @@ from ipalib import api, errors from ipaplatform.paths import paths 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) + normalize_zone, get_reverse_zone_default, zone_is_reverse, + validate_dnssec_forwarder) from ipalib.constants import CACERT NAMED_CONF = paths.NAMED_CONF @@ -447,6 +448,25 @@ def check_reverse_zones(ip_addresses, reverse_zones, options, unattended, search return ret_reverse_zones +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: + forwarders_dnssec_valid = False + logger.warning("DNS forwarder %s does not return DNSSEC signatures in answers", forwarder) + 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 is not configured to support " + "DNSSEC" % forwarder) + + return forwarders_dnssec_valid + class DnsBackup(object): def __init__(self, service): @@ -523,7 +543,7 @@ class BindInstance(service.Service): def setup(self, fqdn, ip_addresses, realm_name, domain_name, forwarders, ntp, reverse_zones, named_user="named", zonemgr=None, - ca_configured=None): + ca_configured=None, no_dnssec_validation=False): self.named_user = named_user self.fqdn = fqdn self.ip_addresses = ip_addresses @@ -535,6 +555,7 @@ class BindInstance(service.Service): self.ntp = ntp self.reverse_zones = reverse_zones self.ca_configured = ca_configured + self.no_dnssec_validation=no_dnssec_validation if not zonemgr: self.zonemgr = 'hostmaster.%s' % self.domain @@ -902,6 +923,12 @@ class BindInstance(service.Service): named_fd.write(named_txt) named_fd.close() + if self.no_dnssec_validation: + # disable validation + named_conf_set_directive("dnssec-validation", "no", + section=NAMED_SECTION_OPTIONS, + str_val=False) + def __setup_resolv_conf(self): self.fstore.backup_file(RESOLV_CONF) resolv_txt = "search "+self.domain+"\n" |