dnspython: Add compatibility shim

`dnspython` 2.0.0 has many changes and several deprecations like:

```
> dns.resolver.resolve() has been added, allowing control of whether
search lists are used. dns.resolver.query() is retained for backwards
compatibility, but deprecated. The default for search list behavior can
be set at in the resolver object with the use_search_by_default
parameter. The default is False.

> dns.resolver.resolve_address() has been added, allowing easy
address-to-name lookups.
```

The new class `DNSResolver`:
- provides the compatibility layer
- defaults the previous behavior (the search list configured in the
  system's resolver configuration is used for relative names)
- defaults lifetime to 15sec (determines the number of seconds
  to spend trying to get an answer to the question)

Fixes: https://pagure.io/freeipa/issue/8383
Signed-off-by: Stanislav Levin <slev@altlinux.org>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
Stanislav Levin
2020-08-28 16:31:10 +03:00
committed by Alexander Bokovoy
parent fdb227e55a
commit 49e643783d
16 changed files with 158 additions and 86 deletions

View File

@@ -20,7 +20,6 @@ dnsrecord_del commands.
import os
import sys
from dns import resolver
from ipalib import api, errors
from ipapython import dnsutil
@@ -37,7 +36,7 @@ else:
validation_domain = f'_acme-challenge.{certbot_domain}'
fqdn = dnsutil.DNSName(validation_domain).make_absolute()
zone = dnsutil.DNSName(resolver.zone_for_name(fqdn))
zone = dnsutil.DNSName(dnsutil.zone_for_name(fqdn))
name = fqdn.relativize(zone)
try:

View File

@@ -24,11 +24,11 @@ import socket
import six
from dns import resolver, rdatatype
from dns import rdatatype
from dns.exception import DNSException
from ipalib import errors
from ipalib.util import validate_domain_name
from ipapython.dnsutil import query_srv
from ipapython.dnsutil import query_srv, resolve
from ipaplatform.paths import paths
from ipapython.ipautil import valid_ip, realm_to_suffix
@@ -562,7 +562,7 @@ class IPADiscovery:
logger.debug("Search DNS for TXT record of %s", qname)
try:
answers = resolver.query(qname, rdatatype.TXT)
answers = resolve(qname, rdatatype.TXT)
except DNSException as e:
logger.debug("DNS record not found: %s", e.__class__.__name__)
answers = []

View File

@@ -53,7 +53,7 @@ from ipaplatform import services
from ipaplatform.constants import constants
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
from ipapython import certdb, kernel_keyring, ipaldap, ipautil
from ipapython import certdb, kernel_keyring, ipaldap, ipautil, dnsutil
from ipapython.admintool import ScriptError
from ipapython.dn import DN
from ipapython.install import typing
@@ -1440,7 +1440,7 @@ def verify_dns_update(fqdn, ips):
logger.debug('DNS resolver: Query: %s IN %s',
fqdn, dns.rdatatype.to_text(record_type))
try:
answers = dns.resolver.query(fqdn, record_type)
answers = dnsutil.resolve(fqdn, record_type)
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
logger.debug('DNS resolver: No record.')
except dns.resolver.NoNameservers:
@@ -1460,10 +1460,9 @@ def verify_dns_update(fqdn, ips):
missing_reverse = [str(ip) for ip in ips]
for ip in ips:
ip_str = str(ip)
addr = dns.reversename.from_address(ip_str)
logger.debug('DNS resolver: Query: %s IN PTR', addr)
logger.debug('DNS resolver: Query: %s IN PTR', ip_str)
try:
answers = dns.resolver.query(addr, dns.rdatatype.PTR)
answers = dnsutil.resolve_address(ip_str)
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
logger.debug('DNS resolver: No record.')
except dns.resolver.NoNameservers:

View File

@@ -42,7 +42,7 @@ import struct
import subprocess
import netaddr
from dns import resolver, rdatatype
from dns import rdatatype
from dns.exception import DNSException
from dns.resolver import NXDOMAIN
from netaddr.core import AddrFormatError
@@ -67,8 +67,12 @@ from ipaplatform.constants import constants
from ipaplatform.paths import paths
from ipapython.ssh import SSHPublicKey
from ipapython.dn import DN, RDN
from ipapython.dnsutil import DNSName
from ipapython.dnsutil import resolve_ip_addresses
from ipapython.dnsutil import (
DNSName,
DNSResolver,
resolve,
resolve_ip_addresses,
)
from ipapython.admintool import ScriptError
if sys.version_info >= (3, 2):
@@ -117,13 +121,13 @@ def has_soa_or_ns_record(domain):
Returns True or False.
"""
try:
resolver.query(domain, rdatatype.SOA)
resolve(domain, rdatatype.SOA)
soa_record_found = True
except DNSException:
soa_record_found = False
try:
resolver.query(domain, rdatatype.NS)
resolve(domain, rdatatype.NS)
ns_record_found = True
except DNSException:
ns_record_found = False
@@ -797,7 +801,7 @@ def _resolve_record(owner, rtype, nameserver_ip=None, edns0=False,
assert isinstance(nameserver_ip, str) or nameserver_ip is None
assert isinstance(rtype, str)
res = dns.resolver.Resolver()
res = DNSResolver()
if nameserver_ip:
res.nameservers = [nameserver_ip]
res.lifetime = timeout
@@ -815,7 +819,7 @@ def _resolve_record(owner, rtype, nameserver_ip=None, edns0=False,
elif edns0:
res.use_edns(0, 0, 4096)
return res.query(owner, rtype)
return res.resolve(owner, rtype)
def _validate_edns0_forwarder(owner, rtype, ip_addr, timeout=10):
@@ -985,7 +989,7 @@ def detect_dns_zone_realm_type(api, domain):
kerberos_record_name = kerberos_prefix + domain_suffix
try:
result = resolver.query(kerberos_record_name, rdatatype.TXT)
result = resolve(kerberos_record_name, rdatatype.TXT)
answer = result.response.answer
# IPA domain will have only one _kerberos TXT record
@@ -1012,7 +1016,7 @@ def detect_dns_zone_realm_type(api, domain):
try:
# The presence of this record is enough, return foreign in such case
resolver.query(ad_specific_record_name, rdatatype.SRV)
resolve(ad_specific_record_name, rdatatype.SRV)
except DNSException:
# If we could not detect type with certainty, return unknown
return 'unknown'

View File

@@ -27,6 +27,7 @@ import dns.exception
import dns.resolver
import dns.rdataclass
import dns.rdatatype
import dns.reversename
import six
@@ -39,6 +40,85 @@ if six.PY3:
logger = logging.getLogger(__name__)
ipa_resolver = None
def get_ipa_resolver():
global ipa_resolver
if ipa_resolver is None:
ipa_resolver = DNSResolver()
return ipa_resolver
def resolve(*args, **kwargs):
return get_ipa_resolver().resolve(*args, **kwargs)
def resolve_address(*args, **kwargs):
return get_ipa_resolver().resolve_address(*args, **kwargs)
def zone_for_name(*args, **kwargs):
if "resolver" not in kwargs:
kwargs["resolver"] = get_ipa_resolver()
return dns.resolver.zone_for_name(*args, **kwargs)
def reset_default_resolver():
"""Re-initialize ipa resolver.
"""
global ipa_resolver
ipa_resolver = DNSResolver()
class DNSResolver(dns.resolver.Resolver):
"""DNS stub resolver compatible with both dnspython < 2.0.0
and dnspython >= 2.0.0.
Set `use_search_by_default` attribute to `True`, which
determines the default for whether the search list configured
in the system's resolver configuration is used for relative
names, and whether the resolver's domain may be added to relative
names.
Increase the default lifetime which determines the number of seconds
to spend trying to get an answer to the question. dnspython 2.0.0
changes this to 5sec, while the previous one was 30sec.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.reset_ipa_defaults()
self.resolve = getattr(super(), "resolve", self.query)
self.resolve_address = getattr(
super(),
"resolve_address",
self._resolve_address
)
def reset_ipa_defaults(self):
self.use_search_by_default = True
# the default is 5sec
self.lifetime = 15
def reset(self):
super().reset()
self.reset_ipa_defaults()
def _resolve_address(self, ip_address, *args, **kwargs):
"""Query nameservers for PTR records.
:param ip_address: IPv4 or IPv6 address
:type ip_address: str
"""
return resolve(
dns.reversename.from_address(ip_address),
rdtype=dns.rdatatype.PTR,
*args,
**kwargs,
)
class DNSZoneAlreadyExists(dns.exception.DNSException):
supp_kwargs = {'zone', 'ns'}
fmt = (u"DNS zone {zone} already exists in DNS "
@@ -321,7 +401,7 @@ def resolve_rrsets(fqdn, rdtypes):
rrsets = []
for rdtype in rdtypes:
try:
answer = dns.resolver.query(fqdn, rdtype)
answer = resolve(fqdn, rdtype)
logger.debug('found %d %s records for %s: %s',
len(answer),
rdtype,
@@ -363,7 +443,7 @@ def check_zone_overlap(zone, raise_on_error=True):
return
try:
containing_zone = dns.resolver.zone_for_name(zone)
containing_zone = zone_for_name(zone)
except dns.exception.DNSException as e:
msg = ("DNS check for domain %s failed: %s." % (zone, e))
if raise_on_error:
@@ -374,7 +454,7 @@ def check_zone_overlap(zone, raise_on_error=True):
if containing_zone == zone:
try:
ns = [ans.to_text() for ans in dns.resolver.query(zone, 'NS')]
ns = [ans.to_text() for ans in resolve(zone, 'NS')]
except dns.exception.DNSException as e:
logger.debug("Failed to resolve nameserver(s) for domain %s: %s",
zone, e)
@@ -463,6 +543,8 @@ def query_srv(qname, resolver=None, **kwargs):
:return: list of dns.rdtypes.IN.SRV.SRV instances
"""
if resolver is None:
resolver = dns.resolver
answer = resolver.query(qname, rdtype=dns.rdatatype.SRV, **kwargs)
resolve_f = resolve
else:
resolve_f = getattr(resolver, "resolve", resolver.query)
answer = resolve_f(qname, rdtype=dns.rdatatype.SRV, **kwargs)
return sort_prio_weight(answer)

View File

@@ -43,9 +43,6 @@ import locale
import collections
import urllib
from dns import resolver, reversename
from dns.exception import DNSException
import six
from six.moves import input
@@ -1112,22 +1109,6 @@ def check_port_bindable(port, socket_type=socket.SOCK_STREAM):
s.close()
def reverse_record_exists(ip_address):
"""
Checks if IP address have some reverse record somewhere.
Does not care where it points.
Returns True/False
"""
reverse = reversename.from_address(str(ip_address))
try:
resolver.query(reverse, "PTR")
except DNSException:
# really don't care what exception, PTR is simply unresolvable
return False
return True
def config_replace_variables(filepath, replacevars=dict(), appendvars=dict(),
removevars=None):
"""

View File

@@ -30,9 +30,9 @@ import shutil
import sys
import time
import dns.resolver
import ldap
import six
from dns.exception import DNSException
from ipaserver.dns_data_management import (
IPASystemRecords,
@@ -320,10 +320,15 @@ def read_reverse_zone(default, ip_address, allow_zone_overlap=False):
def get_auto_reverse_zones(ip_addresses, allow_zone_overlap=False):
auto_zones = []
for ip in ip_addresses:
if ipautil.reverse_record_exists(ip):
try:
dnsutil.resolve_address(str(ip))
except DNSException:
pass
else:
# PTR exist there is no reason to create reverse zone
logger.info("Reverse record for IP address %s already exists", ip)
continue
default_reverse = get_reverse_zone_default(ip)
if not allow_zone_overlap:
try:
@@ -1131,7 +1136,7 @@ class BindInstance(service.Service):
else:
# python DNS might have global resolver cached in this variable
# we have to re-initialize it because resolv.conf has changed
dns.resolver.reset_default_resolver()
dnsutil.reset_default_resolver()
def __generate_rndc_key(self):
installutils.check_entropy()

View File

@@ -12,13 +12,9 @@ from __future__ import print_function
import enum
import logging
import os
# absolute import is necessary because IPA module dns clashes with python-dns
from dns import resolver
import six
import sys
import six
from subprocess import CalledProcessError
from ipalib import api
@@ -294,7 +290,7 @@ def install_check(standalone, api, replica, options, hostname):
if not options.forwarders:
options.forwarders = []
if options.auto_forwarders:
options.forwarders += resolver.get_default_resolver().nameservers
options.forwarders += dnsutil.get_ipa_resolver().nameservers
elif standalone or not replica:
options.forwarders = read_dns_forwarders()

View File

@@ -38,7 +38,7 @@ from contextlib import contextmanager
from configparser import ConfigParser as SafeConfigParser
from configparser import NoOptionError
from dns import resolver, rdatatype
from dns import rdatatype
from dns.exception import DNSException
import ldap
import six
@@ -53,6 +53,7 @@ from ipalib.constants import MAXHOSTNAMELEN
from ipalib.util import validate_hostname
from ipalib import api, errors, x509
from ipapython.dn import DN
from ipapython.dnsutil import resolve, get_ipa_resolver
from ipaserver.install import certs, service, sysupgrade
from ipaplatform import services
from ipaplatform.paths import paths
@@ -187,7 +188,7 @@ def verify_fqdn(host_name, no_host_dns=False, local_hostname=True):
# Verify this is NOT a CNAME
try:
logger.debug('Check if %s is not a CNAME', host_name)
resolver.query(host_name, rdatatype.CNAME)
resolve(host_name, rdatatype.CNAME)
raise HostReverseLookupError("The IPA Server Hostname cannot be a CNAME, only A and AAAA names are allowed.")
except DNSException:
pass
@@ -284,11 +285,13 @@ def read_ip_addresses():
def read_dns_forwarders():
addrs = []
if ipautil.user_input("Do you want to configure DNS forwarders?", True):
print("Following DNS servers are configured in /etc/resolv.conf: %s" %
", ".join(resolver.get_default_resolver().nameservers))
print(
"Following DNS servers are configured in /etc/resolv.conf: %s"
% ", ".join(get_ipa_resolver().nameservers)
)
if ipautil.user_input("Do you want to configure these servers as DNS "
"forwarders?", True):
addrs = resolver.default_resolver.nameservers[:]
addrs = get_ipa_resolver().nameservers[:]
print("All DNS servers from /etc/resolv.conf were added. You can "
"enter additional addresses now:")
while True:

View File

@@ -9,8 +9,6 @@ import logging
import dns.exception as dnsexception
import dns.name as dnsname
import dns.resolver as dnsresolver
import dns.reversename as dnsreversename
import os
import shutil
import socket
@@ -28,6 +26,7 @@ from ipalib.install import certstore, sysrestore
from ipalib.install.kinit import kinit_keytab
from ipapython import ipaldap, ipautil
from ipapython.dn import DN
from ipapython.dnsutil import DNSResolver
from ipapython.admintool import ScriptError
from ipapython.ipachangeconf import IPAChangeConf
from ipaplatform import services
@@ -289,7 +288,7 @@ def check_dns_resolution(host_name, dns_servers):
logger.error(
'Could not resolve any DNS server hostname: %s', dns_servers)
return False
resolver = dnsresolver.Resolver()
resolver = DNSResolver()
resolver.nameservers = server_ips
logger.debug('Search DNS server %s (%s) for %s',
@@ -299,7 +298,7 @@ def check_dns_resolution(host_name, dns_servers):
addresses = set()
for rtype in 'A', 'AAAA':
try:
result = resolver.query(host_name, rtype)
result = resolver.resolve(host_name, rtype)
except dnsexception.DNSException:
rrset = []
else:
@@ -327,8 +326,7 @@ def check_dns_resolution(host_name, dns_servers):
checked.add(address)
try:
logger.debug('Check reverse address %s (%s)', address, host_name)
revname = dnsreversename.from_address(address)
rrset = resolver.query(revname, 'PTR').rrset
rrset = resolver.resolve_address(address).rrset
except Exception as e:
logger.debug('Check failed: %s %s', type(e).__name__, e)
logger.error(

View File

@@ -1193,7 +1193,7 @@ def _san_ip_update_reachable(reachable, dnsname, cname_depth):
"""
fqdn = dnsutil.DNSName(dnsname).make_absolute()
try:
zone = dnsutil.DNSName(resolver.zone_for_name(fqdn))
zone = dnsutil.DNSName(dnsutil.zone_for_name(fqdn))
except resolver.NoNameservers:
return # if there's no zone, there are no records
name = fqdn.relativize(zone)
@@ -1227,7 +1227,7 @@ def _ip_ptr_records(ip):
"""
rname = dnsutil.DNSName(reversename.from_address(ip))
try:
zone = dnsutil.DNSName(resolver.zone_for_name(rname))
zone = dnsutil.DNSName(dnsutil.zone_for_name(rname))
name = rname.relativize(zone)
result = api.Command['dnsrecord_show'](zone, name)['result']
except resolver.NoNameservers:

View File

@@ -79,9 +79,15 @@ from ipalib.util import (normalize_zonemgr,
from ipaplatform import services
from ipapython.dn import DN
from ipapython.ipautil import CheckedIPAddress
from ipapython.dnsutil import check_zone_overlap, DNSZoneAlreadyExists
from ipapython.dnsutil import DNSName
from ipapython.dnsutil import related_to_auto_empty_zone
from ipapython.dnsutil import (
check_zone_overlap,
DNSName,
DNSResolver,
DNSZoneAlreadyExists,
related_to_auto_empty_zone,
resolve,
zone_for_name,
)
from ipaserver.dns_data_management import (
IPASystemRecords,
IPADomainIsNotManagedByIPAError,
@@ -542,7 +548,7 @@ def get_reverse_zone(ipaddr):
ip = netaddr.IPAddress(str(ipaddr))
revdns = DNSName(unicode(ip.reverse_dns))
try:
revzone = DNSName(dns.resolver.zone_for_name(revdns))
revzone = DNSName(zone_for_name(revdns))
except dns.resolver.NoNameservers:
raise errors.NotFound(
reason=_(
@@ -3363,7 +3369,7 @@ class dnsrecord(LDAPObject):
:raises errors.DNSDataMismatch: if data in DNS and LDAP doesn't match
:raises dns.exception.DNSException: if DNS resolution failed
'''
resolver = dns.resolver.Resolver()
resolver = DNSResolver()
resolver.set_flags(0) # disable recursion (for NS RR checks)
max_attempts = int(self.api.env['wait_for_dns'])
warn_attempts = max_attempts // 2
@@ -3379,9 +3385,9 @@ class dnsrecord(LDAPObject):
log_fn = logger.warning
attempt += 1
try:
dns_answer = resolver.query(dns_name, rdtype,
dns.rdataclass.IN,
raise_on_no_answer=False)
dns_answer = resolver.resolve(dns_name, rdtype,
dns.rdataclass.IN,
raise_on_no_answer=False)
dns_rrset = None
if rdtype == _NS:
# NS records can be in Authority section (sometimes)
@@ -4303,7 +4309,7 @@ class dnsforwardzone(DNSZoneBase):
ipa_dns_ip = None
for rdtype in (dns.rdatatype.A, dns.rdatatype.AAAA):
try:
ans = dns.resolver.query(ipa_dns_masters[0], rdtype)
ans = resolve(ipa_dns_masters[0], rdtype)
except dns.exception.DNSException:
continue
else:

View File

@@ -22,8 +22,6 @@ from __future__ import absolute_import
import logging
import dns.resolver
import six
from ipalib import api, errors, util
@@ -64,7 +62,7 @@ from ipapython.ipautil import (
CheckedIPAddress,
TMP_PWD_ENTROPY_BITS
)
from ipapython.dnsutil import DNSName
from ipapython.dnsutil import DNSName, zone_for_name
from ipapython.ssh import SSHPublicKey
from ipapython.dn import DN
from ipapython import kerberos
@@ -826,7 +824,7 @@ class host_del(LDAPDelete):
if updatedns:
# Remove A, AAAA, SSHFP and PTR records of the host
fqdn_dnsname = DNSName(fqdn).make_absolute()
zone = DNSName(dns.resolver.zone_for_name(fqdn_dnsname))
zone = DNSName(zone_for_name(fqdn_dnsname))
relative_hostname = fqdn_dnsname.relativize(zone)
# Get all resources for this host

View File

@@ -49,6 +49,7 @@ from cryptography.hazmat.backends import default_backend
from ipapython import certdb
from ipapython import ipautil
from ipapython.dnsutil import DNSResolver
from ipaplatform.paths import paths
from ipaplatform.services import knownservices
from ipapython.dn import DN
@@ -1436,7 +1437,7 @@ def resolve_record(nameserver, query, rtype="SOA", retry=True, timeout=100):
:timeout: max period of time while method will try to resolve query
(requires retry=True)
"""
res = dns.resolver.Resolver()
res = DNSResolver()
res.nameservers = [nameserver]
res.lifetime = 10 # wait max 10 seconds for reply
@@ -1444,7 +1445,7 @@ def resolve_record(nameserver, query, rtype="SOA", retry=True, timeout=100):
while time.time() < wait_until:
try:
ans = res.query(query, rtype)
ans = res.resolve(query, rtype)
return ans
except dns.exception.DNSException:
if not retry:

View File

@@ -12,7 +12,7 @@ import dns.rdataclass
from ipatests.test_integration.base import IntegrationTest
from ipatests.pytest_ipa.integration import tasks
from ipapython.dnsutil import DNSName
from ipapython.dnsutil import DNSName, DNSResolver
from ipalib.constants import IPA_CA_RECORD
logger = logging.getLogger(__name__)
@@ -45,14 +45,14 @@ IPA_CA_A_REC = (
def resolve_records_from_server(rname, rtype, nameserver):
error = None
res = dns.resolver.Resolver()
res = DNSResolver()
res.nameservers = [nameserver]
res.lifetime = 30
logger.info("Query: %s %s, nameserver %s", rname, rtype, nameserver)
# lets try to query 3x
for _i in range(3):
try:
ans = res.query(rname, rtype)
ans = res.resolve(rname, rtype)
logger.info("Answer: %s", ans.rrset)
return ans.rrset
except (dns.resolver.NXDOMAIN, dns.resolver.Timeout) as e:

View File

@@ -10,13 +10,13 @@ import subprocess
import time
import dns.dnssec
import dns.resolver
import dns.name
from ipatests.test_integration.base import IntegrationTest
from ipatests.pytest_ipa.integration import tasks
from ipatests.pytest_ipa.integration.firewall import Firewall
from ipaplatform.paths import paths
from ipapython.dnsutil import DNSResolver
logger = logging.getLogger(__name__)
@@ -33,7 +33,7 @@ example3_test_zone = "example3.test."
def resolve_with_dnssec(nameserver, query, rtype="SOA"):
res = dns.resolver.Resolver()
res = DNSResolver()
res.nameservers = [nameserver]
res.lifetime = 10 # wait max 10 seconds for reply
# enable Authenticated Data + Checking Disabled flags
@@ -42,7 +42,7 @@ def resolve_with_dnssec(nameserver, query, rtype="SOA"):
# enable EDNS v0 + enable DNSSEC-Ok flag
res.use_edns(0, dns.flags.DO, 0)
ans = res.query(query, rtype)
ans = res.resolve(query, rtype)
return ans