diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install index cf0fe80fe..5a5ceb1f3 100755 --- a/install/tools/ipa-ca-install +++ b/install/tools/ipa-ca-install @@ -35,6 +35,7 @@ from ipaserver.install.installutils import create_replica_config from ipaserver.install.installutils import check_creds, ReplicaConfig from ipaserver.install import dsinstance, ca from ipaserver.install import cainstance, service +from ipaserver.install import custodiainstance from ipapython import version from ipalib import api from ipalib.constants import DOMAIN_LEVEL_0 @@ -219,13 +220,17 @@ def install_replica(safe_options, options, filename): options.domain_name = config.domain_name options.dm_password = config.dirman_password options.host_name = config.host_name + options.ca_host_name = config.ca_host_name if os.path.exists(cafile): options.ca_cert_file = cafile else: options.ca_cert_file = None ca.install_check(True, config, options) - ca.install(True, config, options) + + custodia = custodiainstance.get_custodia_instance( + options, custodiainstance.CustodiaModes.CA_PEER) + ca.install(True, config, options, custodia=custodia) def install_master(safe_options, options): @@ -263,13 +268,17 @@ def install_master(safe_options, options): "Continue to configure the CA with these values?", False): sys.exit("Installation aborted") - ca.install(True, None, options) + # No CA peer available yet. + custodia = custodiainstance.get_custodia_instance( + options, custodiainstance.CustodiaModes.STANDALONE) + ca.install(True, None, options, custodia=custodia) # Run ipa-certupdate to add the new CA certificate to # certificate databases on this server. logger.info("Updating certificate databases.") ipa_certupdate.run_with_args(api) + def install(safe_options, options, filename): options.promote = False diff --git a/ipaserver/install/ca.py b/ipaserver/install/ca.py index efa8e9c8c..b82601b6d 100644 --- a/ipaserver/install/ca.py +++ b/ipaserver/install/ca.py @@ -20,10 +20,7 @@ from ipalib.install.service import enroll_only, master_install_only, replica_ins from ipaserver.install import sysupgrade from ipapython.install import typing from ipapython.install.core import group, knob, extend_knob -from ipaserver.install import (cainstance, - custodiainstance, - dsinstance, - bindinstance) +from ipaserver.install import cainstance, bindinstance, dsinstance from ipapython import ipautil, certdb from ipapython.admintool import ScriptError from ipaplatform import services @@ -240,12 +237,12 @@ def install_check(standalone, replica_config, options): "cannot continue." % (subject, db.secdir)) -def install(standalone, replica_config, options): - install_step_0(standalone, replica_config, options) - install_step_1(standalone, replica_config, options) +def install(standalone, replica_config, options, custodia): + install_step_0(standalone, replica_config, options, custodia=custodia) + install_step_1(standalone, replica_config, options, custodia=custodia) -def install_step_0(standalone, replica_config, options): +def install_step_0(standalone, replica_config, options, custodia): realm_name = options.realm_name dm_password = options.dm_password host_name = options.host_name @@ -278,9 +275,6 @@ def install_step_0(standalone, replica_config, options): else: cafile = os.path.join(replica_config.dir, 'cacert.p12') if options.promote: - custodia = custodiainstance.CustodiaInstance( - replica_config.host_name, - replica_config.realm_name) custodia.get_ca_keys( replica_config.ca_host_name, cafile, @@ -307,7 +301,9 @@ def install_step_0(standalone, replica_config, options): 'certmap.conf', 'subject_base', str(subject_base)) dsinstance.write_certmap_conf(realm_name, ca_subject) - ca = cainstance.CAInstance(realm_name, host_name=host_name) + ca = cainstance.CAInstance( + realm=realm_name, host_name=host_name, custodia=custodia + ) ca.configure_instance(host_name, dm_password, dm_password, subject_base=subject_base, ca_subject=ca_subject, @@ -326,7 +322,7 @@ def install_step_0(standalone, replica_config, options): use_ldaps=standalone) -def install_step_1(standalone, replica_config, options): +def install_step_1(standalone, replica_config, options, custodia): if replica_config is not None and not replica_config.setup_ca: return @@ -335,7 +331,9 @@ def install_step_1(standalone, replica_config, options): subject_base = options._subject_base basedn = ipautil.realm_to_suffix(realm_name) - ca = cainstance.CAInstance(realm_name, host_name=host_name) + ca = cainstance.CAInstance( + realm=realm_name, host_name=host_name, custodia=custodia + ) ca.stop('pki-tomcat') diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index f403c9de1..5b469e13f 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -64,7 +64,6 @@ from ipapython.ipa_log_manager import standard_logging_setup from ipaserver.secrets.kem import IPAKEMKeys from ipaserver.install import certs -from ipaserver.install import custodiainstance from ipaserver.install import dsinstance from ipaserver.install import installutils from ipaserver.install import ldapupdate @@ -298,7 +297,7 @@ class CAInstance(DogtagInstance): 'caSigningCert cert-pki-ca') server_cert_name = 'Server-Cert cert-pki-ca' - def __init__(self, realm=None, host_name=None): + def __init__(self, realm=None, host_name=None, custodia=None): super(CAInstance, self).__init__( realm=realm, subsystem="CA", @@ -323,6 +322,8 @@ class CAInstance(DogtagInstance): self.no_db_setup = False self.keytab = os.path.join( paths.PKI_TOMCAT, self.service_prefix + '.keytab') + # Custodia instance for RA key retrieval + self._custodia = custodia def configure_instance(self, host_name, dm_password, admin_password, pkcs12_info=None, master_host=None, csr_file=None, @@ -761,9 +762,7 @@ class CAInstance(DogtagInstance): self.configure_agent_renewal() def __import_ra_key(self): - custodia = custodiainstance.CustodiaInstance(host_name=self.fqdn, - realm=self.realm) - custodia.import_ra_key(self.master_host) + self._custodia.import_ra_key(self.master_host) self.__set_ra_cert_perms() self.configure_agent_renewal() diff --git a/ipaserver/install/custodiainstance.py b/ipaserver/install/custodiainstance.py index 1eb195ce0..3820eec76 100644 --- a/ipaserver/install/custodiainstance.py +++ b/ipaserver/install/custodiainstance.py @@ -2,6 +2,7 @@ from __future__ import print_function, absolute_import +import enum import logging from ipalib import api @@ -26,6 +27,77 @@ import pwd logger = logging.getLogger(__name__) +class CustodiaModes(enum.Enum): + # peer must have a CA + CA_PEER = 'Custodia CA peer' + # peer must have a CA, KRA preferred + KRA_PEER = 'Custodia KRA peer' + # any master will do + MASTER_PEER = 'Custodia master peer' + # standalone / local instance + STANDALONE = 'Custodia standalone' + + +def get_custodia_instance(config, mode): + """Create Custodia instance + + :param config: configuration/installer object + :param mode: CustodiaModes member + :return: CustodiaInstance object + + The config object must have the following attribute + + *host_name* + FQDN of the new replica/master + *realm_name* + Kerberos realm + *promote* + True, when instance will be promoted from client to replica + *master_host_name* (for *CustodiaModes.MASTER_PEER*) + hostname of a master (may not have a CA) + *ca_host_name* (for *CustodiaModes.CA_PEER*) + hostname of a master with CA + *kra_host_name* (for *CustodiaModes.KRA_PEER*) + hostname of a master with KRA or CA + + For promotion, the instance will upload new keys and retrieve secrets + from the same host. Therefore it uses *ca_host_name* instead of + *master_host_name* to create a replica with CA. + """ + assert isinstance(mode, CustodiaModes) + logger.info( + "Custodia client for '%r' with promotion %s.", + mode, 'yes' if config.promote else 'no' + ) + if config.promote: + if mode == CustodiaModes.CA_PEER: + # In case we install replica with CA, prefer CA host as source for + # all Custodia secret material. + custodia_master = config.ca_host_name + elif mode == CustodiaModes.KRA_PEER: + custodia_master = config.kra_host_name + elif mode == CustodiaModes.MASTER_PEER: + custodia_master = config.master_host_name + elif mode == CustodiaModes.STANDALONE: + custodia_master = None + else: + custodia_master = None + + if custodia_master is None: + # use ldapi with local dirsrv instance + logger.info("Custodia uses LDAPI.") + ldap_uri = None + else: + logger.info("Custodia uses '%s' as master peer.", custodia_master) + ldap_uri = 'ldap://{}'.format(custodia_master) + + return CustodiaInstance( + host_name=config.host_name, + realm=config.realm_name, + ldap_uri=ldap_uri + ) + + class CustodiaInstance(SimpleServiceInstance): def __init__(self, host_name=None, realm=None, ldap_uri=None): super(CustodiaInstance, self).__init__("ipa-custodia") @@ -54,14 +126,19 @@ class CustodiaInstance(SimpleServiceInstance): ipautil.flush_sync(f) def create_instance(self): - suffix = ipautil.realm_to_suffix(self.realm) + if self.ldap_uri is None or self.ldap_uri.startswith('ldapi://'): + # local case, ensure container exists + self.step("Making sure custodia container exists", + self.__create_container) + self.step("Generating ipa-custodia config file", self.__config_file) - self.step("Making sure custodia container exists", self.__create_container) self.step("Generating ipa-custodia keys", self.__gen_keys) - super(CustodiaInstance, self).create_instance(gensvc_name='KEYS', - fqdn=self.fqdn, - ldap_suffix=suffix, - realm=self.realm) + super(CustodiaInstance, self).create_instance( + gensvc_name='KEYS', + fqdn=self.fqdn, + ldap_suffix=ipautil.realm_to_suffix(self.realm), + realm=self.realm + ) sysupgrade.set_upgrade_state('custodia', 'installed', True) def uninstall(self): @@ -108,18 +185,6 @@ class CustodiaInstance(SimpleServiceInstance): logger.info("Secure server.keys mode") os.chmod(self.server_keys, 0o600) - def create_replica(self, master_host_name): - suffix = ipautil.realm_to_suffix(self.realm) - self.ldap_uri = 'ldap://%s' % master_host_name - self.master_host_name = master_host_name - - self.step("Generating ipa-custodia config file", self.__config_file) - self.step("Generating ipa-custodia keys", self.__gen_keys) - super(CustodiaInstance, self).create_instance(gensvc_name='KEYS', - fqdn=self.fqdn, - ldap_suffix=suffix, - realm=self.realm) - def __create_container(self): """ Runs the custodia update file to ensure custodia container is present. @@ -133,7 +198,7 @@ class CustodiaInstance(SimpleServiceInstance): updater.update([os.path.join(paths.UPDATES_DIR, '73-custodia.update')]) def import_ra_key(self, master_host_name): - cli = self.__CustodiaClient(server=master_host_name) + cli = self._get_custodia_client(server=master_host_name) # please note that ipaCert part has to stay here for historical # reasons (old servers expect you to ask for ra/ipaCert during # replication as they store the RA agent cert in an NSS database @@ -141,18 +206,14 @@ class CustodiaInstance(SimpleServiceInstance): cli.fetch_key('ra/ipaCert') def import_dm_password(self, master_host_name): - cli = self.__CustodiaClient(server=master_host_name) + cli = self._get_custodia_client(server=master_host_name) cli.fetch_key('dm/DMHash') - def __wait_keys(self, host, timeout=300): + def _wait_keys(self, host, timeout=300): ldap_uri = 'ldap://%s' % host deadline = int(time.time()) + timeout logger.info("Waiting up to %s seconds to see our keys " "appear on host: %s", timeout, host) - # FIXME: Change once there's better way to show this mesage - # in installer output - print("Waiting for keys to appear on host: {}, " - "please wait until this has completed.".format(host)) konn = KEMLdap(ldap_uri) saved_e = None @@ -160,6 +221,12 @@ class CustodiaInstance(SimpleServiceInstance): try: return konn.check_host_keys(self.fqdn) except Exception as e: + # Print message to console only once for first error. + if saved_e is None: + # FIXME: Change once there's better way to show this + # message in installer output, + print(" Waiting for keys to appear on host: {}, please " + "wait until this has completed.".format(host)) # log only once for the same error if not isinstance(e, type(saved_e)): logger.debug( @@ -169,20 +236,23 @@ class CustodiaInstance(SimpleServiceInstance): raise RuntimeError("Timed out trying to obtain keys.") time.sleep(1) - def __CustodiaClient(self, server): + def _get_custodia_client(self, server): # Before we attempt to fetch keys from this host, make sure our public # keys have been replicated there. - self.__wait_keys(server) + self._wait_keys(server) - return CustodiaClient('host@%s' % self.fqdn, self.server_keys, - paths.KRB5_KEYTAB, server, realm=self.realm) + return CustodiaClient( + client_service='host@{}'.format(self.fqdn), + keyfile=self.server_keys, keytab=paths.KRB5_KEYTAB, + server=server, realm=self.realm + ) - def __get_keys(self, ca_host, cacerts_file, cacerts_pwd, data): + def _get_keys(self, ca_host, cacerts_file, cacerts_pwd, data): # Fetch all needed certs one by one, then combine them in a single # PKCS12 file prefix = data['prefix'] certlist = data['list'] - cli = self.__CustodiaClient(server=ca_host) + cli = self._get_custodia_client(server=ca_host) with NSSDatabase(None) as tmpdb: tmpdb.create_db() @@ -235,7 +305,7 @@ class CustodiaInstance(SimpleServiceInstance): 'subsystemCert cert-pki-ca'] data = {'prefix': 'ca', 'list': certlist} - self.__get_keys(ca_host, cacerts_file, cacerts_pwd, data) + self._get_keys(ca_host, cacerts_file, cacerts_pwd, data) def get_kra_keys(self, ca_host, cacerts_file, cacerts_pwd): certlist = ['auditSigningCert cert-pki-kra', @@ -244,7 +314,7 @@ class CustodiaInstance(SimpleServiceInstance): 'transportCert cert-pki-kra'] data = {'prefix': 'ca', 'list': certlist} - self.__get_keys(ca_host, cacerts_file, cacerts_pwd, data) + self._get_keys(ca_host, cacerts_file, cacerts_pwd, data) def __start(self): super(CustodiaInstance, self).__start() diff --git a/ipaserver/install/ipa_kra_install.py b/ipaserver/install/ipa_kra_install.py index 2e12f5924..cb097e9fe 100644 --- a/ipaserver/install/ipa_kra_install.py +++ b/ipaserver/install/ipa_kra_install.py @@ -33,6 +33,7 @@ from ipaplatform.paths import paths from ipapython import admintool from ipaserver.install import service from ipaserver.install import cainstance +from ipaserver.install import custodiainstance from ipaserver.install import krainstance from ipaserver.install import dsinstance from ipaserver.install import installutils @@ -179,7 +180,6 @@ class KRAInstaller(KRAInstall): api.Backend.ldap2.connect() - config = None if self.installing_replica: if self.options.promote: config = ReplicaConfig() @@ -199,6 +199,7 @@ class KRAInstaller(KRAInstall): config.kra_host_name = config.master_host_name config.setup_kra = True + config.promote = self.options.promote if config.subject_base is None: attrs = api.Backend.ldap2.get_ipa_config() @@ -207,6 +208,11 @@ class KRAInstaller(KRAInstall): if config.kra_host_name is None: config.kra_host_name = service.find_providing_server( 'KRA', api.Backend.ldap2, api.env.ca_host) + custodia = custodiainstance.get_custodia_instance( + config, custodiainstance.CustodiaModes.KRA_PEER) + else: + config = None + custodia = None try: kra.install_check(api, config, self.options) @@ -216,7 +222,7 @@ class KRAInstaller(KRAInstall): print(dedent(self.INSTALLER_START_MESSAGE)) try: - kra.install(api, config, self.options) + kra.install(api, config, self.options, custodia=custodia) except: logger.error('%s', dedent(self.FAIL_MESSAGE)) raise diff --git a/ipaserver/install/kra.py b/ipaserver/install/kra.py index 7837dd516..792e82409 100644 --- a/ipaserver/install/kra.py +++ b/ipaserver/install/kra.py @@ -18,7 +18,6 @@ from ipaplatform.paths import paths from ipapython import certdb from ipapython import ipautil from ipapython.install.core import group -from ipaserver.install import custodiainstance from ipaserver.install import cainstance from ipaserver.install import krainstance from ipaserver.install import dsinstance @@ -70,7 +69,7 @@ def install_check(api, replica_config, options): "new replica file.") -def install(api, replica_config, options): +def install(api, replica_config, options, custodia): if replica_config is None: if not options.setup_kra: return @@ -93,9 +92,6 @@ def install(api, replica_config, options): 'host/{env.host}@{env.realm}'.format(env=api.env), paths.KRB5_KEYTAB, ccache) - custodia = custodiainstance.CustodiaInstance( - replica_config.host_name, - replica_config.realm_name) custodia.get_kra_keys( replica_config.kra_host_name, krafile, diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py index f222f377d..b17c666a2 100644 --- a/ipaserver/install/server/install.py +++ b/ipaserver/install/server/install.py @@ -740,6 +740,7 @@ def install(installer): host_name = options.host_name ip_addresses = options.ip_addresses setup_ca = options.setup_ca + options.promote = False # first master, no promotion # Installation has started. No IPA sysrestore items are restored in case of # failure to enable root cause investigation @@ -821,6 +822,10 @@ def install(installer): setup_pkinit=not options.no_pkinit, subject_base=options.subject_base) + custodia = custodiainstance.get_custodia_instance( + options, custodiainstance.CustodiaModes.MASTER_PEER) + custodia.create_instance() + if setup_ca: if not options.external_cert_files and options.external_ca: # stage 1 of external CA installation @@ -835,7 +840,7 @@ def install(installer): if n in options.__dict__} write_cache(cache_vars) - ca.install_step_0(False, None, options) + ca.install_step_0(False, None, options, custodia=custodia) else: # Put the CA cert where other instances expect it x509.write_certificate(http_ca_cert, paths.IPA_CA_CRT) @@ -855,15 +860,12 @@ def install(installer): ds.enable_ssl() if setup_ca: - ca.install_step_1(False, None, options) + ca.install_step_1(False, None, options, custodia=custodia) otpd = otpdinstance.OtpdInstance() otpd.create_instance('OTPD', host_name, ipautil.realm_to_suffix(realm_name)) - custodia = custodiainstance.CustodiaInstance(host_name, realm_name) - custodia.create_instance() - # Create a HTTP instance http = httpinstance.HTTPInstance(fstore) if options.http_cert_files: @@ -895,7 +897,7 @@ def install(installer): krb.restart() if options.setup_kra: - kra.install(api, None, options) + kra.install(api, None, options, custodia=custodia) if options.setup_dns: dns.install(False, False, options) diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py index 795d663b5..84d8e4e25 100644 --- a/ipaserver/install/server/replicainstall.py +++ b/ipaserver/install/server/replicainstall.py @@ -1361,6 +1361,7 @@ def install(installer): fstore = installer._fstore sstore = installer._sstore config = installer._config + config.promote = installer.promote promote = installer.promote cafile = installer._ca_file dirsrv_pkcs12_info = installer._dirsrv_pkcs12_info @@ -1480,19 +1481,19 @@ def install(installer): otpd.create_instance('OTPD', config.host_name, ipautil.realm_to_suffix(config.realm_name)) - custodia = custodiainstance.CustodiaInstance(config.host_name, - config.realm_name) - if promote: - custodia.create_replica(config.master_host_name) + if ca_enabled: + mode = custodiainstance.CustodiaModes.CA_PEER else: - custodia.create_instance() + mode = custodiainstance.CustodiaModes.MASTER_PEER + custodia = custodiainstance.get_custodia_instance(config, mode) + custodia.create_instance() if ca_enabled: options.realm_name = config.realm_name options.domain_name = config.domain_name options.host_name = config.host_name options.dm_password = config.dirman_password - ca.install(False, config, options) + ca.install(False, config, options, custodia=custodia) # configure PKINIT now that all required services are in place krb.enable_ssl() @@ -1502,7 +1503,7 @@ def install(installer): ds.apply_updates() if kra_enabled: - kra.install(api, config, options) + kra.install(api, config, options, custodia=custodia) service.print_msg("Restarting the KDC") krb.restart()