From c9fb09190ac243bcf45622693944d7e6785141b4 Mon Sep 17 00:00:00 2001 From: Florence Blanc-Renaud Date: Mon, 28 Aug 2017 10:50:58 +0200 Subject: [PATCH] Backport PR 1008 to ipa-4-5 Fix ipa-server-upgrade: This entry already exists ipa-server-upgrade fails when running the ipaload_cacrt plugin. The plugin finds all CA certificates in /etc/httpd/alias and uploads them in LDAP below cn=certificates,cn=ipa,cn=etc,$BASEDN. The issue happens because there is already an entry in LDAP for IPA CA, but with a different DN. The nickname in /etc/httpd/alias can differ from $DOMAIN IPA CA. To avoid the issue: 1/ during upgrade, run a new plugin that removes duplicates and restarts ldap (to make sure that uniqueness attr plugin is working after the new plugin) 2/ modify upload_cacert plugin so that it is using $DOMAIN IPA CA instead of cn=$nickname,cn=ipa,cn=etc,$BASEDN when uploading IPA CA. https://pagure.io/freeipa/issue/7125 Reviewed-By: Fraser Tweedale --- install/updates/90-post_upgrade_plugins.update | 1 + ipalib/install/certstore.py | 19 +++++ .../plugins/update_fix_duplicate_cacrt_in_ldap.py | 84 ++++++++++++++++++++++ ipaserver/install/plugins/upload_cacrt.py | 19 ++++- 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 ipaserver/install/plugins/update_fix_duplicate_cacrt_in_ldap.py diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update index 8477199e07d6729d5847e58bfa67d061bd1410c2..bbc3e29422fc0f139c2ca68a7033863e4c25f8cf 100644 --- a/install/updates/90-post_upgrade_plugins.update +++ b/install/updates/90-post_upgrade_plugins.update @@ -15,6 +15,7 @@ plugin: update_ca_renewal_master plugin: update_idrange_type plugin: update_pacs plugin: update_service_principalalias +plugin: update_fix_duplicate_cacrt_in_ldap plugin: update_upload_cacrt # update_ra_cert_store has to be executed after update_ca_renewal_master plugin: update_ra_cert_store diff --git a/ipalib/install/certstore.py b/ipalib/install/certstore.py index bc2079fb12873444cbe6796eebfdfcfebd0e284d..76181fe47de585974f3fb33ec586f5c576adebb5 100644 --- a/ipalib/install/certstore.py +++ b/ipalib/install/certstore.py @@ -27,6 +27,7 @@ from pyasn1.error import PyAsn1Error from ipapython.dn import DN from ipapython.certdb import get_ca_nickname, TrustFlags from ipalib import errors, x509 +from ipalib.constants import IPA_CA_CN def _parse_cert(dercert): try: @@ -381,3 +382,21 @@ def get_ca_certs_nss(ldap, base_dn, compat_realm, compat_ipa_ca, nss_certs.append((cert, nickname, trust_flags)) return nss_certs + + +def get_ca_subject(ldap, container_ca, base_dn): + """ + Look for the IPA CA certificate subject. + """ + dn = DN(('cn', IPA_CA_CN), container_ca, base_dn) + try: + cacert_subject = ldap.get_entry(dn)['ipacasubjectdn'][0] + except errors.NotFound: + # if the entry doesn't exist, we are dealing with a pre-v4.4 + # installation, where the default CA subject was always based + # on the subject_base. + attrs = ldap.get_ipa_config() + subject_base = attrs.get('ipacertificatesubjectbase')[0] + cacert_subject = DN(('CN', 'Certificate Authority'), subject_base) + + return cacert_subject diff --git a/ipaserver/install/plugins/update_fix_duplicate_cacrt_in_ldap.py b/ipaserver/install/plugins/update_fix_duplicate_cacrt_in_ldap.py new file mode 100644 index 0000000000000000000000000000000000000000..cd4f13a8eb6b5bc9e04fcdd407907497528f8be1 --- /dev/null +++ b/ipaserver/install/plugins/update_fix_duplicate_cacrt_in_ldap.py @@ -0,0 +1,84 @@ +# Authors: +# Florence Blanc-Renaud +# +# Copyright (C) 2017 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 . + +import logging + +from ipalib import Registry, errors +from ipalib import Updater +from ipalib.install import certstore +from ipapython.dn import DN +from ipapython.certdb import get_ca_nickname + +logger = logging.getLogger(__name__) + +register = Registry() + + +@register() +class update_fix_duplicate_cacrt_in_ldap(Updater): + """ + When multiple entries exist for IPA CA cert in ldap, remove the duplicate + + After this plugin, ds needs to be restarted. This ensures that + the attribute uniqueness plugin is working and prevents + other plugins from adding duplicates. + """ + + def execute(self, **options): + # If CA is disabled, no need to check for duplicates of IPA CA + ca_enabled = self.api.Command.ca_is_enabled()['result'] + if not ca_enabled: + return True, [] + + # Look for the IPA CA cert subject + ldap = self.api.Backend.ldap2 + cacert_subject = certstore.get_ca_subject( + ldap, + self.api.env.container_ca, + self.api.env.basedn) + + # Find if there are other certificates with the same subject + # They are duplicates resulting of BZ 1480102 + base_dn = DN(('cn', 'certificates'), ('cn', 'ipa'), ('cn', 'etc'), + self.api.env.basedn) + try: + filter = ldap.make_filter({'ipaCertSubject': cacert_subject}) + result, _truncated = ldap.find_entries( + base_dn=base_dn, + filter=filter, + attrs_list=[]) + except errors.NotFound: + # No duplicate, we're good + logger.debug("No duplicates for IPA CA in LDAP") + return True, [] + + logger.debug("Found %d entrie(s) for IPA CA in LDAP", len(result)) + cacert_dn = DN(('cn', get_ca_nickname(self.api.env.realm)), base_dn) + for entry in result: + if entry.dn == cacert_dn: + continue + # Remove the duplicate + try: + ldap.delete_entry(entry) + logger.debug("Removed the duplicate %s", entry.dn) + except Exception as e: + logger.warning("Failed to remove the duplicate %s: %s", + entry.dn, e) + + return True, [] diff --git a/ipaserver/install/plugins/upload_cacrt.py b/ipaserver/install/plugins/upload_cacrt.py index a1957ca5b675b86f0df36dc820ee31305f54f863..985b74c06e80a3620eb6454c0bd9c7590b04184d 100644 --- a/ipaserver/install/plugins/upload_cacrt.py +++ b/ipaserver/install/plugins/upload_cacrt.py @@ -20,7 +20,7 @@ from ipalib.install import certstore from ipaplatform.paths import paths from ipaserver.install import certs -from ipalib import Registry, errors +from ipalib import Registry, errors, x509 from ipalib import Updater from ipapython import certdb from ipapython.dn import DN @@ -41,6 +41,10 @@ class update_upload_cacrt(Updater): ca_enabled = self.api.Command.ca_is_enabled()['result'] if ca_enabled: ca_nickname = certdb.get_ca_nickname(self.api.env.realm) + ca_subject = certstore.get_ca_subject( + self.api.Backend.ldap2, + self.api.env.container_ca, + self.api.env.basedn) else: ca_nickname = None server_certs = db.find_server_certs() @@ -54,9 +58,18 @@ class update_upload_cacrt(Updater): for nickname, trust_flags in db.list_certs(): if trust_flags.has_key: continue - if nickname == ca_nickname and ca_enabled: - trust_flags = certdb.IPA_CA_TRUST_FLAGS cert = db.get_cert_from_db(nickname, pem=False) + subject = DN( + x509.load_certificate(cert, datatype=x509.DER).subject) + if ca_enabled and subject == ca_subject: + # When ca is enabled, we can have the IPA CA cert stored + # in the nss db with a different nickname (for instance + # when the server was installed with --subject to + # customize the CA cert subject), but it must always be + # stored in LDAP with the DN cn=$DOMAIN IPA CA + # This is why we check the subject instead of the nickname here + nickname = ca_nickname + trust_flags = certdb.IPA_CA_TRUST_FLAGS trust, _ca, eku = certstore.trust_flags_to_key_policy(trust_flags) dn = DN(('cn', nickname), ('cn', 'certificates'), ('cn', 'ipa'), -- 2.13.5