summaryrefslogtreecommitdiffstats
path: root/ipaserver
diff options
context:
space:
mode:
authorMartin Basti <mbasti@redhat.com>2015-05-13 14:45:32 +0200
committerTomas Babej <tbabej@redhat.com>2015-07-07 08:37:15 +0200
commite151492560db25fa13c2a3edf5e2139dc6629047 (patch)
tree1cfb5a1a48dd522e265d425695122858a9366288 /ipaserver
parentb258bcee8337063259aa38b4387b9bb5721fb380 (diff)
downloadfreeipa-e151492560db25fa13c2a3edf5e2139dc6629047.tar.gz
freeipa-e151492560db25fa13c2a3edf5e2139dc6629047.tar.xz
freeipa-e151492560db25fa13c2a3edf5e2139dc6629047.zip
DNSSEC: allow to disable/replace DNSSEC key master
This commit allows to replace or disable DNSSEC key master Replacing DNSSEC master requires to copy kasp.db file manually by user ipa-dns-install: --disable-dnssec-master DNSSEC master will be disabled --dnssec-master --kasp-db=FILE This configure new DNSSEC master server, kasp.db from old server is required for sucessful replacement --force Skip checks https://fedorahosted.org/freeipa/ticket/4657 Reviewed-By: Petr Spacek <pspacek@redhat.com>
Diffstat (limited to 'ipaserver')
-rw-r--r--ipaserver/install/dns.py154
-rw-r--r--ipaserver/install/odsexporterinstance.py12
-rw-r--r--ipaserver/install/opendnssecinstance.py98
-rw-r--r--ipaserver/install/server/install.py23
-rw-r--r--ipaserver/install/server/replicainstall.py30
5 files changed, 295 insertions, 22 deletions
diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
index 8d9570d68..1382382b5 100644
--- a/ipaserver/install/dns.py
+++ b/ipaserver/install/dns.py
@@ -4,10 +4,15 @@
import sys
+from subprocess import CalledProcessError
+
from ipalib import api
+from ipalib import errors
from ipaplatform.paths import paths
+from ipaplatform import services
from ipapython import ipautil
from ipapython import sysrestore
+from ipapython.dn import DN
from ipapython.ipa_log_manager import root_logger
from ipapython.ipaldap import AUTOBIND_ENABLED
from ipapython.ipautil import user_input
@@ -23,6 +28,67 @@ ip_addresses = []
dns_forwarders = []
reverse_zones = []
+NEW_MASTER_MARK = 'NEW_DNSSEC_MASTER'
+
+
+def _find_dnssec_enabled_zones(conn):
+ search_kw = {'idnssecinlinesigning': True}
+ dnssec_enabled_filter = conn.make_filter(search_kw)
+ dn = DN('cn=dns', api.env.basedn)
+ try:
+ entries, truncated = conn.find_entries(
+ base_dn=dn, filter=dnssec_enabled_filter, attrs_list=['idnsname'])
+ except errors.NotFound:
+ return []
+ else:
+ return [entry.single_value['idnsname'] for entry in entries
+ if 'idnsname' in entry]
+
+
+def _is_master():
+ # test if server is DNSSEC key master
+ masters = opendnssecinstance.get_dnssec_key_masters(api.Backend.ldap2)
+ if api.env.host not in masters:
+ raise RuntimeError("Current server is not DNSSEC key master")
+
+
+def _disable_dnssec():
+ fstore = sysrestore.FileStore(paths.SYSRESTORE)
+
+ ods = opendnssecinstance.OpenDNSSECInstance(
+ fstore, ldapi=True, autobind=AUTOBIND_ENABLED)
+ ods.realm = api.env.realm
+
+ ods_exporter = odsexporterinstance.ODSExporterInstance(fstore, ldapi=True)
+ ods_exporter.realm = api.env.realm
+
+ # unconfigure services first
+ ods.uninstall() # needs keytab to flush the latest ods database
+ ods_exporter.uninstall()
+
+ ods.ldap_connect()
+ ods.ldap_disable('DNSSEC', api.env.host, api.env.basedn)
+
+ ods_exporter.ldap_connect()
+ ods_exporter.ldap_disable('DNSKeyExporter', api.env.host, api.env.basedn)
+ ods_exporter.remove_service()
+
+ ods.ldap_disconnect()
+ ods_exporter.ldap_disconnect()
+
+ conn = api.Backend.ldap2
+ dn = DN(('cn', 'DNSSEC'), ('cn', api.env.host), ('cn', 'masters'),
+ ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
+ try:
+ entry = conn.get_entry(dn)
+ except errors.NotFound:
+ pass
+ else:
+ ipa_config = entry.get('ipaConfigString', [])
+ if opendnssecinstance.KEYMASTER in ipa_config:
+ ipa_config.remove(opendnssecinstance.KEYMASTER)
+ conn.update_entry(entry)
+
def install_check(standalone, replica, options, hostname):
global ip_addresses
@@ -41,14 +107,22 @@ def install_check(standalone, replica, options, hostname):
print " * Configure ipa-ods-exporter (required by DNSSEC key master)"
print " * Configure OpenDNSSEC (required by DNSSEC key master)"
print " * Generate DNSSEC master key (required by DNSSEC key master)"
+ elif options.disable_dnssec_master:
+ print " * Unconfigure ipa-ods-exporter"
+ print " * Unconfigure OpenDNSSEC"
+ print ""
+ print "No new zones will be signed without DNSSEC key master IPA server."
+ print ""
+ print ("Please copy file from %s after uninstallation. This file is needed "
+ "on new DNSSEC key " % paths.IPA_KASP_DB_BACKUP)
+ print "master server"
print ""
print "NOTE: DNSSEC zone signing is not enabled by default"
print ""
if options.dnssec_master:
print "DNSSEC support is experimental!"
print ""
- print "Plan carefully, current version doesn't allow you to move DNSSEC"
- print "key master to different server and master cannot be uninstalled"
+ print "Plan carefully, replacing DNSSEC key master is not recommended"
print ""
print ""
print "To accept the default shown in brackets, press the Enter key."
@@ -59,22 +133,79 @@ def install_check(standalone, replica, options, hostname):
"Do you want to setup this IPA server as DNSSEC key master?",
False)):
sys.exit("Aborted")
+ elif (options.disable_dnssec_master and not options.unattended and not
+ ipautil.user_input(
+ "Do you want to disable current DNSSEC key master?",
+ False)):
+ sys.exit("Aborted")
# Check bind packages are installed
if not (bindinstance.check_inst(options.unattended) and
dnskeysyncinstance.check_inst()):
sys.exit("Aborting installation.")
- if options.dnssec_master:
+ if options.disable_dnssec_master:
+ _is_master()
+
+ if options.disable_dnssec_master or options.dnssec_master:
+ dnssec_zones = _find_dnssec_enabled_zones(api.Backend.ldap2)
+
+ if options.disable_dnssec_master:
+ if dnssec_zones and not options.force:
+ raise RuntimeError(
+ "Cannot disable DNSSEC key master, DNSSEC signing is still "
+ "enabled for following zone(s): %s\n"
+ "Use --force option to skip this check." %
+ ", ".join([str(zone) for zone in dnssec_zones]))
+ elif options.dnssec_master:
# check opendnssec packages are installed
if not opendnssecinstance.check_inst():
sys.exit("Aborting installation")
+ if options.kasp_db_file:
+ dnskeysyncd = services.service('ipa-dnskeysyncd')
+
+ if not dnskeysyncd.is_installed():
+ raise RuntimeError("ipa-dnskeysyncd is not configured on this "
+ "server, you cannot reuse OpenDNSSEC "
+ "database (kasp.db file)")
+
+ # check if replica can be the DNSSEC master
+ named = services.knownservices.named
+ ods_enforcerd = services.knownservices.ods_enforcerd
+ cmd = [paths.IPA_DNSKEYSYNCD_REPLICA]
+ environment = {
+ "SOFTHSM2_CONF": paths.DNSSEC_SOFTHSM2_CONF,
+ }
+
+ # stop dnskeysyncd before test
+ dnskeysyncd_running = dnskeysyncd.is_running()
+ dnskeysyncd.stop()
+ try:
+ ipautil.run(cmd, env=environment,
+ runas=ods_enforcerd.get_user_name(),
+ suplementary_groups=[named.get_group_name()])
+ except CalledProcessError as e:
+ root_logger.debug("%s", e)
+ raise RuntimeError("IPA server cannot be the new DNSSEC master "
+ "(some keys are missing)")
+ finally:
+ if dnskeysyncd_running:
+ dnskeysyncd.start()
+ elif dnssec_zones and not options.force:
+ # some zones have --dnssec=true, make sure a user really want to
+ # install new database
+ raise RuntimeError(
+ "DNSSEC is enabled for following zone(s): %s\n"
+ "Please use option --kasp-db to keep current OpenDNSSEC "
+ "database or use --force option to skip this check." %
+ ", ".join([str(zone) for zone in dnssec_zones]))
+
fstore = sysrestore.FileStore(paths.SYSRESTORE)
if options.dnssec_master:
ods = opendnssecinstance.OpenDNSSECInstance(
- fstore, ldapi=True, autobind=AUTOBIND_ENABLED)
+ fstore, ldapi=True)
ods.realm = api.env.realm
dnssec_masters = ods.get_masters()
# we can reinstall current server if it is dnssec master
@@ -126,6 +257,11 @@ def install(standalone, replica, options):
global dns_forwarders
global reverse_zones
+ local_dnskeysyncd_dn = DN(('cn', 'DNSKeySync'), ('cn', api.env.host),
+ ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
+ api.env.basedn)
+ conn = api.Backend.ldap2
+
fstore = sysrestore.FileStore(paths.SYSRESTORE)
conf_ntp = ntpinstance.NTPInstance(fstore).is_enabled()
@@ -149,13 +285,15 @@ def install(standalone, replica, options):
dnskeysyncd = dnskeysyncinstance.DNSKeySyncInstance(fstore, ldapi=True)
dnskeysyncd.create_instance(api.env.host, api.env.realm)
if options.dnssec_master:
- ods = opendnssecinstance.OpenDNSSECInstance(fstore, ldapi=True,
- autobind=AUTOBIND_ENABLED)
+ ods = opendnssecinstance.OpenDNSSECInstance(fstore, ldapi=True)
ods_exporter = odsexporterinstance.ODSExporterInstance(
- fstore, ldapi=True, autobind=AUTOBIND_ENABLED)
+ fstore, ldapi=True)
ods_exporter.create_instance(api.env.host, api.env.realm)
- ods.create_instance(api.env.host, api.env.realm)
+ ods.create_instance(api.env.host, api.env.realm,
+ kasp_db_file=options.kasp_db_file)
+ elif options.disable_dnssec_master:
+ _disable_dnssec()
dnskeysyncd.start_dnskeysyncd()
bind.start_named()
diff --git a/ipaserver/install/odsexporterinstance.py b/ipaserver/install/odsexporterinstance.py
index 5b6245bc4..c37095cfc 100644
--- a/ipaserver/install/odsexporterinstance.py
+++ b/ipaserver/install/odsexporterinstance.py
@@ -15,12 +15,12 @@ from ipapython.dn import DN
from ipapython import sysrestore, ipautil, ipaldap
from ipaplatform.paths import paths
from ipaplatform import services
-from ipalib import errors
+from ipalib import errors, api
class ODSExporterInstance(service.Service):
def __init__(self, fstore=None, dm_password=None, ldapi=False,
- start_tls=False, autobind=ipaldap.AUTOBIND_DISABLED):
+ start_tls=False, autobind=ipaldap.AUTOBIND_ENABLED):
service.Service.__init__(
self, "ipa-ods-exporter",
service_desc="IPA OpenDNSSEC exporter daemon",
@@ -150,6 +150,14 @@ class ODSExporterInstance(service.Service):
def __start(self):
self.start()
+ def remove_service(self):
+ dns_exporter_principal = ("ipa-ods-exporter/%s@%s" % (self.fqdn,
+ self.realm))
+ try:
+ api.Command.service_del(dns_exporter_principal)
+ except errors.NotFound:
+ pass
+
def uninstall(self):
if not self.is_configured():
return
diff --git a/ipaserver/install/opendnssecinstance.py b/ipaserver/install/opendnssecinstance.py
index 538475985..d68691fa3 100644
--- a/ipaserver/install/opendnssecinstance.py
+++ b/ipaserver/install/opendnssecinstance.py
@@ -9,6 +9,9 @@ import os
import pwd
import grp
import stat
+import shutil
+
+from subprocess import CalledProcessError
import _ipap11helper
@@ -31,7 +34,7 @@ def get_dnssec_key_masters(conn):
"""
assert conn is not None
- dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
+ dn = DN(api.env.container_masters, api.env.basedn)
filter_attrs = {
u'cn': u'DNSSEC',
@@ -62,7 +65,7 @@ def check_inst():
class OpenDNSSECInstance(service.Service):
def __init__(self, fstore=None, dm_password=None, ldapi=False,
- start_tls=False, autobind=ipaldap.AUTOBIND_DISABLED):
+ start_tls=False, autobind=ipaldap.AUTOBIND_ENABLED):
service.Service.__init__(
self, "ods-enforcerd",
service_desc="OpenDNSSEC enforcer daemon",
@@ -94,12 +97,14 @@ class OpenDNSSECInstance(service.Service):
self.ldap_connect()
return get_dnssec_key_masters(self.admin_conn)
- def create_instance(self, fqdn, realm_name, generate_master_key=True):
+ def create_instance(self, fqdn, realm_name, generate_master_key=True,
+ kasp_db_file=None):
self.backup_state("enabled", self.is_enabled())
self.backup_state("running", self.is_running())
self.fqdn = fqdn
self.realm = realm_name
self.suffix = ipautil.realm_to_suffix(self.realm)
+ self.kasp_db_file = kasp_db_file
try:
self.stop()
@@ -152,6 +157,21 @@ class OpenDNSSECInstance(service.Service):
except errors.DuplicateEntry:
root_logger.error("DNSSEC service already exists")
+ # add the KEYMASTER identifier into ipaConfigString
+ # this is needed for the re-enabled DNSSEC master
+ dn = DN(('cn', 'DNSSEC'), ('cn', self.fqdn), api.env.container_masters,
+ api.env.basedn)
+ try:
+ entry = self.admin_conn.get_entry(dn, ['ipaConfigString'])
+ except errors.NotFound as e:
+ root_logger.error(
+ "DNSSEC service entry not found in the LDAP (%s)", e)
+ else:
+ config = entry.setdefault('ipaConfigString', [])
+ if KEYMASTER not in config:
+ config.append(KEYMASTER)
+ self.admin_conn.update_entry(entry)
+
def __setup_conf_files(self):
if not self.fstore.has_file(paths.OPENDNSSEC_CONF_FILE):
self.fstore.backup_file(paths.OPENDNSSEC_CONF_FILE)
@@ -250,7 +270,7 @@ class OpenDNSSECInstance(service.Service):
def __setup_dnssec(self):
# run once only
- if self.get_state("KASP_DB_configured"):
+ if self.get_state("KASP_DB_configured") and not self.kasp_db_file:
root_logger.debug("Already configured, skipping step")
return
@@ -259,13 +279,33 @@ class OpenDNSSECInstance(service.Service):
if not self.fstore.has_file(paths.OPENDNSSEC_KASP_DB):
self.fstore.backup_file(paths.OPENDNSSEC_KASP_DB)
- command = [
- paths.ODS_KSMUTIL,
- 'setup'
- ]
+ if self.kasp_db_file:
+ # copy user specified kasp.db to proper location and set proper
+ # privileges
+ shutil.copy(self.kasp_db_file, paths.OPENDNSSEC_KASP_DB)
+ os.chown(paths.OPENDNSSEC_KASP_DB, self.ods_uid, self.ods_gid)
+ os.chmod(paths.OPENDNSSEC_KASP_DB, 0660)
+
+ # regenerate zonelist.xml
+ ods_enforcerd = services.knownservices.ods_enforcerd
+ cmd = [paths.ODS_KSMUTIL, 'zonelist', 'export']
+ stdout, stderr, retcode = ipautil.run(cmd,
+ runas=ods_enforcerd.get_user_name())
+ with open(paths.OPENDNSSEC_ZONELIST_FILE, 'w') as zonelistf:
+ zonelistf.write(stdout)
+ os.chown(paths.OPENDNSSEC_ZONELIST_FILE,
+ self.ods_uid, self.ods_gid)
+ os.chmod(paths.OPENDNSSEC_ZONELIST_FILE, 0660)
- ods_enforcerd = services.knownservices.ods_enforcerd
- ipautil.run(command, stdin="y", runas=ods_enforcerd.get_user_name())
+ else:
+ # initialize new kasp.db
+ command = [
+ paths.ODS_KSMUTIL,
+ 'setup'
+ ]
+
+ ods_enforcerd = services.knownservices.ods_enforcerd
+ ipautil.run(command, stdin="y", runas=ods_enforcerd.get_user_name())
def __setup_dnskeysyncd(self):
# set up dnskeysyncd this is DNSSEC master
@@ -286,6 +326,44 @@ class OpenDNSSECInstance(service.Service):
running = self.restore_state("running")
enabled = self.restore_state("enabled")
+ # stop DNSSEC services before backing up kasp.db
+ try:
+ self.stop()
+ except Exception:
+ pass
+
+ ods_exporter = services.service('ipa-ods-exporter')
+ try:
+ ods_exporter.stop()
+ except Exception:
+ pass
+
+ # remove directive from ipa-dnskeysyncd, this server is not DNSSEC
+ # master anymore
+ installutils.set_directive(paths.SYSCONFIG_IPA_DNSKEYSYNCD,
+ 'ISMASTER', None,
+ quotes=False, separator='=')
+
+ if ipautil.file_exists(paths.OPENDNSSEC_KASP_DB):
+
+ # force to export data
+ ods_enforcerd = services.knownservices.ods_enforcerd
+ cmd = [paths.IPA_ODS_EXPORTER, 'ipa-full-update']
+ try:
+ ipautil.run(cmd, runas=ods_enforcerd.get_user_name())
+ except CalledProcessError:
+ root_logger.debug("OpenDNSSEC database has not been updated")
+
+ try:
+ shutil.copy(paths.OPENDNSSEC_KASP_DB,
+ paths.IPA_KASP_DB_BACKUP)
+ except IOError as e:
+ root_logger.error(
+ "Unable to backup OpenDNSSEC database: %s", e)
+ else:
+ root_logger.info("OpenDNSSEC database backed up in %s",
+ paths.IPA_KASP_DB_BACKUP)
+
for f in [paths.OPENDNSSEC_CONF_FILE, paths.OPENDNSSEC_KASP_FILE,
paths.OPENDNSSEC_KASP_DB, paths.SYSCONFIG_ODS]:
try:
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index 72376357b..b9bf3f34b 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -1326,6 +1326,25 @@ class ServerDNS(common.Installable, core.Group, core.Composite):
description="Setup server to be DNSSEC key master",
)
+ disable_dnssec_master = Knob(
+ bool, False,
+ initializable=False,
+ description="Disable the DNSSEC master on this server",
+ )
+
+ kasp_db_file = Knob(
+ str, None,
+ initializable=False,
+ description="Copy OpenDNSSEC metadata from the specified file (will "
+ "not create a new kasp.db file)",
+ )
+
+ force = Knob(
+ bool, False,
+ initializable=False,
+ description="Force install",
+ )
+
zonemgr = Knob(
str, None,
description=("DNS zone manager e-mail address. Defaults to "
@@ -1614,7 +1633,6 @@ class Server(common.Installable, common.Interactive, core.Composite):
self.ca_cert_files = self.ca.ca_cert_files
self.subject = self.ca.subject
self.ca_signing_algorithm = self.ca.ca_signing_algorithm
-
self.setup_dns = self.dns.setup_dns
self.forwarders = self.dns.forwarders
self.no_forwarders = self.dns.no_forwarders
@@ -1622,6 +1640,9 @@ class Server(common.Installable, common.Interactive, core.Composite):
self.no_reverse = self.dns.no_reverse
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
+ self.kasp_db_file = self.dns.kasp_db_file
+ self.force = self.dns.force
self.zonemgr = self.dns.zonemgr
self.no_host_dns = self.dns.no_host_dns
self.no_dns_sshfp = self.dns.no_dns_sshfp
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index b2dc3dd75..a78eeb331 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -691,6 +691,31 @@ class ReplicaDNS(common.Installable, core.Group, core.Composite):
description="Disable DNSSEC validation",
)
+ dnssec_master = Knob(
+ bool, False,
+ initializable=False,
+ description="Setup server to be DNSSEC key master",
+ )
+
+ disable_dnssec_master = Knob(
+ bool, False,
+ initializable=False,
+ description="Disable the DNSSEC master on this server",
+ )
+
+ force = Knob(
+ bool, False,
+ initializable=False,
+ description="Force install",
+ )
+
+ kasp_db_file = Knob(
+ str, None,
+ initializable=False,
+ description="Copy OpenDNSSEC metadata from the specified file (will "
+ "not create a new kasp.db file)",
+ )
+
no_host_dns = Knob(
bool, False,
description="Do not use DNS for hostname lookup during installation",
@@ -839,7 +864,10 @@ class Replica(common.Installable, common.Interactive, core.Composite):
self.reverse_zones = self.dns.reverse_zones
self.no_reverse = self.dns.no_reverse
self.no_dnssec_validation = self.dns.no_dnssec_validation
- self.dnssec_master = False
+ self.dnssec_master = self.dns.dnssec_master
+ self.disable_dnssec_master = self.dns.disable_dnssec_master
+ self.kasp_db_file = self.dns.kasp_db_file
+ self.force = self.dns.force
self.zonemgr = None
self.no_host_dns = self.dns.no_host_dns
self.no_dns_sshfp = self.dns.no_dns_sshfp