mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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 Reviewed-By: David Kupka <dkupka@redhat.com> Reviewed-By: Petr Spacek <pspacek@redhat.com>
This commit is contained in:
parent
9aa6124b39
commit
f8c8c360f1
@ -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
|
||||
"""
|
||||
|
@ -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
|
||||
@ -4340,11 +4343,100 @@ class dnsforwardzone(DNSZoneBase):
|
||||
_add_warning_fw_zone_is_not_effective(result, fwzone,
|
||||
options['version'])
|
||||
|
||||
def _warning_if_forwarders_do_not_work(self, result, new_zone,
|
||||
*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 = str(ans.rrset.items[0])
|
||||
break
|
||||
|
||||
if not ipa_dns_ip:
|
||||
self.log.error("Cannot resolve %s hostname", ipa_dns_masters[0])
|
||||
return
|
||||
|
||||
# sleep a bit, adding new zone to BIND from LDAP may take a while
|
||||
if new_zone:
|
||||
time.sleep(5)
|
||||
|
||||
# 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)
|
||||
)
|
||||
except UnresolvableRecordError as e:
|
||||
messages.add_message(
|
||||
options['version'],
|
||||
result, messages.DNSServerValidationWarning(
|
||||
server=ipa_dns_ip, error=e
|
||||
)
|
||||
)
|
||||
|
||||
@register()
|
||||
class dnsforwardzone_add(DNSZoneBase_add):
|
||||
__doc__ = _('Create new DNS forward zone.')
|
||||
|
||||
def interactive_prompt_callback(self, kw):
|
||||
# show informative message on client side
|
||||
# server cannot send messages asynchronous
|
||||
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)
|
||||
|
||||
@ -4364,6 +4456,10 @@ 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'):
|
||||
print result, keys, options
|
||||
self.obj._warning_if_forwarders_do_not_work(
|
||||
result, True, *keys, **options)
|
||||
return result
|
||||
|
||||
|
||||
@ -4378,6 +4474,15 @@ class dnsforwardzone_del(DNSZoneBase_del):
|
||||
class dnsforwardzone_mod(DNSZoneBase_mod):
|
||||
__doc__ = _('Modify DNS forward zone.')
|
||||
|
||||
def interactive_prompt_callback(self, kw):
|
||||
# show informative message on client side
|
||||
# server cannot send messages asynchronous
|
||||
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)
|
||||
@ -4406,6 +4511,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, False, *keys,
|
||||
**options)
|
||||
return result
|
||||
|
||||
@register()
|
||||
class dnsforwardzone_find(DNSZoneBase_find):
|
||||
|
@ -34,6 +34,7 @@ from types import NoneType
|
||||
from weakref import WeakKeyDictionary
|
||||
from dns import resolver, rdatatype
|
||||
from dns.exception import DNSException
|
||||
from dns.resolver import NXDOMAIN
|
||||
from netaddr.core import AddrFormatError
|
||||
|
||||
from ipalib import errors, messages
|
||||
@ -580,6 +581,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 +600,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 +622,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 +690,52 @@ 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 NXDOMAIN as e:
|
||||
# sometimes CD flag is ignored and NXDomain is returned
|
||||
# this may cause false positive detection
|
||||
_log_response(log, e)
|
||||
raise DNSSECValidationError(owner=fwzone, rtype=rtype, ip=ipa_ip_addr)
|
||||
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):
|
||||
"""
|
||||
|
@ -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],
|
||||
|
Loading…
Reference in New Issue
Block a user