From f0d829754e2e35225f0dfba980c9f4bae011407e Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 6 Jul 2018 00:04:39 +0200 Subject: [PATCH] Delay enabling services until end of installer Service entries in cn=FQDN,cn=masters,cn=ipa,cn=etc are no longer created as enabled. Instead they are flagged as configuredService. At the very end of the installer, the service entries are switched from configured to enabled service. - SRV records are created at the very end of the installer. - Dogtag installer only picks fully installed servers - Certmonger ignores all configured but not yet enabled servers. Fixes: https://pagure.io/freeipa/issue/7566 Signed-off-by: Christian Heimes Reviewed-By: Alexander Bokovoy Reviewed-By: Fraser Tweedale --- install/tools/ipa-adtrust-install | 6 +- install/tools/ipa-ca-install | 12 ++- install/tools/ipa-dns-install | 4 + ipaserver/dns_data_management.py | 18 +++-- ipaserver/install/adtrustinstance.py | 6 +- ipaserver/install/bindinstance.py | 6 +- ipaserver/install/cainstance.py | 2 +- ipaserver/install/dnskeysyncinstance.py | 4 +- ipaserver/install/httpinstance.py | 4 +- ipaserver/install/ipa_kra_install.py | 7 ++ ipaserver/install/krainstance.py | 2 +- ipaserver/install/krbinstance.py | 4 +- ipaserver/install/odsexporterinstance.py | 4 +- ipaserver/install/opendnssecinstance.py | 6 +- ipaserver/install/server/install.py | 18 +++-- ipaserver/install/server/replicainstall.py | 9 ++- ipaserver/install/service.py | 90 ++++++++++++++++++++-- ipaserver/plugins/serverrole.py | 7 +- 18 files changed, 161 insertions(+), 48 deletions(-) diff --git a/install/tools/ipa-adtrust-install b/install/tools/ipa-adtrust-install index 1484598adba5b1237f00cc55e95167d45a6b40d7..4258f489873b4095a6672e20f2ac5f2858b71982 100755 --- a/install/tools/ipa-adtrust-install +++ b/install/tools/ipa-adtrust-install @@ -31,7 +31,7 @@ import six from optparse import SUPPRESS_HELP # pylint: disable=deprecated-module from ipalib.install import sysrestore -from ipaserver.install import adtrust +from ipaserver.install import adtrust, service from ipaserver.install.installutils import ( read_password, check_server_configuration, @@ -210,6 +210,10 @@ def main(): adtrust.install_check(True, options, api) adtrust.install(True, options, fstore, api) + # Enable configured services and update DNS SRV records + service.enable_services(api.env.host) + api.Command.dns_update_system_records() + print(""" ============================================================================= Setup complete diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install index a3694007e5815d1c44f642057c749879d336dfc5..215a60ae744577de73a7260bbf3dea5a09a4d118 100755 --- a/install/tools/ipa-ca-install +++ b/install/tools/ipa-ca-install @@ -303,18 +303,26 @@ def main(): ) api.finalize() api.Backend.ldap2.connect() - domain_level = dsinstance.get_domain_level(api) + if domain_level > DOMAIN_LEVEL_0: promote(safe_options, options, filename) else: install(safe_options, options, filename) + # pki-spawn restarts 389-DS, reconnect + api.Backend.ldap2.close() + api.Backend.ldap2.connect() + + # Enable configured services and update DNS SRV records + service.enable_services(api.env.host) + api.Command.dns_update_system_records() + api.Backend.ldap2.disconnect() + # execute ipactl to refresh services status ipautil.run(['ipactl', 'start', '--ignore-service-failures'], raiseonerr=False) - api.Backend.ldap2.disconnect() fail_message = ''' Your system may be partly configured. diff --git a/install/tools/ipa-dns-install b/install/tools/ipa-dns-install index cb6c5d887f101135ca593ea6d4ed0caf51478a4c..04d1b140d2c79a0fa72d7df47d556643751bddb7 100755 --- a/install/tools/ipa-dns-install +++ b/install/tools/ipa-dns-install @@ -36,6 +36,7 @@ from ipapython.config import IPAOptionParser from ipapython.ipa_log_manager import standard_logging_setup, root_logger from ipaserver.install import dns as dns_installer +from ipaserver.install import service log_file_name = paths.IPASERVER_INSTALL_LOG @@ -145,6 +146,9 @@ def main(): dns_installer.install_check(True, api, False, options, hostname=api.env.host) dns_installer.install(True, False, options) + # Enable configured services and update DNS SRV records + service.enable_services(api.env.host) + api.Command.dns_update_system_records() # execute ipactl to refresh services status ipautil.run(['ipactl', 'start', '--ignore-service-failures'], diff --git a/ipaserver/dns_data_management.py b/ipaserver/dns_data_management.py index 6016d8a0044d487c3118f43f199b2a433facfa9a..e5987b4bd7b43d3920e9da917258153e448206b7 100644 --- a/ipaserver/dns_data_management.py +++ b/ipaserver/dns_data_management.py @@ -65,11 +65,11 @@ class IPASystemRecords(object): PRIORITY_HIGH = 0 PRIORITY_LOW = 50 - def __init__(self, api_instance): + def __init__(self, api_instance, all_servers=False): self.api_instance = api_instance self.domain_abs = DNSName(self.api_instance.env.domain).make_absolute() self.servers_data = {} - self.__init_data() + self.__init_data(all_servers=all_servers) def reload_data(self): """ @@ -89,14 +89,16 @@ class IPASystemRecords(object): def __get_location_suffix(self, location): return location + DNSName('_locations') + self.domain_abs - def __init_data(self): + def __init_data(self, all_servers=False): self.servers_data = {} - servers_result = self.api_instance.Command.server_find( - no_members=False, - servrole=u"IPA master", # only active, fully installed masters - )['result'] - for s in servers_result: + kwargs = dict(no_members=False) + if not all_servers: + # only active, fully installed masters] + kwargs["servrole"] = u"IPA master" + servers = self.api_instance.Command.server_find(**kwargs) + + for s in servers['result']: weight, location, roles = self.__get_server_attrs(s) self.servers_data[s['cn'][0]] = { 'weight': weight, diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py index b4db055045823ce8ae7e3b264e1442a085f81b2d..a7261b92ac227228b5b6af41db621ed2e5e96668 100644 --- a/ipaserver/install/adtrustinstance.py +++ b/ipaserver/install/adtrustinstance.py @@ -581,7 +581,7 @@ class ADTRUSTInstance(service.Service): self.print_msg(err_msg) self.print_msg("Add the following service records to your DNS " \ "server for DNS zone %s: " % zone) - system_records = IPASystemRecords(api) + system_records = IPASystemRecords(api, all_servers=True) adtrust_records = system_records.get_base_records( [self.fqdn], ["AD trust controller"], include_master_role=False, include_kerberos_realm=False) @@ -734,12 +734,12 @@ class ADTRUSTInstance(service.Service): # Note that self.dm_password is None for ADTrustInstance because # we ensure to be called as root and using ldapi to use autobind try: - self.ldap_enable('ADTRUST', self.fqdn, None, self.suffix) + self.ldap_configure('ADTRUST', self.fqdn, None, self.suffix) except (ldap.ALREADY_EXISTS, errors.DuplicateEntry): root_logger.info("ADTRUST Service startup entry already exists.") try: - self.ldap_enable('EXTID', self.fqdn, None, self.suffix) + self.ldap_configure('EXTID', self.fqdn, None, self.suffix) except (ldap.ALREADY_EXISTS, errors.DuplicateEntry): root_logger.info("EXTID Service startup entry already exists.") diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py index 03dce56aa0610b3dc530e6b2a185515be7956e8b..771c6b0483d07b20fbc8470397eed306651f4a8f 100644 --- a/ipaserver/install/bindinstance.py +++ b/ipaserver/install/bindinstance.py @@ -664,7 +664,7 @@ class BindInstance(service.Service): return normalize_zone(self.host_domain) == normalize_zone(self.domain) def create_file_with_system_records(self): - system_records = IPASystemRecords(self.api) + system_records = IPASystemRecords(self.api, all_servers=True) text = u'\n'.join( IPASystemRecords.records_list_from_zone( system_records.get_base_records() @@ -741,7 +741,7 @@ class BindInstance(service.Service): # Instead we reply on the IPA init script to start only enabled # components as found in our LDAP configuration tree try: - self.ldap_enable('DNS', self.fqdn, None, self.suffix) + self.ldap_configure('DNS', self.fqdn, None, self.suffix) except errors.DuplicateEntry: # service already exists (forced DNS reinstall) # don't crash, just report error @@ -1175,7 +1175,7 @@ class BindInstance(service.Service): except ValueError as error: root_logger.debug(error) - # disabled by default, by ldap_enable() + # disabled by default, by ldap_configure() if enabled: self.enable() diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 0c4d9bf9ad8ae11ac88523857845e16eb62651b9..62e9ad7de6f00eabb48f726a3931eb8acf0ba22b 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -1218,7 +1218,7 @@ class CAInstance(DogtagInstance): config = ['caRenewalMaster'] else: config = [] - self.ldap_enable('CA', self.fqdn, None, basedn, config) + self.ldap_configure('CA', self.fqdn, None, basedn, config) def setup_lightweight_ca_key_retrieval(self): if sysupgrade.get_upgrade_state('dogtag', 'setup_lwca_key_retrieval'): diff --git a/ipaserver/install/dnskeysyncinstance.py b/ipaserver/install/dnskeysyncinstance.py index 3849626e5a253667271913337d1a5aa4a72755bb..28468826d7194e7103e61c0ef3957b849e1be896 100644 --- a/ipaserver/install/dnskeysyncinstance.py +++ b/ipaserver/install/dnskeysyncinstance.py @@ -384,8 +384,8 @@ class DNSKeySyncInstance(service.Service): def __enable(self): try: - self.ldap_enable('DNSKeySync', self.fqdn, None, - self.suffix, self.extra_config) + self.ldap_configure('DNSKeySync', self.fqdn, None, + self.suffix, self.extra_config) except errors.DuplicateEntry: self.logger.error("DNSKeySync service already exists") diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index 7081c7418e76afbd1b4ae28deafefb6b264c62f0..2df51eaa77d3ee3246027a6bcbc4023dbad61160 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -200,7 +200,7 @@ class HTTPInstance(service.Service): # We do not let the system start IPA components on its own, # Instead we reply on the IPA init script to start only enabled # components as found in our LDAP configuration tree - self.ldap_enable('HTTP', self.fqdn, None, self.suffix) + self.ldap_configure('HTTP', self.fqdn, None, self.suffix) def configure_selinux_for_httpd(self): try: @@ -583,7 +583,7 @@ class HTTPInstance(service.Service): if running: self.restart() - # disabled by default, by ldap_enable() + # disabled by default, by ldap_configure() if enabled: self.enable() diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py index 9ebabe057513141ee76d238a3f20e76a27dd932e..3a639ac20f101293edd7449f8846a451469e2297 100644 --- a/ipaserver/install/ipa_kra_install.py +++ b/ipaserver/install/ipa_kra_install.py @@ -224,4 +224,11 @@ class KRAInstaller(KRAInstall): self.log.error(dedent(self.FAIL_MESSAGE)) raise + # pki-spawn restarts 389-DS, reconnect + api.Backend.ldap2.close() + api.Backend.ldap2.connect() + + # Enable configured services and update DNS SRV records + service.enable_services(api.env.host) + api.Command.dns_update_system_records() api.Backend.ldap2.disconnect() diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py index 915d3c3c6e038eeb6a8f94f1ed7f7008c0ef4ead..a23de3960f0789761b4b86227508236c80e97c2f 100644 --- a/ipaserver/install/krainstance.py +++ b/ipaserver/install/krainstance.py @@ -376,4 +376,4 @@ class KRAInstance(DogtagInstance): directives[nickname], cert, paths.KRA_CS_CFG_PATH) def __enable_instance(self): - self.ldap_enable('KRA', self.fqdn, None, self.suffix) + self.ldap_configure('KRA', self.fqdn, None, self.suffix) diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index 5971a30fc566f6e96ce0b08632772d33da5602d2..4041d1b5fb3c3cf3db78b6cb282ce5f17793a0e3 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -240,7 +240,7 @@ class KrbInstance(service.Service): # We do not let the system start IPA components on its own, # Instead we reply on the IPA init script to start only enabled # components as found in our LDAP configuration tree - self.ldap_enable('KDC', self.fqdn, None, self.suffix) + self.ldap_configure('KDC', self.fqdn, None, self.suffix) def __start_instance(self): try: @@ -598,7 +598,7 @@ class KrbInstance(service.Service): except ValueError as error: root_logger.debug(error) - # disabled by default, by ldap_enable() + # disabled by default, by ldap_configure() if enabled: self.enable() diff --git a/ipaserver/install/odsexporterinstance.py b/ipaserver/install/odsexporterinstance.py index 59f27f578dab5663b1a7b734dff3699a6996084d..1694704f967b0b2a5debe76252ae859ae8a47f2b 100644 --- a/ipaserver/install/odsexporterinstance.py +++ b/ipaserver/install/odsexporterinstance.py @@ -69,8 +69,8 @@ class ODSExporterInstance(service.Service): def __enable(self): try: - self.ldap_enable('DNSKeyExporter', self.fqdn, None, - self.suffix) + self.ldap_configure('DNSKeyExporter', self.fqdn, None, + self.suffix) except errors.DuplicateEntry: root_logger.error("DNSKeyExporter service already exists") diff --git a/ipaserver/install/opendnssecinstance.py b/ipaserver/install/opendnssecinstance.py index bc2974a2cf56e4ade1b778303c14f9ce05a8bf0f..92a46a9f6e318454084398ed625cf27a8250c2af 100644 --- a/ipaserver/install/opendnssecinstance.py +++ b/ipaserver/install/opendnssecinstance.py @@ -136,8 +136,8 @@ class OpenDNSSECInstance(service.Service): def __enable(self): try: - self.ldap_enable('DNSSEC', self.fqdn, None, - self.suffix, self.extra_config) + self.ldap_configure('DNSSEC', self.fqdn, None, + self.suffix, self.extra_config) except errors.DuplicateEntry: root_logger.error("DNSSEC service already exists") @@ -368,7 +368,7 @@ class OpenDNSSECInstance(service.Service): self.restore_state("kasp_db_configured") # just eat state - # disabled by default, by ldap_enable() + # disabled by default, by ldap_configure() if enabled: self.enable() diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py index 3651cde827ecf299e5570feed4936311f91749fb..dcdd9aabb746c4973b3f73934d94225503728f0b 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -869,14 +869,6 @@ def install(installer): if options.setup_dns: dns.install(False, False, options) - else: - # Create a BIND instance - bind = bindinstance.BindInstance(fstore) - bind.setup(host_name, ip_addresses, realm_name, - domain_name, (), 'first', (), - zonemgr=options.zonemgr, - no_dnssec_validation=options.no_dnssec_validation) - bind.create_file_with_system_records() if options.setup_adtrust: adtrust.install(False, options, fstore, api) @@ -908,6 +900,16 @@ def install(installer): # Make sure the files we crated in /var/run are recreated at startup tasks.configure_tmpfiles() + # Enable configured services and update DNS SRV records + service.enable_services(host_name) + api.Command.dns_update_system_records() + + if not options.setup_dns: + # After DNS and AD trust are configured and services are + # enabled, create a dummy instance to dump DNS configuration. + bind = bindinstance.BindInstance(fstore) + bind.create_file_with_system_records() + # Everything installed properly, activate ipa service. services.knownservices.ipa.enable() diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index b10f761e3f643f9fa868451192fa4550b24b6b16..59fec452c674b9941ce731748dd63985a08fefc0 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -1518,14 +1518,10 @@ def install(installer): if options.setup_dns: dns.install(False, True, options, api) - else: - api.Command.dns_update_system_records() if options.setup_adtrust: adtrust.install(False, options, fstore, api) - api.Backend.ldap2.disconnect() - if not promote: # Call client install script service.print_msg("Configuring client side components") @@ -1556,6 +1552,11 @@ def install(installer): # Make sure the files we crated in /var/run are recreated at startup tasks.configure_tmpfiles() + # Enable configured services and update DNS SRV records + service.enable_services(config.host_name) + api.Command.dns_update_system_records() + api.Backend.ldap2.disconnect() + # Everything installed properly, activate ipa service. services.knownservices.ipa.enable() diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py index 0523e914aa7debf6aaa82ddcce9b7b26c1833cd3..4271ebe06be03199165894f7a884e17f311896cb 100644 --- a/ipaserver/install/service.py +++ b/ipaserver/install/service.py @@ -24,6 +24,7 @@ import socket import datetime import traceback import tempfile +import warnings import six @@ -59,6 +60,10 @@ SERVICE_LIST = { 'DNSKeySync': ('ipa-dnskeysyncd', 110), } +CONFIGURED_SERVICE = u'configuredService' +ENABLED_SERVICE = 'enabledService' + + def print_msg(message, output_fd=sys.stdout): root_logger.debug(message) output_fd.write(message) @@ -120,7 +125,7 @@ def find_providing_server(svcname, conn, host_name=None, api=api): """ dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) query_filter = conn.make_filter({'objectClass': 'ipaConfigObject', - 'ipaConfigString': 'enabledService', + 'ipaConfigString': ENABLED_SERVICE, 'cn': svcname}, rules='&') try: entries, _trunc = conn.find_entries(filter=query_filter, base_dn=dn) @@ -217,6 +222,53 @@ def set_service_entry_config(name, fqdn, config_values, raise e +def enable_services(fqdn): + """Change all configured services to enabled + + Server.ldap_configure() only marks a service as configured. Services + are enabled at the very end of installation. + + Note: DNS records must be updated with dns_update_system_records, too. + + :param fqdn: hostname of server + """ + ldap2 = api.Backend.ldap2 + search_base = DN(('cn', fqdn), api.env.container_masters, api.env.basedn) + search_filter = ldap2.make_filter( + { + 'objectClass': 'ipaConfigObject', + 'ipaConfigString': CONFIGURED_SERVICE + }, + rules='&' + ) + entries = ldap2.get_entries( + search_base, + filter=search_filter, + scope=api.Backend.ldap2.SCOPE_ONELEVEL, + attrs_list=['cn', 'ipaConfigString'] + ) + for entry in entries: + name = entry['cn'] + cfgstrings = entry.setdefault('ipaConfigString', []) + for value in list(cfgstrings): + if value.lower() == CONFIGURED_SERVICE.lower(): + cfgstrings.remove(value) + if not case_insensitive_attr_has_value(cfgstrings, ENABLED_SERVICE): + cfgstrings.append(ENABLED_SERVICE) + + try: + ldap2.update_entry(entry) + except errors.EmptyModlist: + root_logger.debug("Nothing to do for service %s", name) + except Exception: + root_logger.exception( + "failed to set service %s config values", name + ) + raise + else: + root_logger.debug("Enabled service %s for %s", name, fqdn) + + class Service(object): def __init__(self, service_name, service_desc=None, sstore=None, fstore=None, api=api, realm_name=None, @@ -522,7 +574,35 @@ class Service(object): self.steps = [] def ldap_enable(self, name, fqdn, dm_password=None, ldap_suffix='', - config=[]): + config=()): + """Legacy function, all services should use ldap_configure() + """ + warnings.warn( + "ldap_enable is deprecated, use ldap_configure instead.", + DeprecationWarning, + stacklevel=2 + ) + self._ldap_enable(ENABLED_SERVICE, name, fqdn, ldap_suffix, config) + + def ldap_configure(self, name, fqdn, dm_password=None, ldap_suffix='', + config=()): + """Create or modify service entry in cn=masters,cn=ipa,cn=etc + + Contrary to ldap_enable(), the method only sets + ipaConfigString=configuredService. ipaConfigString=enabledService + is set at the very end of the installation process, to ensure that + other machines see this master/replica after it is fully installed. + + To switch all configured services to enabled, use:: + + ipaserver.install.service.enable_services(api.env.host) + api.Command.dns_update_system_records() + """ + self._ldap_enable( + CONFIGURED_SERVICE, name, fqdn, ldap_suffix, config + ) + + def _ldap_enable(self, value, name, fqdn, ldap_suffix, config): extra_config_opts = [ ' '.join([u'startOrder', unicode(SERVICE_LIST[name][1])]) ] @@ -533,7 +613,7 @@ class Service(object): set_service_entry_config( name, fqdn, - [u'enabledService'], + [value], ldap_suffix=ldap_suffix, post_add_config=extra_config_opts) @@ -559,7 +639,7 @@ class Service(object): # case insensitive for value in entry.get('ipaConfigString', []): - if value.lower() == u'enabledservice': + if value.lower() == ENABLED_SERVICE: entry['ipaConfigString'].remove(value) break @@ -672,7 +752,7 @@ class SimpleServiceInstance(Service): if self.gensvc_name == None: self.enable() else: - self.ldap_enable(self.gensvc_name, self.fqdn, None, self.suffix) + self.ldap_configure(self.gensvc_name, self.fqdn, None, self.suffix) def is_installed(self): return self.service.is_installed() diff --git a/ipaserver/plugins/serverrole.py b/ipaserver/plugins/serverrole.py index db88b3885c538c2800f6e4a1d649083859d43641..35b199387b4d3512d39f197d125c20571e23ad7a 100644 --- a/ipaserver/plugins/serverrole.py +++ b/ipaserver/plugins/serverrole.py @@ -15,16 +15,21 @@ IPA server roles """) + _(""" Get status of roles (DNS server, CA, etc.) provided by IPA masters. """) + _(""" +The status of a role is either enabled, configured, or absent. +""") + _(""" EXAMPLES: """) + _(""" Show status of 'DNS server' role on a server: ipa server-role-show ipa.example.com "DNS server" """) + _(""" Show status of all roles containing 'AD' on a server: - ipa server-role-find --server ipa.example.com --role='AD' + ipa server-role-find --server ipa.example.com --role="AD trust controller" """) + _(""" Show status of all configured roles on a server: ipa server-role-find ipa.example.com +""") + _(""" + Show implicit IPA master role: + ipa server-role-find --include-master """) -- 2.17.1