mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
committed by
Martin Kosek
parent
30bc3a55cf
commit
ca030a089f
@@ -18,6 +18,7 @@ options {
|
||||
pid-file "$NAMED_PID";
|
||||
|
||||
dnssec-enable yes;
|
||||
dnssec-validation yes;
|
||||
|
||||
/* Path to ISC DLV key */
|
||||
bindkeys-file "$BINDKEYS_FILE";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user