mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
parent
6c107d819c
commit
8d19da49c4
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
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
|
||||
else:
|
||||
print("Invalid reverse zone %s for IP address %s" % (zone, ip_address))
|
||||
|
||||
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
|
||||
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:
|
||||
# no ip matching reverse zone found
|
||||
sys.exit("There is no IP address matching reverse zone %s." % rz)
|
||||
if not options.no_reverse:
|
||||
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
|
||||
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 ret_reverse_zones:
|
||||
for rz in checked_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)
|
||||
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
|
||||
|
||||
return ret_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:
|
||||
|
@ -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.")
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user