This patch allows to disable DNSSEC key master on IPA server, or replace
current DNSSEC key master with another IPA server.
Only for master branch.
https://fedorahosted.org/freeipa/ticket/4657
Patches attached.
--
Martin Basti
From 68ce33509c3ea12a2af9401e6856ab14a812ddd0 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Wed, 20 May 2015 17:49:08 +0200
Subject: [PATCH 2/2] DNSSEC: update message
https://fedorahosted.org/freeipa/ticket/4657
---
install/tools/ipa-replica-manage | 1 +
1 file changed, 1 insertion(+)
diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage
index 4f92c0c927b3267eeb466a06d3283823c05437b0..f19c13e4af3746474e915b16c600e548c16b2f72 100755
--- a/install/tools/ipa-replica-manage
+++ b/install/tools/ipa-replica-manage
@@ -701,6 +701,7 @@ def del_master(realm, hostname, options):
dnssec_masters = opendnssecinstance.get_dnssec_key_masters(delrepl.conn)
if hostname in dnssec_masters:
print "Replica is active DNSSEC key master. Uninstall could break your DNS system."
+ print "Please disable or replace DNSSEC key master first."
sys.exit("Deletion aborted")
# Pick CA renewal master
--
2.1.0
From 3fb190e9b687cd0d49c0331521d687279e1c7f1a Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Wed, 13 May 2015 14:45:32 +0200
Subject: [PATCH 1/2] 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
--replace-dnssec-master=IPA_SERVER DNSSEC master will be replaced, by
IPA_SERVER (required to rerun ipa-dns-install wit appropriate options).
--dnssec-master --kasp-db=FILE This configure new DNSSEC master server, kasp.db from old server is required
https://fedorahosted.org/freeipa/ticket/4657
---
install/tools/ipa-dns-install | 28 ++++-
ipaplatform/base/paths.py | 1 +
ipaserver/install/dns.py | 195 +++++++++++++++++++++++++++++--
ipaserver/install/odsexporterinstance.py | 12 +-
ipaserver/install/opendnssecinstance.py | 52 +++++++--
5 files changed, 268 insertions(+), 20 deletions(-)
diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index fd9311657e813988310db2be604ca68d26936af5..711749ccfc6851338095cb20dfc40b2ff930c747 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -61,6 +61,17 @@ def parse_options():
help="DNS zone manager e-mail address. Defaults to hostmaster@DOMAIN")
parser.add_option("-U", "--unattended", dest="unattended", action="store_true",
default=False, help="unattended installation never prompts the user")
+ parser.add_option("--disable-dnssec-master", dest="disable_dnssec_master",
+ action="store_true", default=False, help="Disable DNSSEC "
+ "master on this server")
+ parser.add_option("--replace-dnssec-master", dest="replace_dnssec_master",
+ type="string", metavar="IPA_DNS_SERVER_HOSTNAME",
+ action="store", help="Replace current DNSSEC master "
+ "with specified IPA server")
+ parser.add_option("--kasp-db", dest="kasp_db_file", type="string",
+ metavar="FILE", action="store", help="Do not create new "
+ "kasp.db database for DNSSEC metadata, but copy metadata "
+ "from the specified file")
options, args = parser.parse_args()
safe_options = parser.get_safe_opts(options)
@@ -70,10 +81,17 @@ def parse_options():
elif options.reverse_zones and options.no_reverse:
parser.error("You cannot specify a --reverse-zone option together with --no-reverse")
+ if options.disable_dnssec_master and options.replace_dnssec_master:
+ parser.error("You cannot specify a --disable-dnssec-master option "
+ "together with --replace-dnssec-master")
+
if options.unattended:
if not options.forwarders and not options.no_forwarders:
parser.error("You must specify at least one --forwarder option or --no-forwarders option")
+ if options.kasp_db_file and not ipautil.file_exists(options.kasp_db_file):
+ parser.error("File %s does not exist" % options.kasp_db_file)
+
if options.dm_password:
print ("WARNING: Option -p/--ds-password is deprecated "
"and should not be used anymore.")
@@ -106,8 +124,14 @@ def main():
options.setup_ca = False
- dns_installer.install_check(True, False, options, hostname=api.env.host)
- dns_installer.install(True, False, options)
+ if options.disable_dnssec_master:
+ dns_installer.disable_dnssec_master(options.unattended)
+ elif options.replace_dnssec_master:
+ dns_installer.replace_dnssec_master(options.replace_dnssec_master,
+ options.unattended)
+ else:
+ dns_installer.install_check(True, False, options, hostname=api.env.host)
+ dns_installer.install(True, False, options)
# Restart http instance to make sure that python-dns has the right resolver
# https://bugzilla.redhat.com/show_bug.cgi?id=800368
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 9ba87523b5619188f02bdad6c23d2446a2c4b0f2..aac7a2b651d81b9653721bdc95747a2d7b1eab64 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -143,6 +143,7 @@ class BasePathNamespace(object):
KRA_AGENT_PEM = "/etc/httpd/alias/kra-agent.pem"
CACERT_P12 = "/root/cacert.p12"
ROOT_IPA_CSR = "/root/ipa.csr"
+ ROOT_IPA_KASP_DB_BACKUP = "/root/ipa-kasp.db.backup"
ROOT_TMP_CA_P12 = "/root/tmp-ca.p12"
NAMED_PID = "/run/named/named.pid"
IP = "/sbin/ip"
diff --git a/ipaserver/install/dns.py b/ipaserver/install/dns.py
index 8d9570d68c5c84116839371f3ce8e73d71da2db5..75f7002efe6fc56e6fc125f9157833112ef5a770 100644
--- a/ipaserver/install/dns.py
+++ b/ipaserver/install/dns.py
@@ -5,9 +5,11 @@
import sys
from ipalib import api
+from ipalib import errors
from ipaplatform.paths import paths
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 +25,152 @@ 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)
+ ods_exporter.realm = api.env.realm
+
+ 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.uninstall()
+ ods_exporter.uninstall()
+
+ 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 disable_dnssec_master(unattended):
+ print "=============================================================================="
+ print "This program will disable DNSSEC key master on current IPA server."
+ print ""
+ print "No new zones will be signed without DNSSEC key master IPA server."
+ print ""
+ print "This includes:"
+ print " * Unconfigure ipa-ods-exporter"
+ print " * Unconfigure OpenDNSSEC"
+ print ""
+
+ if (not unattended and not ipautil.user_input(
+ "Do you want to disable current DNSSEC key master?",
+ False)):
+ raise RuntimeError("Cancelled by user.")
+
+ # test if server is DNSSEC key master
+ _is_master()
+
+ dnssec_zones = _find_dnssec_enabled_zones(api.Backend.ldap2)
+ if dnssec_zones:
+ raise RuntimeError("Cannot disable DNSSEC key master, DNSSEC signing "
+ "is still enabled for following zone(s): %s." %
+ ", ".join(dnssec_zones))
+ _disable_dnssec()
+
+
+def replace_dnssec_master(new_master, unattended):
+ print "=============================================================================="
+ print "This program will disable DNSSEC key master on current IPA server "
+ print "and mark specified server to be new DNSSEC key master."
+ print ""
+ print "BE CAREFUL, replacing DNSSEC key masters is not recommended, "
+ print "it may break your DNSSEC signatures."
+ print ""
+ print "Replacing DNSSEC key master requires manual configuration, please "
+ print "read documentation."
+ print ("Please copy file from %s after uninstallation. This file is needed "
+ "on new DNSSEC key " % paths.ROOT_IPA_KASP_DB_BACKUP)
+ print "master server"
+ print ""
+ print "This includes:"
+ print (" * Check the IPA server %s if can be new DNSSEC key master"
+ % new_master)
+ print " * Unconfigure ipa-ods-exporter"
+ print " * Unconfigure OpenDNSSEC"
+ print ""
+
+ if (not unattended and not ipautil.user_input(
+ "Do you want to replace current DNSSEC key master?",
+ False)):
+ raise RuntimeError("Cancelled by user.")
+
+ # test if server is DNSSEC key master
+ _is_master()
+
+ # detect if new DNSSEC key master has ipa-dnskeysyncd installed to be sure
+ # the DNSSEC keys are there and we will not lose them
+ dn = DN(('cn', new_master), ('cn', 'masters'), ('cn', 'ipa'),
+ ('cn', 'etc'), api.env.basedn)
+ dn_keysync = DN(('cn', 'DNSKeySync'), dn)
+
+ conn = api.Backend.ldap2
+
+ try:
+ conn.get_entry(dn)
+ except errors.NotFound:
+ raise RuntimeError("Could not find IPA server %s" % new_master)
+
+ try:
+ entry = conn.get_entry(dn_keysync)
+ except errors.NotFound:
+ raise RuntimeError("IPA server %s has not enabled or installed service"
+ " ipa-dnskeysyncd installed." % new_master)
+ else:
+ ipa_config = entry.get('ipaConfigString', [])
+ if NEW_MASTER_MARK in ipa_config:
+ # new server was already marked to be DNSSEC master
+ pass
+ else:
+ ipa_config.append(NEW_MASTER_MARK)
+ conn.update_entry(entry)
+
+ _disable_dnssec()
+
def install_check(standalone, replica, options, hostname):
global ip_addresses
@@ -47,8 +195,7 @@ def install_check(standalone, replica, options, hostname):
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."
@@ -69,12 +216,30 @@ def install_check(standalone, replica, options, hostname):
# check opendnssec packages are installed
if not opendnssecinstance.check_inst():
sys.exit("Aborting installation")
+ if options.kasp_db_file:
+ local_dnskeysyncd_dn = DN(('cn', 'DNSKeySync'), ('cn', hostname),
+ ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
+ api.env.basedn)
+ conn = api.Backend.ldap2
+
+ try:
+ dnskeysync_entry = conn.get_entry(local_dnskeysyncd_dn)
+ except errors.NotFound:
+ raise RuntimeError(
+ "IPA server has not enabled or installed service "
+ "ipa-dnskeysyncd")
+ else:
+ ipa_config = dnskeysync_entry.get('ipaConfigString', [])
+ if not NEW_MASTER_MARK in ipa_config:
+ # server is not marked as to be new DNSSEC key master
+ sys.exit("Server is not marked to be new DNSSEC master")
+
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 +291,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 +319,24 @@ 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)
+ if options.kasp_db_file:
+ # remove new DNSSEC master mark from dnskeysyncd service
+ # entry must exists
+ dnskeysync_entry = conn.get_entry(local_dnskeysyncd_dn)
+
+ ipa_config = dnskeysync_entry.get('ipaConfigString', [])
+ if NEW_MASTER_MARK in ipa_config:
+ ipa_config.remove(NEW_MASTER_MARK)
+ dnskeysync_entry['ipaConfigString'] = ipa_config
+ conn.update_entry(dnskeysync_entry)
+
dnskeysyncd.start_dnskeysyncd()
bind.start_named()
diff --git a/ipaserver/install/odsexporterinstance.py b/ipaserver/install/odsexporterinstance.py
index 5b6245bc48803b4c5545299e4386213319ae859a..c37095cfc3bba8c6724f45d23293bdf6f4a200ee 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 5384759858463b25e92b20b65ac633b79519d5dd..fd2cfbbd664edd3bc849c6893191d36762dcc3aa 100644
--- a/ipaserver/install/opendnssecinstance.py
+++ b/ipaserver/install/opendnssecinstance.py
@@ -9,6 +9,7 @@ import os
import pwd
import grp
import stat
+import shutil
import _ipap11helper
@@ -62,7 +63,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 +95,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()
@@ -250,7 +253,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 +262,21 @@ 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)
+ 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())
+ 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 +297,29 @@ 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
+
+ if ipautil.file_exists(paths.OPENDNSSEC_KASP_DB):
+ try:
+ shutil.copy(paths.OPENDNSSEC_KASP_DB,
+ paths.ROOT_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.ROOT_IPA_KASP_DB_BACKUP)
+
for f in [paths.OPENDNSSEC_CONF_FILE, paths.OPENDNSSEC_KASP_FILE,
paths.OPENDNSSEC_KASP_DB, paths.SYSCONFIG_ODS]:
try:
--
2.1.0
--
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code