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 <ftweedal@redhat.com>
This commit is contained in:
Florence Blanc-Renaud 2017-08-28 10:50:58 +02:00 committed by Stanislav Laznicka
parent 1b78f79283
commit 69bda6b440
4 changed files with 118 additions and 2 deletions

View File

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

View File

@ -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(cert):
@ -385,3 +386,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

View File

@ -0,0 +1,84 @@
# Authors:
# Florence Blanc-Renaud <flo@redhat.com>
#
# 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 <http://www.gnu.org/licenses/>.
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, []

View File

@ -45,6 +45,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()
@ -58,9 +62,17 @@ 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)
subject = cert.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'),