URL: https://github.com/freeipa/freeipa/pull/1382 Author: tiran Title: #1382: [Backport][ipa-4-6] Making ipa-ca-install more resilient Action: opened
PR body: """ This PR was opened automatically because PR #1232 was pushed to master and backport to ipa-4-6 is required. """ To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/1382/head:pr1382 git checkout pr1382
From 11251a3c69d3ccbdc1f316bb3f46c207b7104cce Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Mon, 30 Oct 2017 17:52:07 +1100 Subject: [PATCH 1/4] CertUpdate: make it easy to invoke from other programs The guts of ipa-certupdate are useful to execute as part of other programs (e.g. as a first step of ipa-ca-install). Refactor ipa_certupdate.CertUpdate to make it easy to do that. In particular, make it possible to use an already-initialised API object. Part of: https://pagure.io/freeipa/issue/6577 --- ipaclient/install/ipa_certupdate.py | 63 ++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/ipaclient/install/ipa_certupdate.py b/ipaclient/install/ipa_certupdate.py index 06fd079226..4e0d4e9bbb 100644 --- a/ipaclient/install/ipa_certupdate.py +++ b/ipaclient/install/ipa_certupdate.py @@ -56,30 +56,36 @@ def run(self): api.bootstrap(context='cli_installer', confdir=paths.ETC_IPA) api.finalize() + api.Backend.rpcclient.connect() + self.run_with_args(api) + api.Backend.rpcclient.disconnect() + + @classmethod + def run_with_args(cls, api): + """ + Run the certupdate procedure with the given API object. + + :param api: API object with ldap2/rpcclient backend connected + (such that Commands can be invoked) + + """ server = urlsplit(api.env.jsonrpc_uri).hostname ldap_uri = ipaldap.get_ldap_uri(server) ldap = ipaldap.LDAPClient(ldap_uri) tmpdir = tempfile.mkdtemp(prefix="tmp-") ccache_name = os.path.join(tmpdir, 'ccache') + old_krb5ccname = os.environ.get('KRB5CCNAME') try: principal = str('host/%s@%s' % (api.env.host, api.env.realm)) kinit_keytab(principal, paths.KRB5_KEYTAB, ccache_name) os.environ['KRB5CCNAME'] = ccache_name - api.Backend.rpcclient.connect() try: - result = api.Backend.rpcclient.forward( - 'ca_is_enabled', - version=u'2.107', - ) + result = api.Command.ca_is_enabled(version=u'2.107') ca_enabled = result['result'] except (errors.CommandError, errors.NetworkError): - result = api.Backend.rpcclient.forward( - 'env', - server=True, - version=u'2.0', - ) + result = api.Command.env(server=True, version=u'2.0') ca_enabled = result['result']['enable_ra'] ldap.gssapi_bind() @@ -92,13 +98,16 @@ def run(self): else: lwcas = [] - api.Backend.rpcclient.disconnect() finally: + if old_krb5ccname is None: + del os.environ['KRB5CCNAME'] + else: + os.environ['KRB5CCNAME'] = old_krb5ccname shutil.rmtree(tmpdir) server_fstore = sysrestore.FileStore(paths.SYSRESTORE) if server_fstore.has_files(): - self.update_server(certs) + cls.update_server(certs) try: # pylint: disable=import-error,ipa-forbidden-import from ipaserver.install import cainstance @@ -108,12 +117,13 @@ def run(self): logger.exception( "Failed to add lightweight CA tracking requests") - self.update_client(certs) + cls.update_client(certs) - def update_client(self, certs): - self.update_file(paths.IPA_CA_CRT, certs) - self.update_file(paths.KDC_CA_BUNDLE_PEM, certs) - self.update_file(paths.CA_BUNDLE_PEM, certs) + @classmethod + def update_client(cls, certs): + cls.update_file(paths.IPA_CA_CRT, certs) + cls.update_file(paths.KDC_CA_BUNDLE_PEM, certs) + cls.update_file(paths.CA_BUNDLE_PEM, certs) ipa_db = certdb.NSSDatabase(api.env.nss_dir) @@ -127,19 +137,20 @@ def update_client(self, certs): nickname, ipa_db.secdir, e) break - self.update_db(ipa_db.secdir, certs) + cls.update_db(ipa_db.secdir, certs) tasks.remove_ca_certs_from_systemwide_ca_store() tasks.insert_ca_certs_into_systemwide_ca_store(certs) - def update_server(self, certs): + @classmethod + def update_server(cls, certs): instance = '-'.join(api.env.realm.split('.')) - self.update_db( + cls.update_db( paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance, certs) if services.knownservices.dirsrv.is_running(): services.knownservices.dirsrv.restart(instance) - self.update_db(paths.HTTPD_ALIAS_DIR, certs) + cls.update_db(paths.HTTPD_ALIAS_DIR, certs) if services.knownservices.httpd.is_running(): services.knownservices.httpd.restart() @@ -170,17 +181,19 @@ def update_server(self, certs): logger.debug("modifying certmonger request '%s'", request_id) certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent') - self.update_file(paths.CA_CRT, certs) - self.update_file(paths.CACERT_PEM, certs) + cls.update_file(paths.CA_CRT, certs) + cls.update_file(paths.CACERT_PEM, certs) - def update_file(self, filename, certs, mode=0o444): + @staticmethod + def update_file(filename, certs, mode=0o444): certs = (c[0] for c in certs if c[2] is not False) try: x509.write_certificate_list(certs, filename) except Exception as e: logger.error("failed to update %s: %s", filename, e) - def update_db(self, path, certs): + @staticmethod + def update_db(path, certs): db = certdb.NSSDatabase(path) for cert, nickname, trusted, eku in certs: trust_flags = certstore.key_policy_to_trust_flags( From 3c6001ab3002321933455f65c2812068aff4eac9 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Mon, 30 Oct 2017 18:05:56 +1100 Subject: [PATCH 2/4] ipa-ca-install: run certupdate as initial step When installing a CA replica, perform a certupdate to ensure that the relevant CA cert is present. This is necessary if the admin has just promoted the topology from CA-less to CA-ful but didn't manually run ipa-certupdate afterwards. Fixes: https://pagure.io/freeipa/issue/6577 --- install/tools/ipa-ca-install | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install index 64478bb0e8..9fdbb06431 100755 --- a/install/tools/ipa-ca-install +++ b/install/tools/ipa-ca-install @@ -27,6 +27,7 @@ import tempfile from ipalib.install.kinit import kinit_keytab from ipapython import ipautil +from ipaclient.install.ipa_certupdate import CertUpdate from ipaserver.install import installutils from ipaserver.install.installutils import create_replica_config from ipaserver.install.installutils import check_creds, ReplicaConfig @@ -174,6 +175,16 @@ def install_replica(safe_options, options, filename): not options.skip_conncheck and options.unattended): sys.exit('admin password required') + # Run ipa-certupdate to ensure we have the CA cert. This is + # necessary if the admin has just promoted the topology from + # CA-less to CA-ful, and ipa-certupdate has not been run yet. + CertUpdate.run_with_args(api) + + # CertUpdate restarts DS causing broken pipe on the original + # connection, so reconnect the backend. + api.Backend.ldap2.disconnect() + api.Backend.ldap2.connect() + if options.promote: config = ReplicaConfig() config.ca_host_name = None From af76b0df22affcdb666a114fb0514f1a34e9305a Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Tue, 31 Oct 2017 18:20:15 +1100 Subject: [PATCH 3/4] Run certupdate after promoting to CA-ful deployment After installing a CA in a CA-less installations (using ipa-ca-install), the new CA certificate is not installed in /etc/httpd/alias. This causes communication failure between IPA framework and Dogtag (it cannot verify the Dogtag server certificate). Perform a CertUpdate as the final step when promoting a CA-less deployment to CA-ful. Fixes: https://pagure.io/freeipa/issue/7230 --- install/tools/ipa-ca-install | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install index 9fdbb06431..9f9cfd56a4 100755 --- a/install/tools/ipa-ca-install +++ b/install/tools/ipa-ca-install @@ -254,6 +254,10 @@ def install_master(safe_options, options): ca.install_check(True, None, options) ca.install(True, None, options) + # Run ipa-certupdate to add the new CA certificate to + # certificate databases on this server. + logger.info("Updating certificate databases.") + CertUpdate.run_with_args(api) def install(safe_options, options, filename): options.promote = False From fe356fba60461d61cd94a8b7aa14f6d01a74a71e Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Fri, 1 Dec 2017 12:27:26 +1100 Subject: [PATCH 4/4] ipa_certupdate: avoid classmethod and staticmethod Because classmethod and staticmethod are just fancy ways of calling plain old functions, turn the classmethods and staticmethods of CertUpdate into plain old functions. This improves readability by making it clear that the behaviour of the routines cannot depend on instance or class variables. Part of: https://pagure.io/freeipa/issue/6577 --- install/tools/ipa-ca-install | 6 +- ipaclient/install/ipa_certupdate.py | 270 ++++++++++++++++++------------------ 2 files changed, 137 insertions(+), 139 deletions(-) diff --git a/install/tools/ipa-ca-install b/install/tools/ipa-ca-install index 9f9cfd56a4..c01e7f71e0 100755 --- a/install/tools/ipa-ca-install +++ b/install/tools/ipa-ca-install @@ -27,7 +27,7 @@ import tempfile from ipalib.install.kinit import kinit_keytab from ipapython import ipautil -from ipaclient.install.ipa_certupdate import CertUpdate +from ipaclient.install import ipa_certupdate from ipaserver.install import installutils from ipaserver.install.installutils import create_replica_config from ipaserver.install.installutils import check_creds, ReplicaConfig @@ -178,7 +178,7 @@ def install_replica(safe_options, options, filename): # Run ipa-certupdate to ensure we have the CA cert. This is # necessary if the admin has just promoted the topology from # CA-less to CA-ful, and ipa-certupdate has not been run yet. - CertUpdate.run_with_args(api) + ipa_certupdate.run_with_args(api) # CertUpdate restarts DS causing broken pipe on the original # connection, so reconnect the backend. @@ -257,7 +257,7 @@ def install_master(safe_options, options): # Run ipa-certupdate to add the new CA certificate to # certificate databases on this server. logger.info("Updating certificate databases.") - CertUpdate.run_with_args(api) + ipa_certupdate.run_with_args(api) def install(safe_options, options, filename): options.promote = False diff --git a/ipaclient/install/ipa_certupdate.py b/ipaclient/install/ipa_certupdate.py index 4e0d4e9bbb..e854b173a8 100644 --- a/ipaclient/install/ipa_certupdate.py +++ b/ipaclient/install/ipa_certupdate.py @@ -57,149 +57,147 @@ def run(self): api.finalize() api.Backend.rpcclient.connect() - self.run_with_args(api) + run_with_args(api) api.Backend.rpcclient.disconnect() - @classmethod - def run_with_args(cls, api): - """ - Run the certupdate procedure with the given API object. - :param api: API object with ldap2/rpcclient backend connected - (such that Commands can be invoked) +def run_with_args(api): + """ + Run the certupdate procedure with the given API object. - """ - server = urlsplit(api.env.jsonrpc_uri).hostname - ldap_uri = ipaldap.get_ldap_uri(server) - ldap = ipaldap.LDAPClient(ldap_uri) + :param api: API object with ldap2/rpcclient backend connected + (such that Commands can be invoked) - tmpdir = tempfile.mkdtemp(prefix="tmp-") - ccache_name = os.path.join(tmpdir, 'ccache') - old_krb5ccname = os.environ.get('KRB5CCNAME') - try: - principal = str('host/%s@%s' % (api.env.host, api.env.realm)) - kinit_keytab(principal, paths.KRB5_KEYTAB, ccache_name) - os.environ['KRB5CCNAME'] = ccache_name + """ + server = urlsplit(api.env.jsonrpc_uri).hostname + ldap_uri = ipaldap.get_ldap_uri(server) + ldap = ipaldap.LDAPClient(ldap_uri) - try: - result = api.Command.ca_is_enabled(version=u'2.107') - ca_enabled = result['result'] - except (errors.CommandError, errors.NetworkError): - result = api.Command.env(server=True, version=u'2.0') - ca_enabled = result['result']['enable_ra'] - - ldap.gssapi_bind() - - certs = certstore.get_ca_certs(ldap, api.env.basedn, - api.env.realm, ca_enabled) - - if ca_enabled: - lwcas = api.Command.ca_find()['result'] - else: - lwcas = [] - - finally: - if old_krb5ccname is None: - del os.environ['KRB5CCNAME'] - else: - os.environ['KRB5CCNAME'] = old_krb5ccname - shutil.rmtree(tmpdir) - - server_fstore = sysrestore.FileStore(paths.SYSRESTORE) - if server_fstore.has_files(): - cls.update_server(certs) - try: - # pylint: disable=import-error,ipa-forbidden-import - from ipaserver.install import cainstance - # pylint: enable=import-error,ipa-forbidden-import - cainstance.add_lightweight_ca_tracking_requests(lwcas) - except Exception: - logger.exception( - "Failed to add lightweight CA tracking requests") - - cls.update_client(certs) - - @classmethod - def update_client(cls, certs): - cls.update_file(paths.IPA_CA_CRT, certs) - cls.update_file(paths.KDC_CA_BUNDLE_PEM, certs) - cls.update_file(paths.CA_BUNDLE_PEM, certs) - - ipa_db = certdb.NSSDatabase(api.env.nss_dir) - - # Remove old IPA certs from /etc/ipa/nssdb - for nickname in ('IPA CA', 'External CA cert'): - while ipa_db.has_nickname(nickname): - try: - ipa_db.delete_cert(nickname) - except ipautil.CalledProcessError as e: - logger.error("Failed to remove %s from %s: %s", - nickname, ipa_db.secdir, e) - break - - cls.update_db(ipa_db.secdir, certs) - - tasks.remove_ca_certs_from_systemwide_ca_store() - tasks.insert_ca_certs_into_systemwide_ca_store(certs) - - @classmethod - def update_server(cls, certs): - instance = '-'.join(api.env.realm.split('.')) - cls.update_db( - paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance, certs) - if services.knownservices.dirsrv.is_running(): - services.knownservices.dirsrv.restart(instance) - - cls.update_db(paths.HTTPD_ALIAS_DIR, certs) - if services.knownservices.httpd.is_running(): - services.knownservices.httpd.restart() - - criteria = { - 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, - 'cert-nickname': IPA_CA_NICKNAME, - 'ca-name': RENEWAL_CA_NAME - } - request_id = certmonger.get_request_id(criteria) - if request_id is not None: - timeout = api.env.startup_timeout + 60 - - logger.debug("resubmitting certmonger request '%s'", request_id) - certmonger.resubmit_request( - request_id, ca='dogtag-ipa-ca-renew-agent-reuse', profile='') - try: - state = certmonger.wait_for_request(request_id, timeout) - except RuntimeError: - raise admintool.ScriptError( - "Resubmitting certmonger request '%s' timed out, " - "please check the request manually" % request_id) - ca_error = certmonger.get_request_value(request_id, 'ca-error') - if state != 'MONITORING' or ca_error: - raise admintool.ScriptError( - "Error resubmitting certmonger request '%s', " - "please check the request manually" % request_id) - - logger.debug("modifying certmonger request '%s'", request_id) - certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent') - - cls.update_file(paths.CA_CRT, certs) - cls.update_file(paths.CACERT_PEM, certs) - - @staticmethod - def update_file(filename, certs, mode=0o444): - certs = (c[0] for c in certs if c[2] is not False) + tmpdir = tempfile.mkdtemp(prefix="tmp-") + ccache_name = os.path.join(tmpdir, 'ccache') + old_krb5ccname = os.environ.get('KRB5CCNAME') + try: + principal = str('host/%s@%s' % (api.env.host, api.env.realm)) + kinit_keytab(principal, paths.KRB5_KEYTAB, ccache_name) + os.environ['KRB5CCNAME'] = ccache_name + + try: + result = api.Command.ca_is_enabled(version=u'2.107') + ca_enabled = result['result'] + except (errors.CommandError, errors.NetworkError): + result = api.Command.env(server=True, version=u'2.0') + ca_enabled = result['result']['enable_ra'] + + ldap.gssapi_bind() + + certs = certstore.get_ca_certs( + ldap, api.env.basedn, api.env.realm, ca_enabled) + + if ca_enabled: + lwcas = api.Command.ca_find()['result'] + else: + lwcas = [] + + finally: + if old_krb5ccname is None: + del os.environ['KRB5CCNAME'] + else: + os.environ['KRB5CCNAME'] = old_krb5ccname + shutil.rmtree(tmpdir) + + server_fstore = sysrestore.FileStore(paths.SYSRESTORE) + if server_fstore.has_files(): + update_server(certs) try: - x509.write_certificate_list(certs, filename) - except Exception as e: - logger.error("failed to update %s: %s", filename, e) - - @staticmethod - def update_db(path, certs): - db = certdb.NSSDatabase(path) - for cert, nickname, trusted, eku in certs: - trust_flags = certstore.key_policy_to_trust_flags( - trusted, True, eku) + # pylint: disable=import-error,ipa-forbidden-import + from ipaserver.install import cainstance + # pylint: enable=import-error,ipa-forbidden-import + cainstance.add_lightweight_ca_tracking_requests(lwcas) + except Exception: + logger.exception( + "Failed to add lightweight CA tracking requests") + + update_client(certs) + + +def update_client(certs): + update_file(paths.IPA_CA_CRT, certs) + update_file(paths.KDC_CA_BUNDLE_PEM, certs) + update_file(paths.CA_BUNDLE_PEM, certs) + + ipa_db = certdb.NSSDatabase(api.env.nss_dir) + + # Remove old IPA certs from /etc/ipa/nssdb + for nickname in ('IPA CA', 'External CA cert'): + while ipa_db.has_nickname(nickname): try: - db.add_cert(cert, nickname, trust_flags) + ipa_db.delete_cert(nickname) except ipautil.CalledProcessError as e: logger.error( - "failed to update %s in %s: %s", nickname, path, e) + "Failed to remove %s from %s: %s", + nickname, ipa_db.secdir, e) + break + + update_db(ipa_db.secdir, certs) + + tasks.remove_ca_certs_from_systemwide_ca_store() + tasks.insert_ca_certs_into_systemwide_ca_store(certs) + + +def update_server(certs): + instance = '-'.join(api.env.realm.split('.')) + update_db(paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance, certs) + if services.knownservices.dirsrv.is_running(): + services.knownservices.dirsrv.restart(instance) + + update_db(paths.HTTPD_ALIAS_DIR, certs) + if services.knownservices.httpd.is_running(): + services.knownservices.httpd.restart() + + criteria = { + 'cert-database': paths.PKI_TOMCAT_ALIAS_DIR, + 'cert-nickname': IPA_CA_NICKNAME, + 'ca-name': RENEWAL_CA_NAME, + } + request_id = certmonger.get_request_id(criteria) + if request_id is not None: + timeout = api.env.startup_timeout + 60 + + logger.debug("resubmitting certmonger request '%s'", request_id) + certmonger.resubmit_request( + request_id, ca='dogtag-ipa-ca-renew-agent-reuse', profile='') + try: + state = certmonger.wait_for_request(request_id, timeout) + except RuntimeError: + raise admintool.ScriptError( + "Resubmitting certmonger request '%s' timed out, " + "please check the request manually" % request_id) + ca_error = certmonger.get_request_value(request_id, 'ca-error') + if state != 'MONITORING' or ca_error: + raise admintool.ScriptError( + "Error resubmitting certmonger request '%s', " + "please check the request manually" % request_id) + + logger.debug("modifying certmonger request '%s'", request_id) + certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent') + + update_file(paths.CA_CRT, certs) + update_file(paths.CACERT_PEM, certs) + + +def update_file(filename, certs, mode=0o444): + certs = (c[0] for c in certs if c[2] is not False) + try: + x509.write_certificate_list(certs, filename) + except Exception as e: + logger.error("failed to update %s: %s", filename, e) + + +def update_db(path, certs): + db = certdb.NSSDatabase(path) + for cert, nickname, trusted, eku in certs: + trust_flags = certstore.key_policy_to_trust_flags(trusted, True, eku) + try: + db.add_cert(cert, nickname, trust_flags) + except ipautil.CalledProcessError as e: + logger.error("failed to update %s in %s: %s", nickname, path, e)
_______________________________________________ FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org