pgreco / rpms / ipa

Forked from forks/areguera/rpms/ipa 4 years ago
Clone
86baa9
From 0a65f076441cfbbf659e109ea6de026504261dba Mon Sep 17 00:00:00 2001
86baa9
From: Fraser Tweedale <ftweedal@redhat.com>
86baa9
Date: Fri, 22 Mar 2019 16:53:53 +1100
86baa9
Subject: [PATCH] Add ipa-cert-fix tool
86baa9
86baa9
The ipa-cert-fix tool wraps `pki-server cert-fix`, performing
86baa9
additional certificate requests for non-Dogtag IPA certificates and
86baa9
performing additional actions.  In particular:
86baa9
86baa9
- Run cert-fix with arguments particular to the IPA deployment.
86baa9
86baa9
- Update IPA RA certificate in the ipara user entry (if renewed).
86baa9
86baa9
- Add shared certificates (if renewed) to the ca_renewal LDAP
86baa9
  container for replication.
86baa9
86baa9
- Become the CA renewal master if shared certificates were renewed.
86baa9
  This ensures other CA replicas, including the previous CA renewal
86baa9
  master if not the current host, pick up those new certificates
86baa9
  when Certmonger attempts to renew them.
86baa9
86baa9
Fixes: https://pagure.io/freeipa/issue/7885
86baa9
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
86baa9
---
86baa9
 freeipa.spec.in                   |   2 +
86baa9
 install/tools/Makefile.am         |   1 +
86baa9
 install/tools/ipa-cert-fix        |   8 +
86baa9
 ipaserver/install/ipa_cert_fix.py | 276 ++++++++++++++++++++++++++++++
86baa9
 4 files changed, 287 insertions(+)
86baa9
 create mode 100755 install/tools/ipa-cert-fix
86baa9
 create mode 100644 ipaserver/install/ipa_cert_fix.py
86baa9
86baa9
diff --git a/freeipa.spec.in b/freeipa.spec.in
86baa9
index 34394b5caa811bffc677da9644120c90a09b1e81..775394619ab0eb682935c0d28fe434bcf8248a01 100644
86baa9
--- a/freeipa.spec.in
86baa9
+++ b/freeipa.spec.in
86baa9
@@ -995,6 +995,7 @@ install/tools/ipa-adtrust-install
86baa9
 install/tools/ipa-backup
86baa9
 install/tools/ipa-ca-install
86baa9
 install/tools/ipa-cacert-manage
86baa9
+install/tools/ipa-cert-fix
86baa9
 install/tools/ipa-compat-manage
86baa9
 install/tools/ipa-crlgen-manage
86baa9
 install/tools/ipa-csreplica-manage
86baa9
@@ -1383,6 +1384,7 @@ fi
86baa9
 %{_sbindir}/ipa-winsync-migrate
86baa9
 %{_sbindir}/ipa-pkinit-manage
86baa9
 %{_sbindir}/ipa-crlgen-manage
