From 1eb5f67aab5b475d51de361c74533f00926be01c Mon Sep 17 00:00:00 2001
From: Christian Heimes <cheimes@redhat.com>
Date: Fri, 1 Dec 2017 14:53:00 +0100
Subject: [PATCH] Add script to regenerate Custodia keys and config

/usr/libexec/ipa/ipa-custodia-regen can be used to recover from broken
ipa-custodia keys and configuration. It removes existing config and
server key files, regenerates new files and uploads the new public keys
to LDAP.

https://pagure.io/freeipa/issue/6893

Signed-off-by: Christian Heimes <cheimes@redhat.com>
---
 freeipa.spec.in                         |   2 +
 install/tools/Makefile.am               |   1 +
 install/tools/ipa-custodia-regen        |   8 +++
 ipaserver/install/ipa_custodia_regen.py | 113 ++++++++++++++++++++++++++++++++
 4 files changed, 124 insertions(+)
 create mode 100755 install/tools/ipa-custodia-regen
 create mode 100644 ipaserver/install/ipa_custodia_regen.py

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 48fdb93f59..d905fd17dd 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -936,6 +936,7 @@ install/tools/ipa-compat-manage
 install/tools/ipa-csreplica-manage
 install/tools/ipa-custodia
 install/tools/ipa-custodia-check
+install/tools/ipa-custodia-regen
 install/tools/ipa-dns-install
 install/tools/ipa-httpd-kdcproxy
 install/tools/ipa-kra-install
@@ -1326,6 +1327,7 @@ fi
 %dir %{_libexecdir}/ipa
 %{_libexecdir}/ipa/ipa-custodia
 %{_libexecdir}/ipa/ipa-custodia-check
+%{_libexecdir}/ipa/ipa-custodia-regen
 %{_libexecdir}/ipa/ipa-dnskeysyncd
 %{_libexecdir}/ipa/ipa-dnskeysync-replica
 %{_libexecdir}/ipa/ipa-ods-exporter
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index 6b9a64a3d2..9d9c5f6f93 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -35,6 +35,7 @@ appdir = $(libexecdir)/ipa/
 dist_app_SCRIPTS =		\
 	ipa-custodia		\
 	ipa-custodia-check	\
+	ipa-custodia-regen	\
 	ipa-httpd-kdcproxy	\
 	ipa-pki-retrieve-key	\
 	$(NULL)
diff --git a/install/tools/ipa-custodia-regen b/install/tools/ipa-custodia-regen
new file mode 100755
index 0000000000..87f348901f
--- /dev/null
+++ b/install/tools/ipa-custodia-regen
@@ -0,0 +1,8 @@
+#! /usr/bin/python2 -E
+#
+# Copyright (C) 2017  FreeIPA Contributors see COPYING for license
+#
+
+from ipaserver.install.ipa_custodia_regen import IPACustodiaRegen
+
+IPACustodiaRegen.run_cli()
diff --git a/ipaserver/install/ipa_custodia_regen.py b/ipaserver/install/ipa_custodia_regen.py
new file mode 100644
index 0000000000..6d2639dfd7
--- /dev/null
+++ b/ipaserver/install/ipa_custodia_regen.py
@@ -0,0 +1,113 @@
+# Copyright (C) 2017  IPA Project Contributors, see COPYING for license
+"""Regenerate Custodia server.keys and config
+
+Christian Heimes <cheimes@redhat.com>
+"""
+from __future__ import print_function
+
+import logging
+import os
+
+from ipalib import api
+from ipaplatform.paths import paths
+from ipapython import admintool
+from ipapython.ipautil import backup_file
+from ipapython.ipa_log_manager import standard_logging_setup
+from ipaserver.install import installutils
+from ipaserver.install import sysupgrade
+from ipaserver.install.custodiainstance import CustodiaInstance
+
+
+logger = logging.getLogger(__name__)
+
+
+class IPACustodiaRegen(admintool.AdminTool):
+    command_name = 'ipa-custodia-regen'
+
+    usage = "%prog [options]\n"
+
+    @classmethod
+    def add_options(cls, parser):
+        super(IPACustodiaRegen, cls).add_options(parser)
+        parser.add_option(
+            "-r", '--regenerate', action="store_true",
+            dest="regenerate", default=False,
+            help="Perform regeneration"
+        )
+
+    def validate_options(self):
+        super(IPACustodiaRegen, self).validate_options(needs_root=True)
+        try:
+            installutils.check_server_configuration()
+        except RuntimeError as e:
+            raise admintool.ScriptError(e)
+        if not self.options.regenerate:
+            raise admintool.ScriptError(
+                "Nothing to do, --regenerate option not given."
+            )
+
+    def run(self):
+        super(IPACustodiaRegen, self).run()
+
+        # use private in-memory ccache
+        os.environ['KRB5CCNAME'] = 'MEMORY:ipa-custodia-regen'
+        # use host keytab to acquire TGT for LDAP connection
+        os.environ['KRB5_CLIENT_KTNAME'] = paths.KRB5_KEYTAB
+
+        if not api.isdone('finalize'):
+            api.bootstrap(
+                in_server=True,
+                debug=self.options.verbose,
+                confdir=paths.ETC_IPA,
+            )
+            api.finalize()
+        if not api.Backend.ldap2.isconnected():
+            api.Backend.ldap2.connect()
+
+        self.regen()
+
+    def regen(self):
+        custodia = CustodiaInstance(
+            host_name=api.env.host,
+            realm=api.env.realm,
+        )
+        # patch class to use logger
+        custodia.print_msg = logger.info
+
+        # stop service if it is running
+        is_running = custodia.is_running()
+        if is_running:
+            logger.info("Stopping %s", custodia.service_name)
+            custodia.stop()
+
+        # backup and remove old file (if exists)
+        for filename in (custodia.config_file, custodia.server_keys):
+            logger.info("Backing up and removing existing '%s'", filename)
+            backup_file(filename)
+            installutils.remove_file(filename)
+
+        # fake uninstalled state
+        sysupgrade.set_upgrade_state('custodia', 'installed', False)
+
+        # Run install to create server keys and config. This will NOT create
+        # new keys for services such as Lightweight CA keys (Dogtag). Cannot
+        # use upgrade_instance() because it doesn't perform all steps on
+        # FreeIPA 4.4 and 4.5.
+        # create_instance() set upgrade state to installed
+        logger.info("Running create_instance to regenerate config and keys.")
+        custodia.create_instance()
+
+        # start Custodia if it has been running before
+        if is_running:
+            logger.info("Starting %s", custodia.service_name)
+            custodia.start()
+        logger.warning(
+            "It may take a couple of minutes until public keys are "
+            "replicated to other LDAP servers."
+        )
+
+
+if __name__ == '__main__':
+    # hack to enable logging to stdout when executed as script on 4.5.x
+    logger.parent = standard_logging_setup(verbose=True)
+    IPACustodiaRegen.run_cli()
