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 <slaznick@redhat.com>
Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
This commit is contained in:
Jan Cholasta
2017-05-03 06:09:03 +00:00
committed by Martin Basti
parent 4d36cbf6ad
commit f769045f0a
8 changed files with 103 additions and 28 deletions

View File

@@ -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 \

View File

@@ -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())

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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):

View File

@@ -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