86baa9
+%{_sbindir}/ipa-cert-fix
86baa9
 %{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit
86baa9
 %{_libexecdir}/certmonger/ipa-server-guard
86baa9
 %dir %{_libexecdir}/ipa
86baa9
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
86baa9
index 9dcd76762f678684b421ce4a7b7b27e673a3ff94..6cdfd109552bff2effbcfabe77ff8bf44c1c60da 100644
86baa9
--- a/install/tools/Makefile.am
86baa9
+++ b/install/tools/Makefile.am
86baa9
@@ -30,6 +30,7 @@ dist_sbin_SCRIPTS =		\
86baa9
 	ipa-winsync-migrate	\
86baa9
 	ipa-pkinit-manage	\
86baa9
 	ipa-crlgen-manage	\
86baa9
+	ipa-cert-fix		\
86baa9
 	$(NULL)
86baa9
 
86baa9
 appdir = $(libexecdir)/ipa/
86baa9
diff --git a/install/tools/ipa-cert-fix b/install/tools/ipa-cert-fix
86baa9
new file mode 100755
86baa9
index 0000000000000000000000000000000000000000..e1fc6f056a2a02ee350a2b09433be6cef46e514a
86baa9
--- /dev/null
86baa9
+++ b/install/tools/ipa-cert-fix
86baa9
@@ -0,0 +1,8 @@
86baa9
+#! /usr/bin/python2 -E
86baa9
+#
86baa9
+# Copyright (C) 2019  FreeIPA Contributors see COPYING for license
86baa9
+#
86baa9
+
86baa9
+from ipaserver.install.ipa_cert_fix import IPACertFix
86baa9
+
86baa9
+IPACertFix.run_cli()
86baa9
diff --git a/ipaserver/install/ipa_cert_fix.py b/ipaserver/install/ipa_cert_fix.py
86baa9
new file mode 100644
86baa9
index 0000000000000000000000000000000000000000..3d9070eac1e7dc03840215dffeb4d73f4d3d0a47
86baa9
--- /dev/null
86baa9
+++ b/ipaserver/install/ipa_cert_fix.py
86baa9
@@ -0,0 +1,276 @@
86baa9
+#
86baa9
+# Copyright (C) 2019  FreeIPA Contributors see COPYING for license
86baa9
+#
86baa9
+
86baa9
+# ipa-cert-fix performs the following steps:
86baa9
+#
86baa9
+# 1. Confirm running as root (AdminTool.validate_options does this)
86baa9
+#
86baa9
+# 2. Confirm that DS is up.
86baa9
+#
86baa9
+# 3. Determine which of following certs (if any) need renewing
86baa9
+#     - IPA RA
86baa9
+#     - Apache HTTPS
86baa9
+#     - 389 LDAPS
86baa9
+#     - Kerberos KDC (PKINIT)
86baa9
+#
86baa9
+# 4. Execute `pki-server cert-fix` with relevant options,
86baa9
+#    including `--extra-cert SERIAL` for each cert from #3.
86baa9
+#
86baa9
+# 5. Print details of renewed certificates.
86baa9
+#
86baa9
+# 6. Install renewed certs from #3 in relevant places
86baa9
+#
86baa9
+# 7. ipactl restart
86baa9
+
86baa9
+from __future__ import print_function, absolute_import
86baa9
+
86baa9
+import datetime
86baa9
+from enum import Enum
86baa9
+import logging
86baa9
+import shutil
86baa9
+
86baa9
+from ipalib import api
86baa9
+from ipalib import x509
86baa9
+from ipaplatform.paths import paths
86baa9
+from ipapython.admintool import AdminTool
86baa9
+from ipapython.certdb import NSSDatabase, EMPTY_TRUST_FLAGS
86baa9
+from ipapython.dn import DN
86baa9
+from ipaserver.install import ca, cainstance, dsinstance, installutils
86baa9
+from ipaserver.install.installutils import is_ipa_configured
86baa9
+from ipapython import ipautil
86baa9
+
86baa9
+msg = """
86baa9
+                          WARNING
86baa9
+
86baa9
+ipa-cert-fix is intended for recovery when expired certificates
86baa9
+prevent the normal operation of FreeIPA.  It should ONLY be used
86baa9
+in such scenarios, and backup of the system, especially certificates
86baa9
+and keys, is STRONGLY RECOMMENDED.
86baa9
+
86baa9
+"""
86baa9
+
86baa9
+logger = logging.getLogger(__name__)
86baa9
+
86baa9
+
86baa9
+class IPACertType(Enum):
86baa9
+    IPARA = "IPA RA"
86baa9
+    HTTPS = "Apache HTTPS"
86baa9
+    LDAPS = "LDAP"
86baa9
+    KDC = "KDC"
86baa9
+
86baa9
+
86baa9
+class IPACertFix(AdminTool):
86baa9
+    command_name = "ipa-cert-fix"
86baa9
+    usage = "%prog"
86baa9
+    description = "Renew expired certificates."
86baa9
+
86baa9
+    def validate_options(self):
86baa9
+        super(IPACertFix, self).validate_options(needs_root=True)
86baa9
+
86baa9
+    def run(self):
86baa9
+        if not is_ipa_configured():
86baa9
+            print("IPA is not configured.")
86baa9
+            return 0  # not really an error
86baa9
+
86baa9
+        if not cainstance.is_ca_installed_locally():
86baa9
+            print("CA is not installed on this server.")
86baa9
+            return 0  # not really an error
86baa9
+
86baa9
+        try:
86baa9
+            ipautil.run(['pki-server', 'cert-fix', '--help'], raiseonerr=True)
86baa9
+        except ipautil.CalledProcessError:
86baa9
+            print(
86baa9
+                "The 'pki-server cert-fix' command is not available; "
86baa9
+                "cannot proceed."
86baa9
+            )
86baa9
+            return 1
86baa9
+
86baa9
+        api.bootstrap(in_server=True, confdir=paths.ETC_IPA)
86baa9
+        api.finalize()
86baa9
+        api.Backend.ldap2.connect()  # ensure DS is up
86baa9
+
86baa9
+        subject_base = dsinstance.DsInstance().find_subject_base()
86baa9
+        if not subject_base:
86baa9
+            raise RuntimeError("Cannot determine certificate subject base.")
86baa9
+
86baa9
+        ca_subject_dn = ca.lookup_ca_subject(api, subject_base)
86baa9
+
86baa9
+        now = datetime.datetime.now() + datetime.timedelta(weeks=2)
86baa9
+        certs, extra_certs = expired_certs(now)
86baa9
+
86baa9
+        if not certs and not extra_certs:
86baa9
+            print("Nothing to do.")
86baa9
+            return 0
86baa9
+
86baa9
+        print(msg)
86baa9
+
86baa9
+        print_intentions(certs, extra_certs)
86baa9
+
86baa9
+        response = ipautil.user_input('Enter "yes" to proceed')
86baa9
+        if response.lower() != 'yes':
86baa9
+            print("Not proceeding.")
86baa9
+            return 0
86baa9
+        print("Proceeding.")
86baa9
+
86baa9
+        run_cert_fix(certs, extra_certs)
86baa9
+
86baa9
+        replicate_dogtag_certs(subject_base, ca_subject_dn, certs)
86baa9
+        install_ipa_certs(subject_base, ca_subject_dn, extra_certs)
86baa9
+
86baa9
+        if any(x != 'sslserver' for x in certs) \
86baa9
+                or any(x[0] is IPACertType.IPARA for x in extra_certs):
86baa9
+            # we renewed a "shared" certificate, therefore we must
86baa9
+            # become the renewal master
86baa9
+            print("Becoming renewal master.")
86baa9
+            cainstance.CAInstance().set_renewal_master()
86baa9
+
86baa9
+        ipautil.run(['ipactl', 'restart'], raiseonerr=True)
86baa9
+
86baa9
+
86baa9
+def expired_certs(now):
86baa9
+    return expired_dogtag_certs(now), expired_ipa_certs(now)
86baa9
+
86baa9
+
86baa9
+def expired_dogtag_certs(now):
86baa9
+    """
86baa9
+    Determine which Dogtag certs are expired, or close to expiry.
86baa9
+
86baa9
+    Return a list of (cert_id, cert) pairs.
86baa9
+
86baa9
+    """
86baa9
+    certs = []
86baa9
+    db = NSSDatabase(nssdir=paths.PKI_TOMCAT_ALIAS_DIR)
86baa9
+
86baa9
+    for certid, nickname in [
86baa9
+        ('sslserver', 'Server-Cert cert-pki-ca'),
86baa9
+        ('subsystem', 'subsystemCert cert-pki-ca'),
86baa9
+        ('ca_ocsp_signing', 'ocspSigningCert cert-pki-ca'),
86baa9
+        ('ca_audit_signing', 'auditSigningCert cert-pki-ca'),
86baa9
+        ('kra_transport', 'transportCert cert-pki-kra'),
86baa9
+        ('kra_storage', 'storageCert cert-pki-kra'),
86baa9
+        ('kra_audit_signing', 'auditSigningCert cert-pki-kra'),
86baa9
+    ]:
86baa9
+        try:
86baa9
+            cert = db.get_cert(nickname)
86baa9
+        except RuntimeError:
86baa9
+            pass  # unfortunately certdb doesn't give us a better exception
86baa9
+        else:
86baa9
+            if cert.not_valid_after <= now:
86baa9
+                certs.append((certid, cert))
86baa9
+
86baa9
+    return certs
86baa9
+
86baa9
+
86baa9
+def expired_ipa_certs(now):
86baa9
+    """
86baa9
+    Determine which IPA certs are expired, or close to expiry.
86baa9
+
86baa9
+    Return a list of (IPACertType, cert) pairs.
86baa9
+
86baa9
+    """
86baa9
+    certs = []
86baa9
+
86baa9
+    # IPA RA
86baa9
+    cert = x509.load_certificate_from_file(paths.RA_AGENT_PEM)
86baa9
+    if cert.not_valid_after <= now:
86baa9
+        certs.append((IPACertType.IPARA, cert))
86baa9
+
86baa9
+    # Apache HTTPD
86baa9
+    db = NSSDatabase(nssdir=paths.HTTPD_ALIAS_DIR)
86baa9
+    cert = db.get_cert('Server-Cert')
86baa9
+    if cert.not_valid_after <= now:
86baa9
+        certs.append((IPACertType.HTTPS, cert))
86baa9
+
86baa9
+    # LDAPS
86baa9
+    ds_dbdir = dsinstance.config_dirname(
86baa9
+        installutils.realm_to_serverid(api.env.realm))
86baa9
+    db = NSSDatabase(nssdir=ds_dbdir)
86baa9
+    cert = db.get_cert('Server-Cert')
86baa9
+    if cert.not_valid_after <= now:
86baa9
+        certs.append((IPACertType.LDAPS, cert))
86baa9
+
86baa9
+    # KDC
86baa9
+    cert = x509.load_certificate_from_file(paths.KDC_CERT)
86baa9
+    if cert.not_valid_after <= now:
86baa9
+        certs.append((IPACertType.KDC, cert))
86baa9
+
86baa9
+    return certs
86baa9
+
86baa9
+
86baa9
+def print_intentions(dogtag_certs, ipa_certs):
86baa9
+    print("The following certificates will be renewed: ")
86baa9
+    print()
86baa9
+
86baa9
+    for certid, cert in dogtag_certs:
86baa9
+        print_cert_info("Dogtag", certid, cert)
86baa9
+
86baa9
+    for certtype, cert in ipa_certs:
86baa9
+        print_cert_info("IPA", certtype.value, cert)
86baa9
+
86baa9
+
86baa9
+def print_cert_info(context, desc, cert):
86baa9
+    print("{} {} certificate:".format(context, desc))
86baa9
+    print("  Subject: {}".format(DN(cert.subject)))
86baa9
+    print("  Serial:  {}".format(cert.serial_number))
86baa9
+    print("  Expires: {}".format(cert.not_valid_after))
86baa9
+    print()
86baa9
+
86baa9
+
86baa9
+def run_cert_fix(certs, extra_certs):
86baa9
+    ldapi_path = (
86baa9
+        paths.SLAPD_INSTANCE_SOCKET_TEMPLATE
86baa9
+        % '-'.join(api.env.realm.split('.'))
86baa9
+    )
86baa9
+    cmd = [
86baa9
+        'pki-server',
86baa9
+        'cert-fix',
86baa9
+        '--ldapi-socket', ldapi_path,
86baa9
+        '--agent-uid', 'ipara',
86baa9
+    ]
86baa9
+    for certid, _cert in certs:
86baa9
+        cmd.extend(['--cert', certid])
86baa9
+    for _certtype, cert in extra_certs:
86baa9
+        cmd.extend(['--extra-cert', str(cert.serial_number)])
86baa9
+    ipautil.run(cmd, raiseonerr=True)
86baa9
+
86baa9
+
86baa9
+def replicate_dogtag_certs(subject_base, ca_subject_dn, certs):
86baa9
+    for certid, _oldcert in certs:
86baa9
+        cert_path = "/etc/pki/pki-tomcat/certs/{}.crt".format(certid)
86baa9
+        cert = x509.load_certificate_from_file(cert_path)
86baa9
+        print_cert_info("Renewed Dogtag", certid, cert)
86baa9
+        replicate_cert(subject_base, ca_subject_dn, cert)
86baa9
+
86baa9
+
86baa9
+def install_ipa_certs(subject_base, ca_subject_dn, certs):
86baa9
+    """Print details and install renewed IPA certificates."""
86baa9
+    for certtype, oldcert in certs:
86baa9
+        cert_path = "/etc/pki/pki-tomcat/certs/{}-renewed.crt" \
86baa9
+                .format(oldcert.serial_number)
86baa9
+        cert = x509.load_certificate_from_file(cert_path)
86baa9
+        print_cert_info("Renewed IPA", certtype.value, cert)
86baa9
+
86baa9
+        if certtype is IPACertType.IPARA:
86baa9
+            shutil.copyfile(cert_path, paths.RA_AGENT_PEM)
86baa9
+            cainstance.update_people_entry(cert)
86baa9
+            replicate_cert(subject_base, ca_subject_dn, cert)
86baa9
+        elif certtype is IPACertType.HTTPS:
86baa9
+            db = NSSDatabase(nssdir=paths.HTTPD_ALIAS_DIR)
86baa9
+            db.delete_cert('Server-Cert')
86baa9
+            db.import_pem_cert('Server-Cert', EMPTY_TRUST_FLAGS, cert_path)
86baa9
+        elif certtype is IPACertType.LDAPS:
86baa9
+            ds_dbdir = dsinstance.config_dirname(
86baa9
+                installutils.realm_to_serverid(api.env.realm))
86baa9
+            db = NSSDatabase(nssdir=ds_dbdir)
86baa9
+            db.delete_cert('Server-Cert')
86baa9
+            db.import_pem_cert('Server-Cert', EMPTY_TRUST_FLAGS, cert_path)
86baa9
+        elif certtype is IPACertType.KDC:
86baa9
+            shutil.copyfile(cert_path, paths.KDC_CERT)
86baa9
+
86baa9
+
86baa9
+def replicate_cert(subject_base, ca_subject_dn, cert):
86baa9
+    nickname = cainstance.get_ca_renewal_nickname(
86baa9
+        subject_base, ca_subject_dn, DN(cert.subject))
86baa9
+    if nickname:
86baa9
+        cainstance.update_ca_renewal_entry(api.Backend.ldap2, nickname, cert)
86baa9
-- 
86baa9
2.20.1
86baa9