summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Kupka <dkupka@redhat.com>2015-12-02 14:20:50 +0000
committerTomas Babej <tbabej@redhat.com>2015-12-14 18:53:53 +0100
commit8d19da49c4259411ff333946019f4b981fab2bcf (patch)
treeb96bc8257c55ecdaa3d5621dd41dbb067fcf2f1d
parent6c107d819c557d32e90bbbd1ab4d60d8b59006db (diff)
dns: Check if domain already exists.
Raise an error when the domain already exists. This can be overriden using --force or --allow-zone-overlap options. https://fedorahosted.org/freeipa/ticket/3681 Reviewed-By: Petr Spacek <pspacek@redhat.com>
-rwxr-xr-xinstall/tools/ipa-dns-install3
-rw-r--r--install/tools/man/ipa-dns-install.13
-rw-r--r--install/tools/man/ipa-server-install.13
-rw-r--r--ipapython/ipautil.py18
-rw-r--r--ipaserver/install/bindinstance.py168
-rw-r--r--ipaserver/install/dns.py36
-rw-r--r--ipaserver/install/server/common.py19
7 files changed, 190 insertions, 60 deletions
diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index bdaffd30b..4fd75670f 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -57,6 +57,9 @@ def parse_options():
help="The reverse DNS zone to use. This option can be used multiple times")
parser.add_option("--no-reverse", dest="no_reverse", action="store_true",
default=False, help="Do not create new reverse DNS zone")
+ parser.add_option("--allow-zone-overlap", dest="allow_zone_overlap",
+ action="store_true", default=False, help="Create DNS "
+ "zone even if it already exists")
parser.add_option("--no-dnssec-validation", dest="no_dnssec_validation", action="store_true",
default=False, help="Disable DNSSEC validation")
parser.add_option("--dnssec-master", dest="dnssec_master", action="store_true",
diff --git a/install/tools/man/ipa-dns-install.1 b/install/tools/man/ipa-dns-install.1
index 2f2d43db7..5ec51c452 100644
--- a/install/tools/man/ipa-dns-install.1
+++ b/install/tools/man/ipa-dns-install.1
@@ -62,6 +62,9 @@ Copy OpenDNSSEC metadata from the specified kasp.db file. This will not create a
\fB\-\-zonemgr\fR
The e\-mail address of the DNS zone manager. Defaults to hostmaster@DOMAIN
.TP
+\fB\-\-allow\-zone\-overlap\fR
+Allow creatin of (reverse) zone even if the zone is already resolvable. Using this option is discouraged as it result in later problems with domain name resolution.
+.TP
\fB\-U\fR, \fB\-\-unattended\fR
An unattended installation that will never prompt for user input
.SH "DEPRECATED OPTIONS"
diff --git a/install/tools/man/ipa-server-install.1 b/install/tools/man/ipa-server-install.1
index 7106e9ed5..98adf1398 100644
--- a/install/tools/man/ipa-server-install.1
+++ b/install/tools/man/ipa-server-install.1
@@ -175,6 +175,9 @@ Do not automatically create DNS SSHFP records.
.TP
\fB\-\-no\-dnssec\-validation\fR
Disable DNSSEC validation on this server.
+.TP
+\fB\-\-allow\-zone\-overlap\fR
+Allow creatin of (reverse) zone even if the zone is already resolvable. Using this option is discouraged as it result in later problems with domain name resolution.
.SS "UNINSTALL OPTIONS"
.TP
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 4cee81e64..7509f08c1 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -40,7 +40,7 @@ from contextlib import contextmanager
import locale
import collections
-from dns import resolver, rdatatype
+from dns import resolver, rdatatype, reversename
from dns.exception import DNSException, Timeout
import six
from six.moves import input
@@ -1031,6 +1031,22 @@ def host_exists(host):
return True
+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 check_zone_overlap(zone, raise_on_timeout=True):
root_logger.info("Checking DNS domain %s, please wait ..." % zone)
if not isinstance(zone, DNSName):
diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py
index 6bfde83de..f590badb4 100644
--- a/ipaserver/install/bindinstance.py
+++ b/ipaserver/install/bindinstance.py
@@ -43,10 +43,12 @@ from ipaplatform.constants import constants
from ipaplatform.paths import paths
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_global_forwarder, DNSSECSignatureMissingError,
- EDNS0UnsupportedError, UnresolvableRecordError)
+ get_dns_forward_zone_update_policy,
+ get_dns_reverse_zone_update_policy,
+ normalize_zone, get_reverse_zone_default,
+ zone_is_reverse, validate_dnssec_global_forwarder,
+ DNSSECSignatureMissingError, EDNS0UnsupportedError,
+ UnresolvableRecordError, verify_host_resolvable)
from ipalib.constants import CACERT
if six.PY3:
@@ -278,20 +280,48 @@ def find_reverse_zone(ip_address, api=api):
return None
-def read_reverse_zone(default, ip_address):
+def read_reverse_zone(default, ip_address, allow_zone_overlap=False):
while True:
zone = ipautil.user_input("Please specify the reverse zone name", default=default)
if not zone:
return None
- if verify_reverse_zone(zone, ip_address):
- break
- else:
- print("Invalid reverse zone %s for IP address %s" % (zone, ip_address))
+ if not verify_reverse_zone(zone, ip_address):
+ root_logger.error("Invalid reverse zone %s for IP address %s"
+ % (zone, ip_address))
+ continue
+ if not allow_zone_overlap:
+ try:
+ ipautil.check_zone_overlap(zone, raise_on_timeout=False)
+ except ValueError as e:
+ root_logger.error("Reverse zone %s will not be used: %s"
+ % (zone, e))
+ continue
+ break
return normalize_zone(zone)
+
+def get_auto_reverse_zones(ip_addresses):
+ auto_zones = []
+ for ip in ip_addresses:
+ if ipautil.reverse_record_exists(ip):
+ # PTR exist there is no reason to create reverse zone
+ root_logger.info("Reverse record for IP address %s already "
+ "exists" % ip)
+ continue
+ default_reverse = get_reverse_zone_default(ip)
+ try:
+ ipautil.check_zone_overlap(default_reverse)
+ except ValueError:
+ root_logger.info("Reverse zone %s for IP address %s already exists"
+ % (default_reverse, ip))
+ continue
+ auto_zones.append((ip, default_reverse))
+ return auto_zones
+
def add_zone(name, zonemgr=None, dns_backup=None, ns_hostname=None,
- update_policy=None, force=False, api=api):
+ update_policy=None, force=False, skip_overlap_check=False,
+ api=api):
# always normalize zones
name = normalize_zone(name)
@@ -317,6 +347,7 @@ def add_zone(name, zonemgr=None, dns_backup=None, ns_hostname=None,
idnsupdatepolicy=unicode(update_policy),
idnsallowquery=u'any',
idnsallowtransfer=u'none',
+ skip_overlap_check=skip_overlap_check,
force=force)
except (errors.DuplicateEntry, errors.EmptyModlist):
pass
@@ -406,46 +437,62 @@ def zonemgr_callback(option, opt_str, value, parser):
parser.values.zonemgr = value
-def check_reverse_zones(ip_addresses, reverse_zones, options, unattended, search_reverse_zones=False):
- reverse_asked = False
- ret_reverse_zones = []
- # check that there is IP address in every reverse zone
- if reverse_zones:
- for rz in reverse_zones:
- for ip in ip_addresses:
- if verify_reverse_zone(rz, ip):
- ret_reverse_zones.append(normalize_zone(rz))
- break
- else:
- # no ip matching reverse zone found
- sys.exit("There is no IP address matching reverse zone %s." % rz)
- if not options.no_reverse:
- # check that there is reverse zone for every IP
- for ip in ip_addresses:
- if search_reverse_zones and find_reverse_zone(str(ip)):
- # reverse zone is already in LDAP
+def check_reverse_zones(ip_addresses, reverse_zones, options, unattended,
+ search_reverse_zones=False):
+ checked_reverse_zones = []
+
+ if not options.no_reverse and not reverse_zones:
+ if unattended:
+ options.no_reverse = True
+ else:
+ options.no_reverse = not create_reverse()
+
+ # shortcut
+ if options.no_reverse:
+ return []
+
+ # verify zones passed in options
+ for rz in reverse_zones:
+ # isn't the zone managed by someone else
+ if not options.allow_zone_overlap:
+ try:
+ ipautil.check_zone_overlap(rz)
+ except ValueError as e:
+ msg = "Reverse zone %s will not be used: %s" % (rz, e)
+ if options.unattended:
+ sys.exit(msg)
+ else:
+ root_logger.warning(msg)
continue
- for rz in ret_reverse_zones:
- if verify_reverse_zone(rz, ip):
- # reverse zone was entered by user
- break
- else:
- # no reverse zone for ip found
- if not reverse_asked:
- if not unattended and not reverse_zones:
- # user did not specify reverse_zone nor no_reverse
- options.no_reverse = not create_reverse()
- if options.no_reverse:
- # user decided not to create reverse zone
- return []
- reverse_asked = True
- rz = get_reverse_zone_default(str(ip))
- if not unattended:
- rz = read_reverse_zone(rz, str(ip))
- ret_reverse_zones.append(rz)
-
- return ret_reverse_zones
+ checked_reverse_zones.append(normalize_zone(rz))
+
+ # check that there is reverse zone for every IP
+ ips_missing_reverse = []
+ for ip in ip_addresses:
+ if search_reverse_zones and find_reverse_zone(str(ip)):
+ # reverse zone is already in LDAP
+ continue
+ for rz in checked_reverse_zones:
+ if verify_reverse_zone(rz, ip):
+ # reverse zone was entered by user
+ break
+ else:
+ ips_missing_reverse.append(ip)
+
+ # create reverse zone for IP addresses that does not have one
+ for (ip, rz) in get_auto_reverse_zones(ips_missing_reverse):
+ if unattended:
+ root_logger.warning("Missing reverse record for IP address %s"
+ % ip)
+ else:
+ if ipautil.user_input("Do you want to create reverse zone for IP "
+ "%s" % ip, True):
+ rz = read_reverse_zone(rz, str(ip), options.allow_zone_overlap)
+ checked_reverse_zones.append(rz)
+
+ return checked_reverse_zones
+
def check_forwarders(dns_forwarders, logger):
print("Checking DNS forwarders, please wait ...")
@@ -770,7 +817,8 @@ class BindInstance(service.Service):
def __setup_zone(self):
# Always use force=True as named is not set up yet
add_zone(self.domain, self.zonemgr, dns_backup=self.dns_backup,
- ns_hostname=self.api.env.host, force=True, api=self.api)
+ ns_hostname=self.api.env.host, force=True,
+ skip_overlap_check=True, api=self.api)
add_rr(self.domain, "_kerberos", "TXT", self.realm, api=self.api)
@@ -788,7 +836,8 @@ class BindInstance(service.Service):
# Always use force=True as named is not set up yet
for reverse_zone in self.reverse_zones:
add_zone(reverse_zone, self.zonemgr, ns_hostname=self.api.env.host,
- dns_backup=self.dns_backup, force=True, api=self.api)
+ dns_backup=self.dns_backup, force=True,
+ skip_overlap_check=True, api=self.api)
def __add_master_records(self, fqdn, addrs):
host, zone = fqdn.split(".", 1)
@@ -817,18 +866,19 @@ class BindInstance(service.Service):
api=self.api)
if not dns_zone_exists(zone, self.api):
- # add DNS domain for host first
- root_logger.debug(
- "Host domain (%s) is different from DNS domain (%s)!" % (
- zone, self.domain))
- root_logger.debug("Add DNS zone for host first.")
-
- add_zone(zone, self.zonemgr, dns_backup=self.dns_backup,
- ns_hostname=self.fqdn, force=True, api=self.api)
+ # check if master hostname is resolvable
+ try:
+ verify_host_resolvable(fqdn, root_logger)
+ except errors.DNSNotARecordError:
+ root_logger.warning("Master FQDN (%s) is not resolvable.",
+ fqdn)
# Add forward and reverse records to self
for addr in addrs:
- add_fwd_rr(zone, host, addr, api=self.api)
+ try:
+ add_fwd_rr(zone, host, addr, self.api)
+ except errors.NotFound as e:
+ pass
reverse_zone = find_reverse_zone(addr, self.api)
if reverse_zone:
diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
index 258bf5dbe..94e9017b6 100644
--- a/ipaserver/install/dns.py
+++ b/ipaserver/install/dns.py
@@ -13,11 +13,13 @@ from subprocess import CalledProcessError
from ipalib import api
from ipalib import errors
+from ipalib import util
from ipaplatform.paths import paths
from ipaplatform.constants import constants
from ipaplatform import services
from ipapython import ipautil
from ipapython import sysrestore
+from ipapython import dnsutil
from ipapython.dn import DN
from ipapython.ipa_log_manager import root_logger
from ipapython.ipaldap import AUTOBIND_ENABLED
@@ -97,6 +99,19 @@ def _disable_dnssec():
conn.update_entry(entry)
+def check_dns_enabled(api):
+ try:
+ api.Backend.rpcclient.connect()
+ result = api.Backend.rpcclient.forward(
+ 'dns_is_enabled',
+ version=u'2.112', # All the way back to 3.0 servers
+ )
+ return result['result']
+ finally:
+ if api.Backend.rpcclient.isconnected():
+ api.Backend.rpcclient.disconnect()
+
+
def install_check(standalone, replica, options, hostname):
global ip_addresses
global reverse_zones
@@ -106,6 +121,27 @@ def install_check(standalone, replica, options, hostname):
raise RuntimeError("Integrated DNS requires '%s' package" %
constants.IPA_DNS_PACKAGE_NAME)
+ # when installing first replica with DNS we need to check zone overlap
+ if not replica or not check_dns_enabled(api):
+ domain = dnsutil.DNSName(util.normalize_zone(api.env.domain))
+ print("Checking DNS domain %s, please wait ..." % domain)
+ try:
+ ipautil.check_zone_overlap(domain, raise_on_timeout=False)
+ except ValueError as e:
+ if options.force or options.allow_zone_overlap:
+ root_logger.warning(e.message)
+ else:
+ raise e
+
+ for reverse_zone in options.reverse_zones:
+ try:
+ ipautil.check_zone_overlap(reverse_zone)
+ except ValueError as e:
+ if options.force or options.allow_zone_overlap:
+ root_logger.warning(e.message)
+ else:
+ raise e
+
if standalone:
print("==============================================================================")
print("This program will setup DNS for the FreeIPA Server.")
diff --git a/ipaserver/install/server/common.py b/ipaserver/install/server/common.py
index 1c161120b..3ea0cdead 100644
--- a/ipaserver/install/server/common.py
+++ b/ipaserver/install/server/common.py
@@ -10,6 +10,8 @@ from ipapython.install import common, core
from ipapython.install.core import Knob
from ipalib.util import validate_domain_name
from ipaserver.install import bindinstance
+from ipapython.ipautil import check_zone_overlap
+from ipapython.dnsutil import DNSName
VALID_SUBJECT_ATTRS = ['st', 'o', 'ou', 'dnqualifier', 'c',
'serialnumber', 'l', 'title', 'sn', 'givenname',
@@ -171,6 +173,11 @@ class BaseServerDNS(common.Installable, core.Group, core.Composite):
description="Do not add any DNS forwarders, use root servers instead",
)
+ allow_zone_overlap = Knob(
+ bool, False,
+ description="Create DNS zone even if it already exists",
+ )
+
reverse_zones = Knob(
(list, str), [],
description=("The reverse DNS zone to use. This option can be used "
@@ -179,6 +186,12 @@ class BaseServerDNS(common.Installable, core.Group, core.Composite):
cli_metavar='REVERSE_ZONE',
)
+ @reverse_zones.validator
+ def reverse_zones(self, values):
+ if not self.allow_zone_overlap:
+ for zone in values:
+ check_zone_overlap(zone)
+
no_reverse = Knob(
bool, False,
description="Do not create new reverse DNS zone",
@@ -255,6 +268,11 @@ class BaseServer(common.Installable, common.Interactive, core.Composite):
@domain_name.validator
def domain_name(self, value):
validate_domain_name(value)
+ if (self.setup_dns and
+ not self.dns.allow_zone_overlap): # pylint: disable=no-member
+ print("Checking DNS domain %s, please wait ..." % value)
+ check_zone_overlap(value, False)
+
dm_password = Knob(
str, None,
@@ -452,6 +470,7 @@ class BaseServer(common.Installable, common.Interactive, core.Composite):
self.no_forwarders = self.dns.no_forwarders
self.reverse_zones = self.dns.reverse_zones
self.no_reverse = self.dns.no_reverse
+ self.allow_zone_overlap = self.dns.allow_zone_overlap
self.no_dnssec_validation = self.dns.no_dnssec_validation
self.dnssec_master = self.dns.dnssec_master
self.disable_dnssec_master = self.dns.disable_dnssec_master