From 2558a0336e9d61b2b7e321b7dfa32426151b4bbb Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Wed, 3 May 2017 06:09:03 +0000 Subject: [PATCH] server install: fix KDC PKINIT configuration Set `pkinit_pool` in `kdc.conf` to a CA certificate bundle of all CAs known to IPA. Make sure `cacert.pem` is exported in all installation code paths. Use the KDC certificate itself as a PKINIT anchor in `login_password`. https://pagure.io/freeipa/issue/6831 Reviewed-By: Stanislav Laznicka Reviewed-By: Martin Babinsky --- install/restart_scripts/Makefile.am | 1 + install/restart_scripts/renew_kdc_cert | 31 ++++++++++++++++++ install/share/kdc.conf.template | 2 ++ ipaclient/install/ipa_certupdate.py | 1 + ipalib/install/kinit.py | 7 +++-- ipaserver/install/krbinstance.py | 27 +++++++++------- ipaserver/install/server/upgrade.py | 57 ++++++++++++++++++++++++++-------- ipaserver/rpcserver.py | 5 ++- 8 files changed, 103 insertions(+), 28 deletions(-) create mode 100755 install/restart_scripts/renew_kdc_cert diff --git a/install/restart_scripts/Makefile.am b/install/restart_scripts/Makefile.am index 04881b406b6be92b46e630f30d724918506e2aa8..240cebdee8cae7a0c7bdf88f5300583b4232fc94 100644 --- a/install/restart_scripts/Makefile.am +++ b/install/restart_scripts/Makefile.am @@ -5,6 +5,7 @@ app_DATA = \ restart_dirsrv \ restart_httpd \ renew_ca_cert \ + renew_kdc_cert \ renew_ra_cert \ stop_pkicad \ renew_ra_cert_pre \ diff --git a/install/restart_scripts/renew_kdc_cert b/install/restart_scripts/renew_kdc_cert new file mode 100755 index 0000000000000000000000000000000000000000..9247920874fc9540ac3421dd59fd902cc195243f --- /dev/null +++ b/install/restart_scripts/renew_kdc_cert @@ -0,0 +1,31 @@ +#!/usr/bin/python2 -E +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +import os +import syslog +import traceback + +from ipaplatform import services +from ipaplatform.paths import paths +from ipaserver.install import certs + + +def main(): + with certs.renewal_lock: + os.chmod(paths.KDC_CERT, 0o644) + + try: + if services.knownservices.krb5kdc.is_running(): + syslog.syslog(syslog.LOG_NOTICE, 'restarting krb5kdc') + services.knownservices.krb5kdc.restart() + except Exception as e: + syslog.syslog( + syslog.LOG_ERR, "cannot restart krb5kdc: {}".format(e)) + + +try: + main() +except Exception: + syslog.syslog(syslog.LOG_ERR, traceback.format_exc()) diff --git a/install/share/kdc.conf.template b/install/share/kdc.conf.template index ec53a1ff5f7110704143074bc7a5d1dfdc705344..306351b86111eb0e883b2398678f50b821e0ad7f 100644 --- a/install/share/kdc.conf.template +++ b/install/share/kdc.conf.template @@ -13,5 +13,7 @@ default_principal_flags = +preauth ; admin_keytab = $KRB5KDC_KADM5_KEYTAB pkinit_identity = FILE:$KDC_CERT,$KDC_KEY + pkinit_anchors = FILE:$KDC_CERT pkinit_anchors = FILE:$CACERT_PEM + pkinit_pool = FILE:$CA_BUNDLE_PEM } diff --git a/ipaclient/install/ipa_certupdate.py b/ipaclient/install/ipa_certupdate.py index 7e8527e1fcb575844e8f4c90016435124b70e381..93da8422b6f503b8c44db678736d7f71f7d7567e 100644 --- a/ipaclient/install/ipa_certupdate.py +++ b/ipaclient/install/ipa_certupdate.py @@ -172,6 +172,7 @@ class CertUpdate(admintool.AdminTool): certmonger.modify(request_id, ca='dogtag-ipa-ca-renew-agent') self.update_file(paths.CA_CRT, certs) + self.update_file(paths.CACERT_PEM, certs) def update_file(self, filename, certs, mode=0o444): certs = (c[0] for c in certs if c[2] is not False) diff --git a/ipalib/install/kinit.py b/ipalib/install/kinit.py index fb6caee4d6b5fef27b53753b21ad83572da31ac4..73471f103eabfe39580c8fbd0665157f635fa5c5 100644 --- a/ipalib/install/kinit.py +++ b/ipalib/install/kinit.py @@ -96,7 +96,7 @@ def kinit_password(principal, password, ccache_name, config=None, raise RuntimeError(result.error_output) -def kinit_armor(ccache_name, pkinit_anchor=None): +def kinit_armor(ccache_name, pkinit_anchors=None): """ perform anonymous pkinit to obtain anonymous ticket to be used as armor for FAST. @@ -113,8 +113,9 @@ def kinit_armor(ccache_name, pkinit_anchor=None): env = {'LC_ALL': 'C'} args = [paths.KINIT, '-n', '-c', ccache_name] - if pkinit_anchor is not None: - args.extend(['-X', 'X509_anchors=FILE:{}'.format(pkinit_anchor)]) + if pkinit_anchors is not None: + for pkinit_anchor in pkinit_anchors: + args.extend(['-X', 'X509_anchors=FILE:{}'.format(pkinit_anchor)]) # this workaround enables us to capture stderr and put it # into the raised exception in case of unsuccessful authentication diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index e52577bbaa15064946f9a3c9720aa40ffc3251aa..1692e0b2badb23c18386346a552c83881018cf60 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -20,7 +20,6 @@ from __future__ import absolute_import from __future__ import print_function -import shutil import os import pwd import socket @@ -28,6 +27,8 @@ import dbus import dns.name +from ipalib import x509 +from ipalib.install import certstore from ipaserver.install import service from ipaserver.install import installutils from ipapython import ipaldap @@ -430,7 +431,8 @@ class KrbInstance(service.Service): ca=certmonger_ca, dns=self.fqdn, storage='FILE', - profile=KDC_PROFILE) + profile=KDC_PROFILE, + post_command='renew_kdc_cert') except dbus.DBusException as e: # if the certificate is already tracked, ignore the error name = e.get_dbus_name() @@ -448,17 +450,23 @@ class KrbInstance(service.Service): service.set_service_entry_config( 'KDC', self.fqdn, [PKINIT_ENABLED], self.suffix) + def _install_pkinit_ca_bundle(self): + ca_certs = certstore.get_ca_certs(self.api.Backend.ldap2, + self.api.env.basedn, + self.api.env.realm, + False) + ca_certs = [c for c, _n, t, _u in ca_certs if t is not False] + x509.write_certificate_list(ca_certs, paths.CACERT_PEM) + def issue_selfsigned_pkinit_certs(self): self._call_certmonger(certmonger_ca="SelfSign") - # for self-signed certificate, the certificate is its own CA, copy it - # as CA cert - shutil.copyfile(paths.KDC_CERT, paths.CACERT_PEM) + with open(paths.CACERT_PEM, 'w'): + pass def issue_ipa_ca_signed_pkinit_certs(self): try: self._call_certmonger() - # copy IPA CA bundle to the KDC's CA cert bundle - shutil.copyfile(paths.IPA_CA_CRT, paths.CACERT_PEM) + self._install_pkinit_ca_bundle() self.pkinit_enable() except RuntimeError as e: root_logger.error("PKINIT certificate request failed: %s", e) @@ -473,10 +481,7 @@ class KrbInstance(service.Service): certs.install_key_from_p12(self.pkcs12_info[0], self.pkcs12_info[1], paths.KDC_KEY) - # copy IPA CA bundle to the KDC's CA cert bundle - # NOTE: this may not be the same set of CA certificates trusted by - # externally provided PKINIT cert. - shutil.copyfile(paths.IPA_CA_CRT, paths.CACERT_PEM) + self._install_pkinit_ca_bundle() self.pkinit_enable() def setup_pkinit(self): diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 648dc1f29c44f89d9fbceb7b50373d93c88b5c1a..db86353165809c57d1ac27bf762393721231fefd 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -11,6 +11,7 @@ import pwd import fileinput import sys +from augeas import Augeas import dns.exception import six @@ -1527,19 +1528,49 @@ def setup_pkinit(krb): else: krb.issue_selfsigned_pkinit_certs() - # reconfigure KDC just in case in order to handle potentially broken - # 4.5.0 -> 4.5.1 upgrade path - replacevars = dict() - replacevars['pkinit_identity'] = 'FILE:{},{}'.format( - paths.KDC_CERT,paths.KDC_KEY) - appendvars = {} - ipautil.backup_config_and_replace_variables( - krb.fstore, paths.KRB5KDC_KDC_CONF, replacevars=replacevars, - appendvars=appendvars) - tasks.restore_context(paths.KRB5KDC_KDC_CONF) - if krb.is_running(): - krb.stop() - krb.start() + aug = Augeas(flags=Augeas.NO_LOAD | Augeas.NO_MODL_AUTOLOAD, + loadpath=paths.USR_SHARE_IPA_DIR) + try: + aug.transform('IPAKrb5', paths.KRB5KDC_KDC_CONF) + aug.load() + + path = '/files{}/realms/{}'.format(paths.KRB5KDC_KDC_CONF, krb.realm) + modified = False + + value = 'FILE:{},{}'.format(paths.KDC_CERT, paths.KDC_KEY) + expr = '{}[count(pkinit_identity)=1][pkinit_identity="{}"]'.format( + path, value) + if not aug.match(expr): + aug.remove('{}/pkinit_identity'.format(path)) + aug.set('{}/pkinit_identity'.format(path), value) + modified = True + + for value in ['FILE:{}'.format(paths.KDC_CERT), + 'FILE:{}'.format(paths.CACERT_PEM)]: + expr = '{}/pkinit_anchors[.="{}"]'.format(path, value) + if not aug.match(expr): + aug.set('{}/pkinit_anchors[last()+1]'.format(path), value) + modified = True + + value = 'FILE:{}'.format(paths.CA_BUNDLE_PEM) + expr = '{}/pkinit_pool[.="{}"]'.format(path, value) + if not aug.match(expr): + aug.set('{}/pkinit_pool[last()+1]'.format(path), value) + modified = True + + if modified: + try: + aug.save() + except IOError: + for error_path in aug.match('/augeas//error'): + root_logger.error('augeas: %s', aug.get(error_path)) + raise + + if krb.is_running(): + krb.stop() + krb.start() + finally: + aug.close() def disable_httpd_system_trust(http): diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index 996a3d29884ca0180c39841f6986abf9b23ff13a..4cde2815a0fe9332d67c84b531f573ff88b1a302 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -945,7 +945,10 @@ class login_password(Backend, KerberosSession): self.debug('Obtaining armor in ccache %s', armor_path) try: - kinit_armor(armor_path, pkinit_anchor=paths.CACERT_PEM) + kinit_armor( + armor_path, + pkinit_anchors=[paths.KDC_CERT, paths.KDC_CA_BUNDLE_PEM], + ) except RuntimeError as e: self.error("Failed to obtain armor cache") # We try to continue w/o armor, 2FA will be impacted -- 2.9.4