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

Reply via email to