Hi,

the attached patches implement automatic CA certificate renewal as well as the initial version of the CA certificate management tool.

Requires my patches 172-196.

In order to test, you must install current git version of certmonger (see <https://fedorahosted.org/certmonger/ticket/26>) and set SELinux to permissive (see <https://bugzilla.redhat.com/show_bug.cgi?id=1078783>). Make sure you install certmonger before running ipa-server-install/ipa-replica-install. On F20 you can use RPMs located at <http://jcholast.fedorapeople.org/certmonger-git/>.

To test automatic renewal, move system time forward (see <https://fedorahosted.org/freeipa/ticket/2803#comment:17> for more info about certificate renewal testing, nickname of the CA certificate is "caSigningCert cert-pki-ca"). In CA-full installs the renewal should be fully automatic, in CA-less installs you should be alerted via syslog to renew the certificate using ipa-cacert-manage.

To test manual renewal, run "ipa-cacert-manage renew". You can run it on any CA master. To make the renewed certificate available on other CA masters, you must run "getcert resubmit -d /etc/pki/pki-tomcat/alias -n 'caSigningCert cert-pki-ca'" on each of them. Note that currently you can't change the chaining of the CA certificate.

Honza

--
Jan Cholasta
>From 3b3c5b99c1005a049436dc262cf8258daf7486c3 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Wed, 12 Mar 2014 11:41:02 +0100
Subject: [PATCH 01/13] Add function for checking if certificate is self-signed
 to ipalib.x509.

---
 ipalib/x509.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/ipalib/x509.py b/ipalib/x509.py
index ca6eac5..8d049bd 100644
--- a/ipalib/x509.py
+++ b/ipalib/x509.py
@@ -164,6 +164,12 @@ def get_serial_number(certificate, datatype=PEM, dbdir=None):
     del(nsscert)
     return serial_number
 
+def is_self_signed(certificate, datatype=PEM, dbdir=None):
+    nsscert = load_certificate(certificate, datatype, dbdir)
+    self_signed = (nsscert.issuer == nsscert.subject)
+    del nsscert
+    return self_signed
+
 def make_pem(data):
     """
     Convert a raw base64-encoded blob into something that looks like a PE
-- 
1.8.5.3

>From 47635bbc42b36928d57ea48cfb6e40b621e6082a Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Tue, 18 Feb 2014 18:14:47 +0100
Subject: [PATCH 02/13] Support CA certificate renewal in
 dogtag-ipa-ca-renew-agent.

---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    | 49 ++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 57eb4e5..8a1bd83 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -270,11 +270,60 @@ def export_csr():
 
     return (ISSUED, cert)
 
+def renew_ca_cert():
+    """
+    This is used for automatic CA certificate renewal.
+    """
+    cert = os.environ.get('CERTMONGER_CERTIFICATE')
+    if not cert:
+        return (REJECTED, "New certificate requests not supported")
+
+    operation = os.environ.get('CERTMONGER_OPERATION')
+    if operation == 'SUBMIT':
+        result = retrieve_cert()
+        if result[0] == ISSUED:
+            new_cert = x509.normalize_certificate(result[1])
+            old_cert = x509.normalize_certificate(cert)
+            if new_cert != old_cert:
+                return result
+
+        state = 'retrieve'
+        if x509.is_self_signed(cert):
+            ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
+            if ca.is_renewal_master():
+                state = 'request'
+    elif operation == 'POLL':
+        cookie = os.environ.get('CERTMONGER_CA_COOKIE')
+        if not cookie:
+            return (UNCONFIGURED, "Cookie not provided")
+
+        state, sep, cookie = cookie.partition(':')
+        if state not in ('retrieve', 'request'):
+            return (UNCONFIGURED, "Invalid cookie")
+
+        os.environ['CERTMONGER_CA_COOKIE'] = cookie
+    else:
+        return (OPERATION_NOT_SUPPORTED_BY_HELPER,)
+
+    if state == 'retrieve':
+        result = retrieve_cert()
+    elif state == 'request':
+        os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert'
+        result = request_and_store_cert()
+
+    if result[0] == WAIT:
+        return (result[0], '%s:%s' % (state, result[1]))
+    elif result[0] == WAIT_WITH_DELAY:
+        return (result[0], result[1], '%s:%s' % (state, result[2]))
+    else:
+        return result
+
 def main():
     handlers = {
         'ipaStorage':       store_cert,
         'ipaRetrieval':     retrieve_cert,
         'ipaCSRExport':     export_csr,
+        'ipaCACertRenewal': renew_ca_cert,
     }
 
     api.bootstrap(context='renew')
-- 
1.8.5.3

>From 17c951b69870224adde95b8c919c7556a5890dcb Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Tue, 18 Feb 2014 18:15:49 +0100
Subject: [PATCH 03/13] Allow IPA master hosts to update CA certificate in
 LDAP.

---
 install/updates/40-delegation.update | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update
index 3fabdf9..3f1ff16 100644
--- a/install/updates/40-delegation.update
+++ b/install/updates/40-delegation.update
@@ -374,6 +374,9 @@ add: member: 'cn=Host Administrators,cn=privileges,cn=pbac,$SUFFIX'
 dn: cn=Revoke Certificate,cn=permissions,cn=pbac,$SUFFIX
 add: member: 'cn=Host Administrators,cn=privileges,cn=pbac,$SUFFIX'
 
+dn: cn=ipa,cn=etc,$SUFFIX
+add:aci:'(target = "ldap:///cn=CAcert,cn=ipa,cn=etc,$SUFFIX";)(targetattr = cACertificate)(version 3.0; acl "Modify compatibility CA certificate"; allow (write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)'
+
 # Automember tasks
 dn: cn=Automember Task Administrator,cn=privileges,cn=pbac,$SUFFIX
 default:objectClass: nestedgroup
-- 
1.8.5.3

>From 5238d0260bb853bd630cd3bcc5ff40522307dd52 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Wed, 12 Mar 2014 11:32:59 +0100
Subject: [PATCH 04/13] Automatically update CA certificate in LDAP on renewal.

---
 install/restart_scripts/renew_ca_cert | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/install/restart_scripts/renew_ca_cert b/install/restart_scripts/renew_ca_cert
index 2663887..e08c5d3 100644
--- a/install/restart_scripts/renew_ca_cert
+++ b/install/restart_scripts/renew_ca_cert
@@ -29,6 +29,7 @@ import traceback
 
 from ipapython import dogtag, certmonger, ipautil
 from ipapython import services as ipaservices
+from ipapython.dn import DN
 from ipalib import api, errors, x509, util
 from ipaserver.install import certs, cainstance, installutils
 from ipaserver.plugins.ldap2 import ldap2
@@ -88,6 +89,32 @@ def main():
                 syslog.LOG_ERR,
                 "Updating trust on certificate %s failed in %s" %
                 (nickname, db.secdir))
+    elif nickname == 'caSigningCert cert-pki-ca' and ca.is_renewal_master():
+        # Update CA certificate in LDAP
+        tmpdir = tempfile.mkdtemp(prefix="tmp-")
+        try:
+            principal = str('host/%s@%s' % (api.env.host, api.env.realm))
+            ccache = ipautil.kinit_hostprincipal('/etc/krb5.keytab', tmpdir,
+                                                 principal)
+
+            conn = ldap2(shared_instance=False, ldap_uri=api.env.ldap_uri)
+            conn.connect(ccache=ccache)
+
+            dn = DN(('cn', 'CAcert'), ('cn', 'ipa'), ('cn', 'etc'),
+                    api.env.basedn)
+            try:
+                entry = conn.get_entry(dn, attrs_list=['cACertificate;binary'])
+                entry['cACertificate;binary'] = [cert]
+                conn.update_entry(entry)
+            except errors.EmptyModlist:
+                pass
+
+            conn.disconnect()
+        except Exception, e:
+            syslog.syslog(
+                syslog.LOG_ERR, "Updating CA certificate failed: %s" % e)
+        finally:
+            shutil.rmtree(tmpdir)
 
     # Now we can start the CA. Using the ipaservices start should fire
     # off the servlet to verify that the CA is actually up and responding so
-- 
1.8.5.3

>From 626517f155eacf93de6665f3221accc17f4c5a07 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Wed, 12 Mar 2014 11:33:18 +0100
Subject: [PATCH 05/13] Track CA certificate using dogtag-ipa-ca-renew-agent.

---
 install/tools/ipa-upgradeconfig | 19 +++++++++++++++++--
 ipaserver/install/cainstance.py | 20 +++++++++++++-------
 2 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig
index d77a338..433311b 100644
--- a/install/tools/ipa-upgradeconfig
+++ b/install/tools/ipa-upgradeconfig
@@ -594,7 +594,7 @@ def certificate_renewal_update(ca):
     dogtag_constants = dogtag.configured_constants()
 
     # bump version when requests is changed
-    version = 1
+    version = 2
     requests = (
         (
             dogtag_constants.ALIAS_DIR,
@@ -602,6 +602,7 @@ def certificate_renewal_update(ca):
             'dogtag-ipa-ca-renew-agent',
             'stop_pkicad',
             'renew_ca_cert',
+            None,
         ),
         (
             dogtag_constants.ALIAS_DIR,
@@ -609,6 +610,7 @@ def certificate_renewal_update(ca):
             'dogtag-ipa-ca-renew-agent',
             'stop_pkicad',
             'renew_ca_cert',
+            None,
         ),
         (
             dogtag_constants.ALIAS_DIR,
@@ -616,6 +618,15 @@ def certificate_renewal_update(ca):
             'dogtag-ipa-ca-renew-agent',
             'stop_pkicad',
             'renew_ca_cert',
+            None,
+        ),
+        (
+            dogtag_constants.ALIAS_DIR,
+            'caSigningCert cert-pki-ca',
+            'dogtag-ipa-ca-renew-agent',
+            'stop_pkicad',
+            'renew_ca_cert',
+            'ipaCACertRenewal',
         ),
         (
             '/etc/httpd/alias',
@@ -623,6 +634,7 @@ def certificate_renewal_update(ca):
             'dogtag-ipa-ca-renew-agent',
             None,
             'renew_ra_cert',
+            None,
         ),
         (
             dogtag_constants.ALIAS_DIR,
@@ -630,6 +642,7 @@ def certificate_renewal_update(ca):
             'dogtag-ipa-renew-agent',
             None,
             None,
+            None,
         ),
     )
 
@@ -644,11 +657,13 @@ def certificate_renewal_update(ca):
         return False
 
     # State not set, lets see if we are already configured
-    for nss_dir, nickname, ca_name, pre_command, post_command in requests:
+    for request in requests:
+        nss_dir, nickname, ca_name, pre_command, post_command, profile = request
         criteria = (
             ('cert_storage_location', nss_dir, certmonger.NPATH),
             ('cert_nickname', nickname, None),
             ('ca_name', ca_name, None),
+            ('template_profile', profile, None),
         )
         request_id = certmonger.get_request_id(criteria)
         if request_id is None:
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 0b93b72..e78bdb8 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -310,9 +310,10 @@ def stop_tracking_certificates(dogtag_constants):
     cmonger.start()
 
     for nickname in ['Server-Cert cert-pki-ca',
-                        'auditSigningCert cert-pki-ca',
-                        'ocspSigningCert cert-pki-ca',
-                        'subsystemCert cert-pki-ca']:
+                     'auditSigningCert cert-pki-ca',
+                     'ocspSigningCert cert-pki-ca',
+                     'subsystemCert cert-pki-ca',
+                     'caSigningCert cert-pki-ca']:
         try:
             certmonger.stop_tracking(
                 dogtag_constants.ALIAS_DIR, nickname=nickname)
@@ -1425,12 +1426,16 @@ class CAInstance(service.Service):
                 'Unable to determine PIN for CA instance: %s' % e)
 
     def configure_renewal(self):
+        reqs = (
+            ('auditSigningCert cert-pki-ca', None),
+            ('ocspSigningCert cert-pki-ca',  None),
+            ('subsystemCert cert-pki-ca',    None),
+            ('caSigningCert cert-pki-ca',    'ipaCACertRenewal'),
+        )
         pin = self.__get_ca_pin()
 
         # Server-Cert cert-pki-ca is renewed per-server
-        for nickname in ['auditSigningCert cert-pki-ca',
-                         'ocspSigningCert cert-pki-ca',
-                         'subsystemCert cert-pki-ca']:
+        for nickname, profile in reqs:
             try:
                 certmonger.dogtag_start_tracking(
                     ca='dogtag-ipa-ca-renew-agent',
@@ -1439,7 +1444,8 @@ class CAInstance(service.Service):
                     pinfile=None,
                     secdir=self.dogtag_constants.ALIAS_DIR,
                     pre_command='stop_pkicad',
-                    post_command='renew_ca_cert "%s"' % nickname)
+                    post_command='renew_ca_cert "%s"' % nickname,
+                    profile=profile)
             except (ipautil.CalledProcessError, RuntimeError), e:
                 root_logger.error(
                     "certmonger failed to start tracking certificate: %s" % e)
-- 
1.8.5.3

>From e9cf41b6742103cfef7b6edaf3548e73c362ebdb Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Mon, 24 Mar 2014 15:30:53 +0100
Subject: [PATCH 06/13] Add method for setting CA renewal master in LDAP to
 CAInstance.

---
 ipaserver/install/cainstance.py | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index e78bdb8..0f134cf 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -1579,6 +1579,25 @@ class CAInstance(service.Service):
 
         return True
 
+    def set_renewal_master(self):
+        if not self.admin_conn:
+            self.ldap_connect()
+
+        base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
+                     api.env.basedn)
+        filter = '(&(cn=CA)(ipaConfigString=caRenewalMaster))'
+        entries = self.admin_conn.get_entries(base_dn=base_dn, filter=filter,
+                                              attrs_list=['ipaConfigString'])
+        for entry in entries:
+            entry['ipaConfigString'] = [x for x in entry['ipaConfigString']
+                                        if x.lower() != 'carenewalmaster']
+            self.admin_conn.update_entry(entry)
+
+        dn = DN(('cn', 'CA'), ('cn', api.env.host), base_dn)
+        entry = self.admin_conn.get_entry(dn, ['ipaConfigString'])
+        entry['ipaConfigString'].append('caRenewalMaster')
+        self.admin_conn.update_entry(entry)
+
 
 def replica_ca_install_check(config):
     if not config.setup_ca:
-- 
1.8.5.3

>From 17f0238a53b0bac8156da82ed09e9bf31bd503bc Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Wed, 12 Mar 2014 11:35:10 +0100
Subject: [PATCH 07/13] Provide additional functions to ipapython.certmonger.

---
 ipapython/certmonger.py | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/ipapython/certmonger.py b/ipapython/certmonger.py
index e7edc64..782d38b 100644
--- a/ipapython/certmonger.py
+++ b/ipapython/certmonger.py
@@ -277,6 +277,20 @@ def stop_tracking(secdir, request_id=None, nickname=None):
 
     return (stdout, stderr, returncode)
 
+def modify(request_id, profile=None):
+    args = ['/usr/bin/getcert', 'start-tracking',
+            '-i', request_id]
+    if profile:
+        args += ['-T', profile]
+    return ipautil.run(args)
+
+def resubmit_request(request_id, profile=None):
+    args = ['/usr/bin/ipa-getcert', 'resubmit',
+            '-i', request_id]
+    if profile:
+        args += ['-T', profile]
+    return ipautil.run(args)
+
 def _find_IPA_ca():
     """
     Look through all the certmonger CA files to find the one that
@@ -445,6 +459,18 @@ def check_state(dirs):
 
     return reqids
 
+def wait_for_request(request_id):
+    for i in range(0, 120, 5):
+        state = get_request_value(request_id, 'state').strip()
+        if state in ('CA_REJECTED', 'CA_UNREACHABLE', 'CA_UNCONFIGURED',
+                     'NEED_GUIDANCE', 'NEED_CA', 'MONITORING'):
+            break
+        time.sleep(5)
+    else:
+        raise RuntimeError("request timed out")
+
+    return state
+
 if __name__ == '__main__':
     request_id = request_cert("/etc/httpd/alias", "Test", "cn=tiger.example.com,O=IPA", "HTTP/[email protected]")
     csr = get_request_value(request_id, 'csr')
-- 
1.8.5.3

>From 3454961fefd9ba492d9517bc1f101cadd52f1247 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Thu, 27 Feb 2014 15:09:10 +0100
Subject: [PATCH 08/13] Move external cert validation from ipa-server-install
 to installutils.

---
 install/tools/ipa-server-install  | 45 +++-------------------------------
 ipaserver/install/installutils.py | 51 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 54 insertions(+), 42 deletions(-)

diff --git a/install/tools/ipa-server-install b/install/tools/ipa-server-install
index 34393b7..90f8b70 100755
--- a/install/tools/ipa-server-install
+++ b/install/tools/ipa-server-install
@@ -70,7 +70,6 @@ from ipapython import ipautil
 from ipapython import dogtag
 from ipalib import api, errors, util
 from ipapython.config import IPAOptionParser
-from ipalib.x509 import load_certificate_from_file, load_certificate_chain_from_file
 from ipalib.util import validate_domain_name
 from ipalib.constants import CACERT
 from ipapython import services as ipaservices
@@ -707,48 +706,12 @@ def main():
 
     if options.external_cert_file:
         try:
-            extcert = load_certificate_from_file(options.external_cert_file)
-        except IOError, e:
-            print "Can't load the PEM certificate: %s." % str(e)
-            sys.exit(1)
-        except nss.error.NSPRError:
-            print "'%s' is not a valid PEM-encoded certificate." % options.external_cert_file
-            sys.exit(1)
-
-        certsubject = DN(str(extcert.subject))
-        wantsubject = DN(('CN','Certificate Authority'), options.subject)
-        if certsubject != wantsubject:
-            print "Subject of the external certificate is not correct (got %s, expected %s)." % (certsubject, wantsubject)
-            sys.exit(1)
-
-        try:
-            extchain = load_certificate_chain_from_file(options.external_ca_file)
-        except IOError, e:
-            print "Can't load the external CA chain: %s." % str(e)
-            sys.exit(1)
-        except nss.error.NSPRError:
-            print "'%s' is not a valid PEM-encoded certificate chain." % options.external_ca_file
-            sys.exit(1)
-
-        certdict = dict((DN(str(cert.subject)), cert) for cert in extchain)
-        del extchain
-        certissuer = DN(str(extcert.issuer))
-        if certissuer not in certdict:
-            print "The external certificate is not signed by the external CA (unknown issuer %s)." % certissuer
+            validate_external_cert(options.external_cert_file,
+                                   options.external_ca_file, options.subject)
+        except ValueError, e:
+            print e
             sys.exit(1)
 
-        cert = extcert
-        del extcert
-        while cert.issuer != cert.subject:
-            certissuer = DN(str(cert.issuer))
-            if certissuer not in certdict:
-                print "The external CA chain is incomplete (%s is missing from the chain)." % certissuer
-                sys.exit(1)
-            del cert
-            cert = certdict[certissuer]
-        del certdict
-        del cert
-
     # We only set up the CA if the PKCS#12 options are not given.
     if options.dirsrv_pkcs12:
         setup_ca = False
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index daf81e8..1c7afe2 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -33,13 +33,14 @@ from contextlib import contextmanager
 from dns import resolver, rdatatype
 from dns.exception import DNSException
 import ldap
+from nss.error import NSPRError
 
 from ipapython import ipautil, sysrestore, admintool, dogtag
 from ipapython.admintool import ScriptError
 from ipapython.ipa_log_manager import *
 from ipalib.util import validate_hostname
 from ipapython import config
-from ipalib import errors
+from ipalib import errors, x509
 from ipapython.dn import DN
 from ipaserver.install import certs
 from ipapython import services as ipaservices
@@ -846,3 +847,51 @@ def stopped_service(service, instance_name=""):
         finally:
             root_logger.debug('Starting %s%s.', service, log_instance_name)
             ipaservices.knownservices[service].start(instance_name)
+
+
+def validate_external_cert(cert_file, ca_file, subject_base):
+    extcert = None
+    try:
+        extcert = x509.load_certificate_from_file(cert_file)
+        certsubject = DN(str(extcert.subject))
+        certissuer = DN(str(extcert.issuer))
+    except IOError, e:
+        raise ValueError("Can't load the PEM certificate: %s." % e)
+    except (TypeError, NSPRError):
+        raise ValueError(
+            "'%s' is not a valid PEM-encoded certificate." % cert_file)
+    finally:
+        del extcert
+
+    wantsubject = DN(('CN', 'Certificate Authority'), subject_base)
+    if certsubject != wantsubject:
+        raise ValueError(
+            "Subject of the external certificate is not correct (got %s, "
+            "expected %s)." % (certsubject, wantsubject))
+
+    extchain = None
+    try:
+        extchain = x509.load_certificate_chain_from_file(ca_file)
+        certdict = dict((DN(str(cert.subject)), DN(str(cert.issuer)))
+                        for cert in extchain)
+    except IOError, e:
+        raise ValueError("Can't load the external CA chain: %s." % e)
+    except (TypeError, NSPRError):
+        raise ValueError(
+            "'%s' is not a valid PEM-encoded certificate chain." % ca_file)
+    finally:
+        del extchain
+
+    if certissuer not in certdict:
+        raise ValueError(
+            "The external certificate is not signed by the external CA "
+            "(unknown issuer %s)." % certissuer)
+
+    while certsubject != certissuer:
+        certsubject = certissuer
+        try:
+            certissuer = certdict[certsubject]
+        except KeyError:
+            raise ValueError(
+                "The external CA chain is incomplete (%s is missing from the "
+                "chain)." % certsubject)
-- 
1.8.5.3

>From b8af412e34cd1f5ddf4a27fbe3596f124f129d2d Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Thu, 13 Mar 2014 10:27:54 +0100
Subject: [PATCH 09/13] Add method for verifying CA certificates to
 NSSDatabase.

---
 ipaserver/install/certs.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index 9ee854e..b7c0465 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -276,6 +276,28 @@ class NSSDatabase(object):
 
         return None
 
+    def verify_ca_cert_validity(self, nickname):
+        certdb = cert = None
+        nss.nss_init(self.secdir)
+        try:
+            certdb = nss.get_default_certdb()
+            cert = nss.find_cert_from_nickname(nickname)
+            intended_usage = (nss.certificateUsageUserCertImport |
+                              nss.certificateUsageVerifyCA |
+                              nss.certificateUsageProtectedObjectSigner |
+                              nss.certificateUsageAnyCA)
+            try:
+                approved_usage = cert.verify_now(certdb, True, intended_usage)
+            except NSPRError, e:
+                if e.errno != -8102:    # SEC_ERROR_INADEQUATE_KEY_USAGE
+                    raise ValueError(e.strerror)
+                approved_usage = 0
+            if approved_usage & intended_usage != intended_usage:
+                raise ValueError('invalid for a CA')
+        finally:
+            del certdb, cert
+            nss.nss_shutdown()
+
 
 class CertDB(object):
     """An IPA-server-specific wrapper around NSS
-- 
1.8.5.3

>From d61bb0bceae81c3407d76bd2674e02b4b3c9d424 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Wed, 19 Mar 2014 14:11:23 +0100
Subject: [PATCH 10/13] Add permissions for CA certificate renewal.

---
 install/updates/40-delegation.update | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update
index 3f1ff16..f77461a 100644
--- a/install/updates/40-delegation.update
+++ b/install/updates/40-delegation.update
@@ -362,6 +362,26 @@ replace:aci:'(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX";)(targetattr
 # admins group
 replace:aci:'(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX";)(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)::(targetfilter = "(!(cn=admins))")(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX";)(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)'
 
+dn: cn=Add CA Signing Certificate For Renewal,cn=permissions,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: ipapermission
+default:cn: Add CA Signing Certificate For Renewal
+default:member: cn=Certificate Administrators,cn=privileges,cn=pbac,$SUFFIX
+default:ipapermissiontype: SYSTEM
+
+dn: cn=Modify CA Signing Certificate For Renewal,cn=permissions,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: ipapermission
+default:cn: Modify CA Signing Certificate For Renewal
+default:member: cn=Certificate Administrators,cn=privileges,cn=pbac,$SUFFIX
+default:ipapermissiontype: SYSTEM
+
+dn: $SUFFIX
+add:aci:'(target = "ldap:///cn=caSigningCert cert-pki-ca,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX")(version 3.0; acl "permission:Add CA Signing Certificate For Renewal"; allow (add) groupdn = "ldap:///cn=Add CA Signing Certificate For Renewal,cn=permissions,cn=pbac,$SUFFIX";)'
+add:aci:'(target = "ldap:///cn=caSigningCert cert-pki-ca,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX")(targetattr = userCertificate)(version 3.0; acl "permission:Modify CA Signing Certificate For Renewal"; allow (write) groupdn = "ldap:///cn=Modify CA Signing Certificate For Renewal,cn=permissions,cn=pbac,$SUFFIX";)'
+
 dn: cn=ipa,cn=etc,$SUFFIX
 add:aci:'(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(version 3.0; acl "Add CA Certificates for renewals"; allow(add) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)'
 add:aci:'(target = "ldap:///cn=*,cn=ca_renewal,cn=ipa,cn=etc,$SUFFIX";)(targetattr = "userCertificate")(version 3.0; acl "Modify CA Certificates for renewals"; allow(write) userdn = "ldap:///fqdn=$FQDN,cn=computers,cn=accounts,$SUFFIX";;)'
-- 
1.8.5.3

>From ce0fc3046dd9bcefb2f82a7d71c9b27661451ac4 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Thu, 13 Mar 2014 10:28:27 +0100
Subject: [PATCH 11/13] Add CA certificate management tool ipa-cacert-manage.

Part of https://fedorahosted.org/freeipa/ticket/3737
---
 freeipa.spec.in                        |   2 +
 install/tools/Makefile.am              |   1 +
 install/tools/ipa-cacert-manage        |  23 +++
 install/tools/man/Makefile.am          |   1 +
 install/tools/man/ipa-cacert-manage.1  |  51 ++++++
 ipaserver/install/ipa_cacert_manage.py | 280 +++++++++++++++++++++++++++++++++
 6 files changed, 358 insertions(+)
 create mode 100644 install/tools/ipa-cacert-manage
 create mode 100644 install/tools/man/ipa-cacert-manage.1
 create mode 100644 ipaserver/install/ipa_cacert_manage.py

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 763e40b..d4eb603 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -635,6 +635,7 @@ fi
 %{_sbindir}/ipactl
 %{_sbindir}/ipa-upgradeconfig
 %{_sbindir}/ipa-advise
+%{_sbindir}/ipa-cacert-manage
 %{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit
 %{_libexecdir}/ipa-otpd
 %config(noreplace) %{_sysconfdir}/sysconfig/ipa_memcached
@@ -770,6 +771,7 @@ fi
 %{_mandir}/man1/ipa-backup.1.gz
 %{_mandir}/man1/ipa-restore.1.gz
 %{_mandir}/man1/ipa-advise.1.gz
+%{_mandir}/man1/ipa-cacert-manage.1.gz
 
 %files server-trust-ad
 %{_sbindir}/ipa-adtrust-install
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index 2cf66c6..76857fb 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -24,6 +24,7 @@ sbin_SCRIPTS =			\
 	ipa-backup		\
 	ipa-restore		\
 	ipa-advise		\
+	ipa-cacert-manage	\
 	$(NULL)
 
 EXTRA_DIST =			\
diff --git a/install/tools/ipa-cacert-manage b/install/tools/ipa-cacert-manage
new file mode 100644
index 0000000..5e969b7
--- /dev/null
+++ b/install/tools/ipa-cacert-manage
@@ -0,0 +1,23 @@
+#! /usr/bin/python2 -E
+# Authors: Jan Cholasta <[email protected]>
+#
+# Copyright (C) 2014  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+from ipaserver.install.ipa_cacert_manage import CACertManage
+
+CACertManage.run_cli()
diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am
index 33e8a9e..413f0fd 100644
--- a/install/tools/man/Makefile.am
+++ b/install/tools/man/Makefile.am
@@ -22,6 +22,7 @@ man1_MANS = 				\
 	ipa-backup.1			\
 	ipa-restore.1			\
 	ipa-advise.1			\
+	ipa-cacert-manage.1		\
         $(NULL)
 
 man8_MANS =				\
diff --git a/install/tools/man/ipa-cacert-manage.1 b/install/tools/man/ipa-cacert-manage.1
new file mode 100644
index 0000000..007cbf2
--- /dev/null
+++ b/install/tools/man/ipa-cacert-manage.1
@@ -0,0 +1,51 @@
+.\" A man page for ipa-cacert-manage
+.\" Copyright (C) 2014 Red Hat, Inc.
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful, but
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+.\" General Public License for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
+.\"
+.\" Author: Jan Cholasta <[email protected]>
+.\"
+.TH "ipa-cacert-manage" "1" "Aug 12 2013" "FreeIPA" "FreeIPA Manual Pages"
+.SH "NAME"
+ipa\-cacert\-manage \- Manage IPA CA certificates
+.SH "SYNOPSIS"
+ipa\-cacert\-manage [\fIOPTION\fR]... [COMMAND]
+.SH "DESCRIPTION"
+Manage CA certificates. The available commands are:
+.TP
+\fBrenew\fR
+\- Renew IPA CA certificate. Self-signed CA certificates are renewed in one step. CA certificates signed by an external CA are renewed in two steps. In the first step a certificate request is generated in /var/lib/ipa/ca.csr. In the second step, the certificate created by signing the request and the external CA certificate are set using the \-\-external\-cert\-file and \-\-external\-ca\-file options.
+.SH "OPTIONS"
+.TP
+\fB\-p\fR \fIDM_PASSWORD\fR, \fB\-\-password\fR=\fIDM_PASSWORD\fR
+The Directory Manager password to use for authentication.
+.TP
+\fB\-\-external\-cert\-file\fR=\fIFILE\fR
+PEM file containing a certificate signed by the external CA. Must be given with \-\-external\-ca\-file.
+.TP
+\fB\-\-external\-ca\-file\fR=\fIFILE\fR
+PEM file containing the external CA chain.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Print debugging information.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+Output only errors.
+.TP
+\fB\-\-external\-cert\-file\fR=\fIFILE\fR
+Log to the given file.
+.SH "EXIT STATUS"
+0 if the command was successful
+
+1 if an error occurred
diff --git a/ipaserver/install/ipa_cacert_manage.py b/ipaserver/install/ipa_cacert_manage.py
new file mode 100644
index 0000000..9ab0e4d
--- /dev/null
+++ b/ipaserver/install/ipa_cacert_manage.py
@@ -0,0 +1,280 @@
+# Authors: Jan Cholasta <[email protected]>
+#
+# Copyright (C) 2014  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import time
+from optparse import OptionGroup
+import base64
+from nss import nss
+from nss.error import NSPRError
+import krbV
+
+from ipapython import admintool, certmonger, ipautil
+from ipapython.dn import DN
+from ipalib import api, errors, x509, util
+from ipaserver.install import certs, cainstance, installutils
+from ipaserver.plugins.ldap2 import ldap2
+
+
+class CACertManage(admintool.AdminTool):
+    command_name = 'ipa-cacert-manage'
+
+    usage = "%prog renew [options]"
+
+    description = "Manage CA certificates."
+
+    cert_nickname = 'caSigningCert cert-pki-ca'
+
+    @classmethod
+    def add_options(cls, parser):
+        super(CACertManage, cls).add_options(parser)
+
+        parser.add_option(
+            "-p", "--password", dest='password',
+            help="Directory Manager password")
+
+        renew_group = OptionGroup(parser, "Renew options")
+        renew_group.add_option(
+            "--external-cert-file", dest='external_cert_file',
+            help="PEM file containing a certificate signed by the external CA")
+        renew_group.add_option(
+            "--external-ca-file", dest='external_ca_file',
+            help="PEM file containing the external CA chain")
+        parser.add_option_group(renew_group)
+
+    def validate_options(self):
+        super(CACertManage, self).validate_options(needs_root=True)
+
+        installutils.check_server_configuration()
+
+        parser = self.option_parser
+
+        if not self.args:
+            parser.error("command not provided")
+
+        command = self.command = self.args[0]
+        options = self.options
+
+        if command == 'renew':
+            if options.external_cert_file and not options.external_ca_file:
+                parser.error("--external-ca-file not specified")
+            elif not options.external_cert_file and options.external_ca_file:
+                parser.error("--external-cert-file not specified")
+        else:
+            parser.error("unknown command \"%s\"" % command)
+
+    def run(self):
+        command = self.command
+        options = self.options
+
+        api.bootstrap(in_server=True)
+        api.finalize()
+
+        if command == 'renew' and options.external_cert_file:
+            self.conn = self.ldap_connect()
+        else:
+            self.conn = None
+
+        try:
+            if command == 'renew':
+                rc = self.renew()
+        finally:
+            if self.conn is not None:
+                self.conn.disconnect()
+
+        return rc
+
+    def ldap_connect(self):
+        conn = ldap2()
+
+        password = self.options.password
+        if not password:
+            try:
+                ccache = krbV.default_context().default_ccache()
+                conn.connect(ccache=ccache)
+            except (krbV.Krb5Error, errors.ACIError):
+                pass
+            else:
+                return conn
+
+            password = installutils.read_password(
+                "Directory Manager", confirm=False, validate=False)
+            if password is None:
+                raise admintool.ScriptError(
+                    "Directory Manager password required")
+
+        conn.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=password)
+
+        return conn
+
+    def renew(self):
+        ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
+        if not ca.is_configured():
+            raise admintool.ScriptError("CA is not configured on this system")
+
+        nss_dir = ca.dogtag_constants.ALIAS_DIR
+        criteria = (('cert_storage_location', nss_dir, certmonger.NPATH),
+                    ('cert_nickname', self.cert_nickname, None))
+        self.request_id = certmonger.get_request_id(criteria)
+        if self.request_id is None:
+            raise admintool.ScriptError(
+                "CA certificate is not tracked by certmonger")
+        self.log.debug(
+            "Found certmonger request id %r", self.request_id)
+
+        db = certs.CertDB(api.env.realm, nssdir=nss_dir)
+        cert = db.get_cert_from_db(self.cert_nickname, pem=False)
+
+        options = self.options
+        if options.external_cert_file:
+            return self.renew_external_step_2(ca, cert)
+
+        if x509.is_self_signed(cert, x509.DER):
+            return self.renew_self_signed(ca)
+        else:
+            return self.renew_external_step_1(ca)
+
+    def renew_self_signed(self, ca):
+        try:
+            ca.set_renewal_master()
+        except errors.NotFound:
+            raise admintool.ScriptError("CA renewal master not found")
+
+        self.resubmit_request('ipaCACertRenewal')
+
+        print "CA certificate successfully renewed"
+
+    def renew_external_step_1(self, ca):
+        self.resubmit_request('ipaCSRExport')
+
+        print("The next step is to get /var/lib/ipa/ca.csr signed by your CA "
+              "and re-run ipa-cacert-manage as:")
+        print("ipa-cacert-manage renew "
+              "--external-cert-file=/path/to/signed_certificate "
+              "--external-ca-file=/path/to/external_ca_certificate")
+
+    def renew_external_step_2(self, ca, old_cert):
+        options = self.options
+        cert_filename = options.external_cert_file
+
+        nss_cert = None
+        nss.nss_init(ca.dogtag_constants.ALIAS_DIR)
+        try:
+            try:
+                installutils.validate_external_cert(
+                    cert_filename, options.external_ca_file,
+                    x509.subject_base())
+            except ValueError, e:
+                raise admintool.ScriptError(e)
+
+            nss_cert = x509.load_certificate(old_cert, x509.DER)
+            subject = nss_cert.subject
+            issuer = nss_cert.issuer
+            #pylint: disable=E1101
+            pkinfo = nss_cert.subject_public_key_info.format()
+            #pylint: enable=E1101
+
+            nss_cert = x509.load_certificate_from_file(cert_filename)
+            if not nss_cert.is_ca_cert():
+                raise admintool.ScriptError("Not a CA certificate")
+            if nss_cert.subject != subject:
+                raise admintool.ScriptError("Subject name mismatch")
+            if nss_cert.issuer != issuer:
+                raise admintool.ScriptError("Issuer mismatch")
+            #pylint: disable=E1101
+            if nss_cert.subject_public_key_info.format() != pkinfo:
+                raise admintool.ScriptError("Subject public key info mismatch")
+            #pylint: enable=E1101
+            cert = nss_cert.der_data
+        finally:
+            del nss_cert
+            nss.nss_shutdown()
+
+        with certs.NSSDatabase() as tmpdb:
+            pw = ipautil.write_tmp_file(ipautil.ipa_generate_password())
+            tmpdb.create_db(pw.name)
+            tmpdb.add_single_pem_cert('IPA CA', ',,',
+                                      x509.make_pem(base64.b64encode(old_cert)))
+
+            try:
+                tmpdb.add_single_pem_cert('IPA CA', ',,',
+                                          x509.make_pem(base64.b64encode(cert)))
+            except ipautil.CalledProcessError:
+                raise admintool.ScriptError("Failed to import the certificate")
+
+            try:
+                tmpdb.verify_ca_cert_validity('IPA CA')
+            except ValueError, e:
+                raise admintool.ScriptError(
+                    "Not a valid CA certificate: %s" % e)
+
+        dn = DN(('cn', self.cert_nickname), ('cn', 'ca_renewal'),
+                ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
+        try:
+            entry = self.conn.get_entry(dn, ['usercertificate'])
+            entry['usercertificate'] = [cert]
+            self.conn.update_entry(entry)
+        except errors.NotFound:
+            entry = self.conn.make_entry(
+                dn,
+                objectclass=['top', 'pkiuser', 'nscontainer'],
+                cn=[self.cert_nickname],
+                usercertificate=[cert])
+            self.conn.add_entry(entry)
+        except errors.EmptyModlist:
+            pass
+
+        try:
+            ca.set_renewal_master()
+        except errors.NotFound:
+            raise admintool.ScriptError("CA renewal master not found")
+
+        self.resubmit_request('ipaCACertRenewal')
+
+        print "CA certificate successfully renewed"
+
+    def resubmit_request(self, profile):
+        self.log.debug("resubmitting certmonger request '%s'", self.request_id)
+        certmonger.resubmit_request(self.request_id, profile=profile)
+        try:
+            state = certmonger.wait_for_request(self.request_id)
+        except RuntimeError:
+            raise admintool.ScriptError(
+                "Resubmitting certmonger request '%s' timed out, "
+                "please check the request manually" % self.request_id)
+        self.log.debug("certmonger request is in state %r", state)
+        if state != 'MONITORING':
+            raise admintool.ScriptError(
+                "Error resubmitting certmonger request '%s', "
+                "please check the request manually" % self.request_id)
+
+        if profile != 'ipaCACertRenewal':
+            self.log.debug("modifying certmonger request '%s'", self.request_id)
+            certmonger.modify(self.request_id, profile='ipaCACertRenewal')
+            try:
+                state = certmonger.wait_for_request(self.request_id)
+            except RuntimeError:
+                raise admintool.ScriptError(
+                    "Modifying certmonger request '%s' timed out, "
+                    "please check the request manually" % self.request_id)
+            self.log.debug("certmonger request is in state %r", state)
+            if state != 'MONITORING':
+                raise admintool.ScriptError(
+                    "Error modifying certmonger request '%s', "
+                    "please check the request manually" % self.request_id)
-- 
1.8.5.3

>From facffe21445c1e6615e844247ef6b1dbbde11d7f Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Wed, 12 Mar 2014 11:36:30 +0100
Subject: [PATCH 12/13] Alert user when externally signed CA is about to
 expire.

---
 install/certmonger/dogtag-ipa-ca-renew-agent-submit | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 8a1bd83..ea6b433 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -278,6 +278,8 @@ def renew_ca_cert():
     if not cert:
         return (REJECTED, "New certificate requests not supported")
 
+    is_self_signed = x509.is_self_signed(cert)
+
     operation = os.environ.get('CERTMONGER_OPERATION')
     if operation == 'SUBMIT':
         result = retrieve_cert()
@@ -288,7 +290,7 @@ def renew_ca_cert():
                 return result
 
         state = 'retrieve'
-        if x509.is_self_signed(cert):
+        if is_self_signed:
             ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
             if ca.is_renewal_master():
                 state = 'request'
@@ -307,6 +309,10 @@ def renew_ca_cert():
 
     if state == 'retrieve':
         result = retrieve_cert()
+        if not is_self_signed and result[0] != ISSUED:
+            syslog.syslog(syslog.LOG_ALERT,
+                          "IPA CA certificate is about to expire, "
+                          "use ipa-cacert-manage to renew it")
     elif state == 'request':
         os.environ['CERTMONGER_CA_PROFILE'] = 'caCACert'
         result = request_and_store_cert()
-- 
1.8.5.3

>From 4ce1dc442f1e2753ce140faaf946244211bcbbec Mon Sep 17 00:00:00 2001
From: Jan Cholasta <[email protected]>
Date: Wed, 19 Mar 2014 12:56:03 +0100
Subject: [PATCH 13/13] Load sysupgrade.state on demand.

This prevents SELinux denials when the sysupgrade module is imported in a
confined process.
---
 ipaserver/install/sysupgrade.py | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/ipaserver/install/sysupgrade.py b/ipaserver/install/sysupgrade.py
index c508d2b..b02ab8e 100644
--- a/ipaserver/install/sysupgrade.py
+++ b/ipaserver/install/sysupgrade.py
@@ -26,17 +26,25 @@ from ipapython.ipa_log_manager import *
 STATEFILE_DIR = '/var/lib/ipa/sysupgrade'
 STATEFILE_FILE = 'sysupgrade.state'
 
-_sstore = sysrestore.StateFile(STATEFILE_DIR, STATEFILE_FILE)
+_sstore = None
+
+def _load_sstore():
+    global _sstore
+    if _sstore is None:
+        _sstore = sysrestore.StateFile(STATEFILE_DIR, STATEFILE_FILE)
 
 def get_upgrade_state(module, state):
+    _load_sstore()
     global _sstore
     return _sstore.get_state(module, state)
 
 def set_upgrade_state(module, state, value):
+    _load_sstore()
     global _sstore
     _sstore.backup_state(module, state, value)
 
 def remove_upgrade_state(module, state):
+    _load_sstore()
     global _sstore
     _sstore.delete_state(module, state)
 
-- 
1.8.5.3

_______________________________________________
Freeipa-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to