Imported Upstream version 4.6.2
This commit is contained in:
22
ipaserver/install/plugins/__init__.py
Normal file
22
ipaserver/install/plugins/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Authors:
|
||||
# Rob Crittenden <rcritten@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2011 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/>.
|
||||
|
||||
"""
|
||||
Provide a separate api for updates.
|
||||
"""
|
||||
384
ipaserver/install/plugins/adtrust.py
Normal file
384
ipaserver/install/plugins/adtrust.py
Normal file
@@ -0,0 +1,384 @@
|
||||
# Authors:
|
||||
# Martin Kosek <mkosek@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2012 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 ipapython.dn import DN
|
||||
from ipaserver.install import sysupgrade
|
||||
from ipaserver.install.adtrustinstance import ADTRUSTInstance
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
DEFAULT_ID_RANGE_SIZE = 200000
|
||||
|
||||
|
||||
@register()
|
||||
class update_default_range(Updater):
|
||||
"""
|
||||
Create default ID range for upgraded servers.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
dn = DN(self.api.env.container_ranges, self.api.env.basedn)
|
||||
search_filter = "objectclass=ipaDomainIDRange"
|
||||
try:
|
||||
ldap.find_entries(search_filter, [], dn)
|
||||
except errors.NotFound:
|
||||
pass
|
||||
else:
|
||||
logger.debug("default_range: ipaDomainIDRange entry found, skip "
|
||||
"plugin")
|
||||
return False, []
|
||||
|
||||
dn = DN(('cn', 'admins'), self.api.env.container_group,
|
||||
self.api.env.basedn)
|
||||
try:
|
||||
admins_entry = ldap.get_entry(dn, ['gidnumber'])
|
||||
except errors.NotFound:
|
||||
logger.error("default_range: No local ID range and no admins "
|
||||
"group found. Cannot create default ID range")
|
||||
return False, []
|
||||
|
||||
id_range_base_id = admins_entry['gidnumber'][0]
|
||||
id_range_name = '%s_id_range' % self.api.env.realm
|
||||
id_range_size = DEFAULT_ID_RANGE_SIZE
|
||||
|
||||
range_entry = [
|
||||
dict(attr='objectclass', value='top'),
|
||||
dict(attr='objectclass', value='ipaIDrange'),
|
||||
dict(attr='objectclass', value='ipaDomainIDRange'),
|
||||
dict(attr='cn', value=id_range_name),
|
||||
dict(attr='ipabaseid', value=id_range_base_id),
|
||||
dict(attr='ipaidrangesize', value=id_range_size),
|
||||
dict(attr='iparangetype', value='ipa-local'),
|
||||
]
|
||||
|
||||
dn = DN(('cn', '%s_id_range' % self.api.env.realm),
|
||||
self.api.env.container_ranges, self.api.env.basedn)
|
||||
|
||||
update = {'dn': dn, 'default': range_entry}
|
||||
|
||||
# Default range entry has a hard-coded range size to 200000 which is
|
||||
# a default range size in ipa-server-install. This could cause issues
|
||||
# if user did not use a default range, but rather defined an own,
|
||||
# bigger range (option --idmax).
|
||||
# We should make our best to check if this is the case and provide
|
||||
# user with an information how to fix it.
|
||||
dn = DN(self.api.env.container_dna_posix_ids, self.api.env.basedn)
|
||||
search_filter = "objectclass=dnaSharedConfig"
|
||||
attrs = ['dnaHostname', 'dnaRemainingValues']
|
||||
try:
|
||||
(entries, _truncated) = ldap.find_entries(search_filter, attrs, dn)
|
||||
except errors.NotFound:
|
||||
logger.warning("default_range: no dnaSharedConfig object found. "
|
||||
"Cannot check default range size.")
|
||||
else:
|
||||
masters = set()
|
||||
remaining_values_sum = 0
|
||||
for entry in entries:
|
||||
hostname = entry.get('dnahostname', [None])[0]
|
||||
if hostname is None or hostname in masters:
|
||||
continue
|
||||
remaining_values = entry.get('dnaremainingvalues', [''])[0]
|
||||
try:
|
||||
remaining_values = int(remaining_values)
|
||||
except ValueError:
|
||||
logger.warning("default_range: could not parse "
|
||||
"remaining values from '%s'",
|
||||
remaining_values)
|
||||
continue
|
||||
else:
|
||||
remaining_values_sum += remaining_values
|
||||
|
||||
masters.add(hostname)
|
||||
|
||||
if remaining_values_sum > DEFAULT_ID_RANGE_SIZE:
|
||||
msg = ['could not verify default ID range size',
|
||||
'Please use the following command to set correct ID range size',
|
||||
' $ ipa range-mod %s --range-size=RANGE_SIZE' % id_range_name,
|
||||
'RANGE_SIZE may be computed from --idstart and --idmax options '
|
||||
'used during IPA server installation:',
|
||||
' RANGE_SIZE = (--idmax) - (--idstart) + 1'
|
||||
]
|
||||
|
||||
logger.error("default_range: %s", "\n".join(msg))
|
||||
|
||||
return False, [update]
|
||||
|
||||
|
||||
@register()
|
||||
class update_default_trust_view(Updater):
|
||||
"""
|
||||
Create Default Trust View for upgraded servers.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
default_trust_view_dn = DN(('cn', 'Default Trust View'),
|
||||
self.api.env.container_views,
|
||||
self.api.env.basedn)
|
||||
|
||||
default_trust_view_entry = [
|
||||
dict(attr='objectclass', value='top'),
|
||||
dict(attr='objectclass', value='ipaIDView'),
|
||||
dict(attr='cn', value='Default Trust View'),
|
||||
dict(attr='description', value='Default Trust View for AD users. '
|
||||
'Should not be deleted.'),
|
||||
]
|
||||
|
||||
# First, see if trusts are enabled on the server
|
||||
if not self.api.Command.adtrust_is_enabled()['result']:
|
||||
logger.debug('AD Trusts are not enabled on this server')
|
||||
return False, []
|
||||
|
||||
# Second, make sure the Default Trust View does not exist yet
|
||||
try:
|
||||
ldap.get_entry(default_trust_view_dn)
|
||||
except errors.NotFound:
|
||||
pass
|
||||
else:
|
||||
logger.debug('Default Trust View already present on this server')
|
||||
return False, []
|
||||
|
||||
# We have a server with AD trust support without Default Trust View.
|
||||
# Create the Default Trust View entry.
|
||||
|
||||
update = {
|
||||
'dn': default_trust_view_dn,
|
||||
'default': default_trust_view_entry
|
||||
}
|
||||
|
||||
return False, [update]
|
||||
|
||||
|
||||
@register()
|
||||
class update_sigden_extdom_broken_config(Updater):
|
||||
"""Fix configuration of sidgen and extdom plugins
|
||||
|
||||
Upgrade to IPA 4.2+ cause that sidgen and extdom plugins have improperly
|
||||
configured basedn.
|
||||
|
||||
All trusts which have been added when config was broken must to be
|
||||
re-added manually.
|
||||
|
||||
https://fedorahosted.org/freeipa/ticket/5665
|
||||
"""
|
||||
|
||||
sidgen_config_dn = DN("cn=IPA SIDGEN,cn=plugins,cn=config")
|
||||
extdom_config_dn = DN("cn=ipa_extdom_extop,cn=plugins,cn=config")
|
||||
|
||||
def _fix_config(self):
|
||||
"""Due upgrade error configuration of sidgen and extdom plugins may
|
||||
contain literally "$SUFFIX" value instead of real DN in nsslapd-basedn
|
||||
attribute
|
||||
|
||||
:return: True if config was fixed, False if fix is not needed
|
||||
"""
|
||||
ldap = self.api.Backend.ldap2
|
||||
basedn_attr = 'nsslapd-basedn'
|
||||
modified = False
|
||||
|
||||
for dn in (self.sidgen_config_dn, self.extdom_config_dn):
|
||||
try:
|
||||
entry = ldap.get_entry(dn, attrs_list=[basedn_attr])
|
||||
except errors.NotFound:
|
||||
logger.debug("configuration for %s not found, skipping", dn)
|
||||
else:
|
||||
configured_suffix = entry.single_value.get(basedn_attr)
|
||||
if configured_suffix is None:
|
||||
raise RuntimeError(
|
||||
"Missing attribute {attr} in {dn}".format(
|
||||
attr=basedn_attr, dn=dn
|
||||
)
|
||||
)
|
||||
elif configured_suffix == "$SUFFIX":
|
||||
# configured value is wrong, fix it
|
||||
entry.single_value[basedn_attr] = str(self.api.env.basedn)
|
||||
logger.debug("updating attribute %s of %s to correct "
|
||||
"value %s",
|
||||
basedn_attr, dn, self.api.env.basedn)
|
||||
ldap.update_entry(entry)
|
||||
modified = True
|
||||
else:
|
||||
logger.debug("configured basedn for %s is okay", dn)
|
||||
|
||||
return modified
|
||||
|
||||
def execute(self, **options):
|
||||
if sysupgrade.get_upgrade_state('sidgen', 'config_basedn_updated'):
|
||||
logger.debug("Already done, skipping")
|
||||
return False, ()
|
||||
|
||||
restart = False
|
||||
if self._fix_config():
|
||||
sysupgrade.set_upgrade_state('sidgen', 'update_sids', True)
|
||||
restart = True # DS has to be restarted to apply changes
|
||||
|
||||
sysupgrade.set_upgrade_state('sidgen', 'config_basedn_updated', True)
|
||||
return restart, ()
|
||||
|
||||
|
||||
@register()
|
||||
class update_sids(Updater):
|
||||
"""SIDs may be not created properly if bug with wrong configuration for
|
||||
sidgen and extdom plugins is effective
|
||||
|
||||
This must be run after "update_sigden_extdom_broken_config"
|
||||
https://fedorahosted.org/freeipa/ticket/5665
|
||||
"""
|
||||
sidgen_config_dn = DN("cn=IPA SIDGEN,cn=plugins,cn=config")
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
if sysupgrade.get_upgrade_state('sidgen', 'update_sids') is not True:
|
||||
logger.debug("SIDs do not need to be generated")
|
||||
return False, ()
|
||||
|
||||
# check if IPA domain for AD trust has been created, and if we need to
|
||||
# regenerate missing SIDs if attribute 'ipaNTSecurityIdentifier'
|
||||
domain_IPA_AD_dn = DN(
|
||||
('cn', self.api.env.domain),
|
||||
self.api.env.container_cifsdomains,
|
||||
self.api.env.basedn)
|
||||
attr_name = 'ipaNTSecurityIdentifier'
|
||||
|
||||
try:
|
||||
entry = ldap.get_entry(domain_IPA_AD_dn, attrs_list=[attr_name])
|
||||
except errors.NotFound:
|
||||
logger.debug("IPA domain object %s is not configured",
|
||||
domain_IPA_AD_dn)
|
||||
sysupgrade.set_upgrade_state('sidgen', 'update_sids', False)
|
||||
return False, ()
|
||||
else:
|
||||
if not entry.single_value.get(attr_name):
|
||||
# we need to run sidgen task
|
||||
sidgen_task_dn = DN(
|
||||
"cn=generate domain sid,cn=ipa-sidgen-task,cn=tasks,"
|
||||
"cn=config")
|
||||
sidgen_tasks_attr = {
|
||||
"objectclass": ["top", "extensibleObject"],
|
||||
"cn": ["sidgen"],
|
||||
"delay": [0],
|
||||
"nsslapd-basedn": [self.api.env.basedn],
|
||||
}
|
||||
|
||||
task_entry = ldap.make_entry(sidgen_task_dn,
|
||||
**sidgen_tasks_attr)
|
||||
try:
|
||||
ldap.add_entry(task_entry)
|
||||
except errors.DuplicateEntry:
|
||||
logger.debug("sidgen task already created")
|
||||
else:
|
||||
logger.debug("sidgen task has been created")
|
||||
|
||||
# we have to check all trusts domains which may been affected by the
|
||||
# bug. Symptom is missing 'ipaNTSecurityIdentifier' attribute
|
||||
|
||||
base_dn = DN(self.api.env.container_adtrusts, self.api.env.basedn)
|
||||
try:
|
||||
trust_domain_entries, truncated = ldap.find_entries(
|
||||
base_dn=base_dn,
|
||||
scope=ldap.SCOPE_ONELEVEL,
|
||||
attrs_list=["cn"],
|
||||
# more types of trusts can be stored under cn=trusts, we need
|
||||
# the type with ipaNTTrustPartner attribute
|
||||
filter="(&(ipaNTTrustPartner=*)(!(%s=*)))" % attr_name
|
||||
)
|
||||
except errors.NotFound:
|
||||
pass
|
||||
else:
|
||||
if truncated:
|
||||
logger.warning("update_sids: Search results were truncated")
|
||||
|
||||
for entry in trust_domain_entries:
|
||||
domain = entry.single_value["cn"]
|
||||
logger.error(
|
||||
"Your trust to %s is broken. Please re-create it by "
|
||||
"running 'ipa trust-add' again.", domain)
|
||||
|
||||
sysupgrade.set_upgrade_state('sidgen', 'update_sids', False)
|
||||
return False, ()
|
||||
|
||||
|
||||
@register()
|
||||
class update_tdo_gidnumber(Updater):
|
||||
"""
|
||||
Create a gidNumber attribute for Trusted Domain Objects.
|
||||
|
||||
The value is taken from the fallback group defined in cn=Default SMB Group.
|
||||
"""
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
# First, see if trusts are enabled on the server
|
||||
if not self.api.Command.adtrust_is_enabled()['result']:
|
||||
logger.debug('AD Trusts are not enabled on this server')
|
||||
return False, []
|
||||
|
||||
# Read the gidnumber of the fallback group
|
||||
dn = DN(('cn', ADTRUSTInstance.FALLBACK_GROUP_NAME),
|
||||
self.api.env.container_group,
|
||||
self.api.env.basedn)
|
||||
|
||||
try:
|
||||
entry = ldap.get_entry(dn, ['gidnumber'])
|
||||
gidNumber = entry.get('gidnumber')
|
||||
except errors.NotFound:
|
||||
logger.error("%s not found",
|
||||
ADTRUSTInstance.FALLBACK_GROUP_NAME)
|
||||
return False, ()
|
||||
|
||||
if not gidNumber:
|
||||
logger.error("%s does not have a gidnumber",
|
||||
ADTRUSTInstance.FALLBACK_GROUP_NAME)
|
||||
return False, ()
|
||||
|
||||
# For each trusted domain object, add gidNumber
|
||||
try:
|
||||
tdos = ldap.get_entries(
|
||||
DN(self.api.env.container_adtrusts, self.api.env.basedn),
|
||||
scope=ldap.SCOPE_ONELEVEL,
|
||||
filter="(objectclass=ipaNTTrustedDomain)",
|
||||
attrs_list=['gidnumber'])
|
||||
for tdo in tdos:
|
||||
# if the trusted domain object does not contain gidnumber,
|
||||
# add the default fallback group gidnumber
|
||||
if not tdo.get('gidnumber'):
|
||||
try:
|
||||
tdo['gidnumber'] = gidNumber
|
||||
ldap.update_entry(tdo)
|
||||
logger.debug("Added gidnumber %s to %s",
|
||||
gidNumber, tdo.dn)
|
||||
except Exception:
|
||||
logger.warning(
|
||||
"Failed to add gidnumber to %s", tdo.dn)
|
||||
|
||||
except errors.NotFound:
|
||||
logger.debug("No trusted domain object to update")
|
||||
return False, ()
|
||||
|
||||
return False, ()
|
||||
130
ipaserver/install/plugins/ca_renewal_master.py
Normal file
130
ipaserver/install/plugins/ca_renewal_master.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# Authors:
|
||||
# Jan Cholasta <jcholast@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 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 ipaserver.install import installutils, cainstance
|
||||
from ipalib import errors
|
||||
from ipalib import Updater
|
||||
from ipalib.install import certmonger
|
||||
from ipalib.plugable import Registry
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
@register()
|
||||
class update_ca_renewal_master(Updater):
|
||||
"""
|
||||
Set CA renewal master in LDAP.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ca = cainstance.CAInstance(self.api.env.realm)
|
||||
if not ca.is_configured():
|
||||
logger.debug("CA is not configured on this host")
|
||||
return False, []
|
||||
|
||||
ldap = self.api.Backend.ldap2
|
||||
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
|
||||
self.api.env.basedn)
|
||||
dn = DN(('cn', 'CA'), ('cn', self.api.env.host), base_dn)
|
||||
filter = '(&(cn=CA)(ipaConfigString=caRenewalMaster))'
|
||||
try:
|
||||
entries = ldap.get_entries(base_dn=base_dn, filter=filter,
|
||||
attrs_list=[])
|
||||
except errors.NotFound:
|
||||
pass
|
||||
else:
|
||||
logger.debug("found CA renewal master %s", entries[0].dn[1].value)
|
||||
|
||||
master = False
|
||||
updates = []
|
||||
|
||||
for entry in entries:
|
||||
if entry.dn == dn:
|
||||
master = True
|
||||
continue
|
||||
|
||||
updates.append({
|
||||
'dn': entry.dn,
|
||||
'updates': [
|
||||
dict(action='remove', attr='ipaConfigString',
|
||||
value='caRenewalMaster')
|
||||
],
|
||||
})
|
||||
|
||||
if master:
|
||||
return False, updates
|
||||
else:
|
||||
return False, []
|
||||
|
||||
criteria = {
|
||||
'cert-file': paths.RA_AGENT_PEM,
|
||||
}
|
||||
request_id = certmonger.get_request_id(criteria)
|
||||
if request_id is not None:
|
||||
logger.debug("found certmonger request for RA cert")
|
||||
|
||||
ca_name = certmonger.get_request_value(request_id, 'ca-name')
|
||||
if ca_name is None:
|
||||
logger.warning(
|
||||
"certmonger request for RA cert is missing ca_name, "
|
||||
"assuming local CA is renewal slave")
|
||||
return False, []
|
||||
ca_name = ca_name.strip()
|
||||
|
||||
if ca_name == 'dogtag-ipa-renew-agent':
|
||||
pass
|
||||
elif ca_name == 'dogtag-ipa-retrieve-agent-submit':
|
||||
return False, []
|
||||
elif ca_name == 'dogtag-ipa-ca-renew-agent':
|
||||
return False, []
|
||||
else:
|
||||
logger.warning(
|
||||
"certmonger request for RA cert has unknown ca_name '%s', "
|
||||
"assuming local CA is renewal slave", ca_name)
|
||||
return False, []
|
||||
else:
|
||||
logger.debug("certmonger request for RA cert not found")
|
||||
|
||||
config = installutils.get_directive(
|
||||
paths.CA_CS_CFG_PATH, 'subsystem.select', '=')
|
||||
|
||||
if config == 'New':
|
||||
pass
|
||||
elif config == 'Clone':
|
||||
return False, []
|
||||
else:
|
||||
logger.warning(
|
||||
"CS.cfg has unknown subsystem.select value '%s', "
|
||||
"assuming local CA is renewal slave", config)
|
||||
return (False, False, [])
|
||||
|
||||
update = {
|
||||
'dn': dn,
|
||||
'updates': [
|
||||
dict(action='add', attr='ipaConfigString',
|
||||
value='caRenewalMaster')
|
||||
],
|
||||
}
|
||||
|
||||
return False, [update]
|
||||
553
ipaserver/install/plugins/dns.py
Normal file
553
ipaserver/install/plugins/dns.py
Normal file
@@ -0,0 +1,553 @@
|
||||
# Authors:
|
||||
# Martin Kosek <mkosek@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2012 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/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
import dns.exception
|
||||
import re
|
||||
import traceback
|
||||
import time
|
||||
|
||||
from ldif import LDIFWriter
|
||||
|
||||
from ipalib import Registry, errors, util
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
from ipapython import dnsutil
|
||||
from ipaserver.install import sysupgrade
|
||||
from ipaserver.install.bindinstance import ensure_dnsserver_container_exists
|
||||
from ipaserver.plugins.dns import dns_container_exists
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
class DNSUpdater(Updater):
|
||||
backup_dir = u'/var/lib/ipa/backup/'
|
||||
# override backup_filename in subclass, it will be mangled by strftime
|
||||
backup_filename = None
|
||||
|
||||
def __init__(self, api):
|
||||
super(DNSUpdater, self).__init__(api)
|
||||
backup_path = u'%s%s' % (self.backup_dir, self.backup_filename)
|
||||
self.backup_path = time.strftime(backup_path)
|
||||
self._ldif_writer = None
|
||||
self._saved_privileges = set() # store privileges only once
|
||||
self.saved_zone_to_privilege = {}
|
||||
|
||||
def version_update_needed(self, target_version):
|
||||
"""Test if IPA DNS version is smaller than target version."""
|
||||
assert isinstance(target_version, int)
|
||||
|
||||
try:
|
||||
return int(self.api.Command['dnsconfig_show'](
|
||||
all=True)['result']['ipadnsversion'][0]) < target_version
|
||||
except errors.NotFound:
|
||||
# IPA DNS is not configured
|
||||
return False
|
||||
|
||||
@property
|
||||
def ldif_writer(self):
|
||||
if not self._ldif_writer:
|
||||
logger.info('Original zones will be saved in LDIF format in '
|
||||
'%s file', self.backup_path)
|
||||
self._ldif_writer = LDIFWriter(open(self.backup_path, 'w'))
|
||||
return self._ldif_writer
|
||||
|
||||
def backup_zone(self, zone):
|
||||
"""Backup zone object, its records, permissions, and privileges.
|
||||
|
||||
Mapping from zone to privilege (containing zone's permissions)
|
||||
will be stored in saved_zone_to_privilege dict for further usage.
|
||||
"""
|
||||
dn = str(zone['dn'])
|
||||
del zone['dn'] # dn shouldn't be as attribute in ldif
|
||||
self.ldif_writer.unparse(dn, zone)
|
||||
|
||||
ldap = self.api.Backend.ldap2
|
||||
if 'managedBy' in zone:
|
||||
permission = ldap.get_entry(DN(zone['managedBy'][0]))
|
||||
self.ldif_writer.unparse(str(permission.dn), dict(permission.raw))
|
||||
for privilege_dn in permission.get('member', []):
|
||||
# privileges can be shared by multiples zones
|
||||
if privilege_dn not in self._saved_privileges:
|
||||
self._saved_privileges.add(privilege_dn)
|
||||
privilege = ldap.get_entry(privilege_dn)
|
||||
self.ldif_writer.unparse(str(privilege.dn),
|
||||
dict(privilege.raw))
|
||||
|
||||
# remember privileges referened by permission
|
||||
if 'member' in permission:
|
||||
self.saved_zone_to_privilege[
|
||||
zone['idnsname'][0]
|
||||
] = permission['member']
|
||||
|
||||
if 'idnszone' in zone['objectClass']:
|
||||
# raw values are required to store into ldif
|
||||
records = self.api.Command['dnsrecord_find'](zone['idnsname'][0],
|
||||
all=True,
|
||||
raw=True,
|
||||
sizelimit=0)['result']
|
||||
for record in records:
|
||||
if record['idnsname'][0] == u'@':
|
||||
# zone record was saved before
|
||||
continue
|
||||
dn = str(record['dn'])
|
||||
del record['dn']
|
||||
self.ldif_writer.unparse(dn, record)
|
||||
|
||||
|
||||
@register()
|
||||
class update_ipaconfigstring_dnsversion_to_ipadnsversion(Updater):
|
||||
"""
|
||||
IPA <= 4.3.1 used ipaConfigString "DNSVersion 1" on DNS container.
|
||||
This was hard to deal with in API so from IPA 4.3.2 we are using
|
||||
new ipaDNSVersion attribute with integer syntax.
|
||||
Old ipaConfigString is left there for now so if someone accidentally
|
||||
executes upgrade on an old replica again it will not re-upgrade the data.
|
||||
"""
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
dns_container_dn = DN(self.api.env.container_dns, self.api.env.basedn)
|
||||
try:
|
||||
container_entry = ldap.get_entry(dns_container_dn)
|
||||
except errors.NotFound:
|
||||
# DNS container not found, nothing to upgrade
|
||||
return False, []
|
||||
|
||||
if 'ipadnscontainer' in [
|
||||
o.lower() for o in container_entry['objectclass']
|
||||
]:
|
||||
# version data are already migrated
|
||||
return False, []
|
||||
|
||||
logger.debug('Migrating DNS ipaConfigString to ipaDNSVersion')
|
||||
container_entry['objectclass'].append('ipadnscontainer')
|
||||
version = 0
|
||||
for config_option in container_entry.get("ipaConfigString", []):
|
||||
matched = re.match("^DNSVersion\s+(?P<version>\d+)$",
|
||||
config_option, flags=re.I)
|
||||
if matched:
|
||||
version = int(matched.group("version"))
|
||||
else:
|
||||
logger.error(
|
||||
'Failed to parse DNS version from ipaConfigString, '
|
||||
'defaulting to version %s', version)
|
||||
container_entry['ipadnsversion'] = version
|
||||
ldap.update_entry(container_entry)
|
||||
logger.debug('ipaDNSVersion = %s', version)
|
||||
return False, []
|
||||
|
||||
|
||||
@register()
|
||||
class update_dnszones(Updater):
|
||||
"""
|
||||
Update all zones to meet requirements in the new FreeIPA versions
|
||||
|
||||
1) AllowQuery and AllowTransfer
|
||||
Set AllowQuery and AllowTransfer ACLs in all zones that may be configured
|
||||
in an upgraded FreeIPA instance.
|
||||
|
||||
Upgrading to new version of bind-dyndb-ldap and having these ACLs empty
|
||||
would result in a leak of potentially sensitive DNS information as
|
||||
zone transfers are enabled for all hosts if not disabled in named.conf
|
||||
or LDAP.
|
||||
|
||||
This plugin disables the zone transfer by default so that it needs to be
|
||||
explicitly enabled by FreeIPA Administrator.
|
||||
|
||||
2) Update policy
|
||||
SSH public key support includes a feature to automatically add/update
|
||||
client SSH fingerprints in SSHFP records. However, the update won't
|
||||
work for zones created before this support was added as they don't allow
|
||||
clients to update SSHFP records in their update policies.
|
||||
|
||||
This module extends the original policy to allow the SSHFP updates.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
if not dns_container_exists(ldap):
|
||||
return False, []
|
||||
|
||||
try:
|
||||
zones = self.api.Command.dnszone_find(all=True)['result']
|
||||
except errors.NotFound:
|
||||
logger.debug('No DNS zone to update found')
|
||||
return False, []
|
||||
|
||||
for zone in zones:
|
||||
update = {}
|
||||
if not zone.get('idnsallowquery'):
|
||||
# allow query from any client by default
|
||||
update['idnsallowquery'] = u'any;'
|
||||
|
||||
if not zone.get('idnsallowtransfer'):
|
||||
# do not open zone transfers by default
|
||||
update['idnsallowtransfer'] = u'none;'
|
||||
|
||||
old_policy = util.get_dns_forward_zone_update_policy(
|
||||
self.api.env.realm, ('A', 'AAAA'))
|
||||
if zone.get('idnsupdatepolicy', [''])[0] == old_policy:
|
||||
update['idnsupdatepolicy'] = util.get_dns_forward_zone_update_policy(\
|
||||
self.api.env.realm)
|
||||
|
||||
if update:
|
||||
# FIXME: https://fedorahosted.org/freeipa/ticket/4722
|
||||
self.api.Command.dnszone_mod(zone[u'idnsname'][0].make_absolute(),
|
||||
**update)
|
||||
|
||||
return False, []
|
||||
|
||||
|
||||
@register()
|
||||
class update_dns_limits(Updater):
|
||||
"""
|
||||
bind-dyndb-ldap persistent search queries LDAP for all DNS records.
|
||||
The LDAP connection must have no size or time limits to work
|
||||
properly. This plugin updates limits of the existing DNS service
|
||||
principal to match there requirements.
|
||||
"""
|
||||
limit_attributes = ['nsTimeLimit', 'nsSizeLimit', 'nsIdleTimeout', 'nsLookThroughLimit']
|
||||
limit_value = '-1'
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
if not dns_container_exists(ldap):
|
||||
return False, []
|
||||
|
||||
dns_principal = 'DNS/%s@%s' % (self.env.host, self.env.realm)
|
||||
dns_service_dn = DN(('krbprincipalname', dns_principal),
|
||||
self.env.container_service,
|
||||
self.env.basedn)
|
||||
|
||||
try:
|
||||
entry = ldap.get_entry(dns_service_dn, self.limit_attributes)
|
||||
except errors.NotFound:
|
||||
# this host may not have DNS service set
|
||||
logger.debug("DNS: service %s not found, no need to update limits",
|
||||
dns_service_dn)
|
||||
return False, []
|
||||
|
||||
if all(entry.get(limit.lower(), [None])[0] == self.limit_value for limit in self.limit_attributes):
|
||||
logger.debug("DNS: limits for service %s already set",
|
||||
dns_service_dn)
|
||||
# service is already updated
|
||||
return False, []
|
||||
|
||||
limit_updates = []
|
||||
|
||||
for limit in self.limit_attributes:
|
||||
limit_updates.append(dict(action='only', attr=limit,
|
||||
value=self.limit_value))
|
||||
|
||||
dnsupdate = {'dn': dns_service_dn, 'updates': limit_updates}
|
||||
logger.debug("DNS: limits for service %s will be updated",
|
||||
dns_service_dn)
|
||||
|
||||
|
||||
return False, [dnsupdate]
|
||||
|
||||
|
||||
@register()
|
||||
class update_master_to_dnsforwardzones(DNSUpdater):
|
||||
"""
|
||||
Update all zones to meet requirements in the new FreeIPA versions
|
||||
|
||||
All masters zones with specified forwarders, and forward-policy different
|
||||
than none, will be tranformed to forward zones.
|
||||
Original masters zone will be backed up to ldif file.
|
||||
|
||||
This should be applied only once,
|
||||
and only if original version was lower than 4.0
|
||||
"""
|
||||
backup_filename = u'dns-master-to-forward-zones-%Y-%m-%d-%H-%M-%S.ldif'
|
||||
|
||||
def execute(self, **options):
|
||||
# check LDAP if forwardzones already uses new semantics
|
||||
if not self.version_update_needed(target_version=1):
|
||||
# forwardzones already uses new semantics,
|
||||
# no upgrade is required
|
||||
return False, []
|
||||
|
||||
logger.debug('Updating forward zones')
|
||||
# update the DNSVersion, following upgrade can be executed only once
|
||||
self.api.Command['dnsconfig_mod'](ipadnsversion=1)
|
||||
|
||||
# Updater in IPA version from 4.0 to 4.1.2 doesn't work well, this
|
||||
# should detect if update in past has been executed, and set proper
|
||||
# DNSVersion into LDAP
|
||||
try:
|
||||
fwzones = self.api.Command.dnsforwardzone_find()['result']
|
||||
except errors.NotFound:
|
||||
# No forwardzones found, update probably has not been executed yet
|
||||
pass
|
||||
else:
|
||||
if fwzones:
|
||||
# fwzones exist, do not execute upgrade again
|
||||
return False, []
|
||||
|
||||
zones = []
|
||||
try:
|
||||
# raw values are required to store into ldif
|
||||
zones = self.api.Command.dnszone_find(all=True,
|
||||
raw=True,
|
||||
sizelimit=0)['result']
|
||||
except errors.NotFound:
|
||||
pass
|
||||
|
||||
if not zones:
|
||||
logger.debug('No DNS zone to update found')
|
||||
return False, []
|
||||
|
||||
zones_to_transform = []
|
||||
|
||||
for zone in zones:
|
||||
if (
|
||||
zone.get('idnsforwardpolicy', [u'first'])[0] == u'none' or
|
||||
zone.get('idnsforwarders', []) == []
|
||||
):
|
||||
continue # don't update zone
|
||||
|
||||
zones_to_transform.append(zone)
|
||||
|
||||
if zones_to_transform:
|
||||
logger.info('Zones with specified forwarders with policy '
|
||||
'different than none will be transformed to forward '
|
||||
'zones.')
|
||||
# update
|
||||
for zone in zones_to_transform:
|
||||
try:
|
||||
self.backup_zone(zone)
|
||||
except Exception:
|
||||
logger.error('Unable to create backup for zone, '
|
||||
'terminating zone upgrade')
|
||||
logger.error("%s", traceback.format_exc())
|
||||
return False, []
|
||||
|
||||
# delete master zone
|
||||
try:
|
||||
self.api.Command['dnszone_del'](zone['idnsname'])
|
||||
except Exception as e:
|
||||
logger.error('Transform to forwardzone terminated: '
|
||||
'removing zone %s failed (%s)',
|
||||
zone['idnsname'][0], e)
|
||||
logger.error("%s", traceback.format_exc())
|
||||
continue
|
||||
|
||||
# create forward zone
|
||||
try:
|
||||
kw = {
|
||||
'idnsforwarders': zone.get('idnsforwarders', []),
|
||||
'idnsforwardpolicy': zone.get('idnsforwardpolicy',
|
||||
[u'first'])[0],
|
||||
'skip_overlap_check': True,
|
||||
}
|
||||
self.api.Command['dnsforwardzone_add'](zone['idnsname'][0], **kw)
|
||||
except Exception:
|
||||
logger.error('Transform to forwardzone terminated: '
|
||||
'creating forwardzone %s failed',
|
||||
zone['idnsname'][0])
|
||||
logger.error("%s", traceback.format_exc())
|
||||
continue
|
||||
|
||||
# create permission if original zone has one
|
||||
if 'managedBy' in zone:
|
||||
try:
|
||||
perm_name = self.api.Command['dnsforwardzone_add_permission'](
|
||||
zone['idnsname'][0])['value']
|
||||
except Exception:
|
||||
logger.error('Transform to forwardzone terminated: '
|
||||
'Adding managed by permission to forward '
|
||||
'zone %s failed', zone['idnsname'])
|
||||
logger.error("%s", traceback.format_exc())
|
||||
logger.info('Zone %s was transformed to forward zone '
|
||||
' without managed permissions',
|
||||
zone['idnsname'][0])
|
||||
continue
|
||||
|
||||
else:
|
||||
if zone['idnsname'][0] in self.saved_zone_to_privilege:
|
||||
privileges = [
|
||||
dn[0].value for dn in self.saved_zone_to_privilege[zone['idnsname'][0]]
|
||||
]
|
||||
try:
|
||||
self.api.Command['permission_add_member'](perm_name,
|
||||
privilege=privileges)
|
||||
except Exception:
|
||||
logger.error('Unable to restore privileges '
|
||||
'for permission %s, for zone %s',
|
||||
perm_name, zone['idnsname'])
|
||||
logger.error("%s", traceback.format_exc())
|
||||
logger.info('Zone %s was transformed to '
|
||||
'forward zone without restored '
|
||||
'privileges',
|
||||
zone['idnsname'][0])
|
||||
continue
|
||||
|
||||
logger.debug('Zone %s was sucessfully transformed to forward '
|
||||
'zone',
|
||||
zone['idnsname'][0])
|
||||
|
||||
return False, []
|
||||
|
||||
|
||||
@register()
|
||||
class update_dnsforward_emptyzones(DNSUpdater):
|
||||
"""
|
||||
Migrate forward policies which conflict with automatic empty zones
|
||||
(RFC 6303) to use forward policy = only.
|
||||
|
||||
BIND ignores conflicting forwarding configuration
|
||||
when forwarding policy != only.
|
||||
bind-dyndb-ldap 9.0+ will do the same so we have to adjust FreeIPA zones
|
||||
accordingly.
|
||||
"""
|
||||
backup_filename = u'dns-forwarding-empty-zones-%Y-%m-%d-%H-%M-%S.ldif'
|
||||
|
||||
def update_zones(self):
|
||||
try:
|
||||
fwzones = self.api.Command.dnsforwardzone_find(all=True,
|
||||
raw=True)['result']
|
||||
except errors.NotFound:
|
||||
# No forwardzones found, we are done
|
||||
return
|
||||
|
||||
logged_once = False
|
||||
for zone in fwzones:
|
||||
if not (
|
||||
dnsutil.related_to_auto_empty_zone(
|
||||
dnsutil.DNSName(zone.get('idnsname')[0]))
|
||||
and zone.get('idnsforwardpolicy', [u'first'])[0] != u'only'
|
||||
and zone.get('idnsforwarders', []) != []
|
||||
):
|
||||
# this zone does not conflict with automatic empty zone
|
||||
continue
|
||||
|
||||
if not logged_once:
|
||||
logger.info('Forward policy for zones conflicting with '
|
||||
'automatic empty zones will be changed to "only"')
|
||||
logged_once = True
|
||||
|
||||
# backup
|
||||
try:
|
||||
self.backup_zone(zone)
|
||||
except Exception:
|
||||
logger.error('Unable to create backup for zone %s, '
|
||||
'terminating zone upgrade',
|
||||
zone['idnsname'][0])
|
||||
logger.error("%s", traceback.format_exc())
|
||||
continue
|
||||
|
||||
# change forward policy
|
||||
try:
|
||||
self.api.Command['dnsforwardzone_mod'](
|
||||
zone['idnsname'][0],
|
||||
idnsforwardpolicy=u'only'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error('Forward policy update for zone %s failed '
|
||||
'(%s)', zone['idnsname'][0], e)
|
||||
logger.error("%s", traceback.format_exc())
|
||||
continue
|
||||
|
||||
logger.debug('Zone %s was sucessfully modified to use forward '
|
||||
'policy "only"', zone['idnsname'][0])
|
||||
|
||||
def update_global_ldap_forwarder(self):
|
||||
config = self.api.Command['dnsconfig_show'](all=True,
|
||||
raw=True)['result']
|
||||
if (
|
||||
config.get('idnsforwardpolicy', [u'first'])[0] == u'first'
|
||||
and config.get('idnsforwarders', [])
|
||||
):
|
||||
logger.info('Global forward policy in LDAP for all servers will '
|
||||
'be changed to "only" to avoid conflicts with '
|
||||
'automatic empty zones')
|
||||
self.backup_zone(config)
|
||||
self.api.Command['dnsconfig_mod'](idnsforwardpolicy=u'only')
|
||||
|
||||
def execute(self, **options):
|
||||
# check LDAP if DNS subtree already uses new semantics
|
||||
if not self.version_update_needed(target_version=2):
|
||||
# forwardzones already use new semantics, no upgrade is required
|
||||
return False, []
|
||||
|
||||
logger.debug('Updating forwarding policies in LDAP '
|
||||
'to avoid conflicts with automatic empty zones')
|
||||
# update the DNSVersion, following upgrade can be executed only once
|
||||
self.api.Command['dnsconfig_mod'](ipadnsversion=2)
|
||||
|
||||
self.update_zones()
|
||||
try:
|
||||
if dnsutil.has_empty_zone_addresses(self.api.env.host):
|
||||
self.update_global_ldap_forwarder()
|
||||
except dns.exception.DNSException as ex:
|
||||
logger.error('Skipping update of global DNS forwarder in LDAP: '
|
||||
'Unable to determine if local server is using an '
|
||||
'IP address belonging to an automatic empty zone. '
|
||||
'Consider changing forwarding policy to "only". '
|
||||
'DNS exception: %s', ex)
|
||||
|
||||
return False, []
|
||||
|
||||
|
||||
@register()
|
||||
class update_dnsserver_configuration_into_ldap(DNSUpdater):
|
||||
"""
|
||||
DNS Locations feature requires to have DNS configuration stored in LDAP DB.
|
||||
Create DNS server configuration in LDAP for each old server
|
||||
"""
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
if sysupgrade.get_upgrade_state('dns', 'server_config_to_ldap'):
|
||||
logger.debug('upgrade is not needed')
|
||||
return False, []
|
||||
|
||||
dns_container_dn = DN(self.api.env.container_dns, self.api.env.basedn)
|
||||
try:
|
||||
ldap.get_entry(dns_container_dn)
|
||||
except errors.NotFound:
|
||||
logger.debug('DNS container not found, nothing to upgrade')
|
||||
sysupgrade.set_upgrade_state('dns', 'server_config_to_ldap', True)
|
||||
return False, []
|
||||
|
||||
result = self.api.Command.server_show(self.api.env.host)['result']
|
||||
if not 'DNS server' in result.get('enabled_role_servrole', []):
|
||||
logger.debug('This server is not DNS server, nothing to upgrade')
|
||||
sysupgrade.set_upgrade_state('dns', 'server_config_to_ldap', True)
|
||||
return False, []
|
||||
|
||||
# create container first, if doesn't exist
|
||||
ensure_dnsserver_container_exists(ldap, self.api)
|
||||
|
||||
try:
|
||||
self.api.Command.dnsserver_add(self.api.env.host)
|
||||
except errors.DuplicateEntry:
|
||||
logger.debug("DNS server configuration already exists "
|
||||
"in LDAP database")
|
||||
else:
|
||||
logger.debug("DNS server configuration has been sucessfully "
|
||||
"created in LDAP database")
|
||||
sysupgrade.set_upgrade_state('dns', 'server_config_to_ldap', True)
|
||||
return False, []
|
||||
118
ipaserver/install/plugins/fix_replica_agreements.py
Normal file
118
ipaserver/install/plugins/fix_replica_agreements.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Authors:
|
||||
# Rob Crittenden <rcritten@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2012 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 ipaserver.install import replication
|
||||
from ipalib import Registry
|
||||
from ipalib import Updater
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
EXCLUDE_TEMPLATE = '(objectclass=*) $ EXCLUDE %s'
|
||||
|
||||
|
||||
@register()
|
||||
class update_replica_attribute_lists(Updater):
|
||||
"""
|
||||
Run through all replication agreements and ensure that EXCLUDE list
|
||||
has all the required attributes so that we don't cause replication
|
||||
storms.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
# We need an LDAPClient connection to the backend
|
||||
logger.debug("Start replication agreement exclude list update task")
|
||||
conn = self.api.Backend.ldap2
|
||||
|
||||
repl = replication.ReplicationManager(self.api.env.realm,
|
||||
self.api.env.host,
|
||||
None, conn=conn)
|
||||
|
||||
# We need to update only IPA replica agreements, not winsync
|
||||
ipa_replicas = repl.find_ipa_replication_agreements()
|
||||
|
||||
logger.debug("Found %d agreement(s)", len(ipa_replicas))
|
||||
|
||||
for replica in ipa_replicas:
|
||||
for desc in replica.get('description', []):
|
||||
logger.debug('%s', desc)
|
||||
|
||||
self._update_attr(repl, replica,
|
||||
'nsDS5ReplicatedAttributeList',
|
||||
replication.EXCLUDES, template=EXCLUDE_TEMPLATE)
|
||||
self._update_attr(repl, replica,
|
||||
'nsDS5ReplicatedAttributeListTotal',
|
||||
replication.TOTAL_EXCLUDES, template=EXCLUDE_TEMPLATE)
|
||||
self._update_attr(repl, replica,
|
||||
'nsds5ReplicaStripAttrs', replication.STRIP_ATTRS)
|
||||
|
||||
logger.debug("Done updating agreements")
|
||||
|
||||
return False, [] # No restart, no updates
|
||||
|
||||
def _update_attr(self, repl, replica, attribute, values, template='%s'):
|
||||
"""Add or update an attribute of a replication agreement
|
||||
|
||||
If the attribute doesn't already exist, it is added and set to
|
||||
`template` with %s substituted by a space-separated `values`.
|
||||
If the attribute does exist, `values` missing from it are just
|
||||
appended to the end, also space-separated.
|
||||
|
||||
:param repl: Replication manager
|
||||
:param replica: Replica agreement
|
||||
:param attribute: Attribute to add or update
|
||||
:param values: List of values the attribute should hold
|
||||
:param template: Template to use when adding attribute
|
||||
"""
|
||||
attrlist = replica.single_value.get(attribute)
|
||||
if attrlist is None:
|
||||
logger.debug("Adding %s", attribute)
|
||||
|
||||
# Need to add it altogether
|
||||
replica[attribute] = [template % " ".join(values)]
|
||||
|
||||
try:
|
||||
repl.conn.update_entry(replica)
|
||||
logger.debug("Updated")
|
||||
except Exception as e:
|
||||
logger.error("Error caught updating replica: %s", str(e))
|
||||
|
||||
else:
|
||||
attrlist_normalized = attrlist.lower().split()
|
||||
missing = [a for a in values
|
||||
if a.lower() not in attrlist_normalized]
|
||||
|
||||
if missing:
|
||||
logger.debug("%s needs updating (missing: %s)", attribute,
|
||||
', '.join(missing))
|
||||
|
||||
replica[attribute] = [
|
||||
'%s %s' % (attrlist, ' '.join(missing))]
|
||||
|
||||
try:
|
||||
repl.conn.update_entry(replica)
|
||||
logger.debug("Updated %s", attribute)
|
||||
except Exception as e:
|
||||
logger.error("Error caught updating %s: %s",
|
||||
attribute, str(e))
|
||||
else:
|
||||
logger.debug("%s: No update necessary", attribute)
|
||||
182
ipaserver/install/plugins/rename_managed.py
Normal file
182
ipaserver/install/plugins/rename_managed.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# Authors:
|
||||
# Rob Crittenden <rcritten@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2011 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
|
||||
|
||||
import six
|
||||
|
||||
from ipalib import Registry, errors
|
||||
from ipalib import Updater
|
||||
from ipapython import ipautil
|
||||
from ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
|
||||
def entry_to_update(entry):
|
||||
"""
|
||||
Convert an entry into a name/value pair list that looks like an update.
|
||||
|
||||
An entry is a dict.
|
||||
|
||||
An update is a list of name/value pairs.
|
||||
"""
|
||||
update = []
|
||||
for attr in entry.keys():
|
||||
if isinstance(entry[attr], list):
|
||||
for item in entry[attr]:
|
||||
update.append(dict(attr=str(attr), value=str(item)))
|
||||
else:
|
||||
update.append(dict(attr=str(attr), value=str(entry[attr])))
|
||||
|
||||
return update
|
||||
|
||||
|
||||
class GenerateUpdateMixin(object):
|
||||
def _dn_suffix_replace(self, dn, old_suffix, new_suffix):
|
||||
"""Replace all occurences of "old" AVAs in a DN by "new"
|
||||
|
||||
If the input DN doesn't end with old_suffix, log, an raise ValueError.
|
||||
"""
|
||||
if not dn.endswith(old_suffix):
|
||||
logger.error("unable to replace suffix '%s' with '%s' in '%s'",
|
||||
old_suffix, new_suffix, dn)
|
||||
raise ValueError('no replacement made')
|
||||
return DN(*dn[:-len(old_suffix)]) + new_suffix
|
||||
|
||||
def generate_update(self, deletes=False):
|
||||
"""
|
||||
We need to separate the deletes that need to happen from the
|
||||
new entries that need to be added.
|
||||
"""
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
suffix = ipautil.realm_to_suffix(self.api.env.realm)
|
||||
searchfilter = '(objectclass=*)'
|
||||
definitions_managed_entries = []
|
||||
|
||||
old_template_container = DN(('cn', 'etc'), suffix)
|
||||
new_template_container = DN(('cn', 'Templates'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix)
|
||||
|
||||
old_definition_container = DN(('cn', 'managed entries'), ('cn', 'plugins'), ('cn', 'config'), suffix)
|
||||
new_definition_container = DN(('cn', 'Definitions'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix)
|
||||
|
||||
update_list = []
|
||||
restart = False
|
||||
|
||||
# If the old entries don't exist the server has already been updated.
|
||||
try:
|
||||
definitions_managed_entries, _truncated = ldap.find_entries(
|
||||
searchfilter, ['*'], old_definition_container,
|
||||
ldap.SCOPE_ONELEVEL)
|
||||
except errors.NotFound:
|
||||
return (False, update_list)
|
||||
|
||||
for entry in definitions_managed_entries:
|
||||
assert isinstance(entry.dn, DN)
|
||||
if deletes:
|
||||
old_dn = entry['managedtemplate'][0]
|
||||
assert isinstance(old_dn, DN)
|
||||
try:
|
||||
entry = ldap.get_entry(old_dn, ['*'])
|
||||
except errors.NotFound:
|
||||
pass
|
||||
else:
|
||||
# Compute the new dn by replacing the old container with the new container
|
||||
try:
|
||||
new_dn = self._dn_suffix_replace(
|
||||
entry.dn,
|
||||
old_suffix=old_template_container,
|
||||
new_suffix=new_template_container)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# The old attributes become defaults for the new entry
|
||||
new_update = {'dn': new_dn,
|
||||
'default': entry_to_update(entry)}
|
||||
|
||||
# Delete the old entry
|
||||
old_update = {'dn': entry.dn, 'deleteentry': None}
|
||||
|
||||
# Add the delete and replacement updates to the list of all updates
|
||||
update_list.append(old_update)
|
||||
update_list.append(new_update)
|
||||
|
||||
else:
|
||||
# Update the template dn by replacing the old containter with the new container
|
||||
try:
|
||||
new_dn = self._dn_suffix_replace(
|
||||
entry['managedtemplate'][0],
|
||||
old_suffix=old_template_container,
|
||||
new_suffix=new_template_container)
|
||||
except ValueError:
|
||||
continue
|
||||
entry['managedtemplate'] = new_dn
|
||||
|
||||
# Update the entry dn similarly
|
||||
try:
|
||||
new_dn = self._dn_suffix_replace(
|
||||
entry.dn,
|
||||
old_suffix=old_definition_container,
|
||||
new_suffix=new_definition_container)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# The old attributes become defaults for the new entry
|
||||
new_update = {'dn': new_dn,
|
||||
'default': entry_to_update(entry)}
|
||||
|
||||
# Add the replacement update to the collection of all updates
|
||||
update_list.append(new_update)
|
||||
|
||||
if len(update_list) > 0:
|
||||
restart = True
|
||||
update_list.sort(reverse=True, key=lambda x: x['dn'])
|
||||
|
||||
return (restart, update_list)
|
||||
|
||||
|
||||
@register()
|
||||
class update_managed_post_first(Updater, GenerateUpdateMixin):
|
||||
"""
|
||||
Update managed entries
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
# Never need to restart with the pre-update changes
|
||||
_ignore, update_list = self.generate_update(False)
|
||||
|
||||
return False, update_list
|
||||
|
||||
|
||||
@register()
|
||||
class update_managed_post(Updater, GenerateUpdateMixin):
|
||||
"""
|
||||
Update managed entries
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
(restart, update_list) = self.generate_update(True)
|
||||
|
||||
return restart, update_list
|
||||
60
ipaserver/install/plugins/update_ca_topology.py
Normal file
60
ipaserver/install/plugins/update_ca_topology.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from ipalib import errors
|
||||
from ipalib import Registry
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
from ipaserver.install import cainstance
|
||||
from ipaserver.install import ldapupdate
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_ca_topology(Updater):
|
||||
"""
|
||||
Updates CA topology configuration entries
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
|
||||
ca = cainstance.CAInstance(self.api.env.realm)
|
||||
if not ca.is_configured():
|
||||
logger.debug("CA is not configured on this host")
|
||||
return False, []
|
||||
|
||||
ld = ldapupdate.LDAPUpdate(ldapi=True, sub_dict={
|
||||
'SUFFIX': self.api.env.basedn,
|
||||
'FQDN': self.api.env.host,
|
||||
})
|
||||
|
||||
ld.update([paths.CA_TOPOLOGY_ULDIF])
|
||||
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
ca_replica_dn = DN(
|
||||
('cn', 'replica'),
|
||||
('cn', 'o=ipaca'),
|
||||
('cn', 'mapping tree'),
|
||||
('cn', 'config'))
|
||||
|
||||
check_interval_attr = 'nsds5replicabinddngroupcheckinterval'
|
||||
default_check_interval = ['60']
|
||||
|
||||
try:
|
||||
ca_replica_entry = ldap.get_entry(ca_replica_dn)
|
||||
except errors.NotFound:
|
||||
pass
|
||||
else:
|
||||
if check_interval_attr not in ca_replica_entry:
|
||||
ca_replica_entry[check_interval_attr] = default_check_interval
|
||||
ldap.update_entry(ca_replica_entry)
|
||||
|
||||
return False, []
|
||||
128
ipaserver/install/plugins/update_dna_shared_config.py
Normal file
128
ipaserver/install/plugins/update_dna_shared_config.py
Normal file
@@ -0,0 +1,128 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
import time
|
||||
import ldap
|
||||
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib import errors
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_dna_shared_config(Updater):
|
||||
def execute(self, **options):
|
||||
method = options.get('method', "SASL/GSSAPI")
|
||||
protocol = options.get('protocol', "LDAP")
|
||||
|
||||
dna_bind_method = "dnaRemoteBindMethod"
|
||||
dna_conn_protocol = "dnaRemoteConnProtocol"
|
||||
dna_plugin = DN(('cn', 'Distributed Numeric Assignment Plugin'),
|
||||
('cn', 'plugins'),
|
||||
('cn', 'config'))
|
||||
dna_config_base = DN(('cn', 'posix IDs'), dna_plugin)
|
||||
|
||||
conn = self.api.Backend.ldap2
|
||||
|
||||
# Check the plugin is enabled else it is useless to update
|
||||
# the shared entry
|
||||
try:
|
||||
entry = conn.get_entry(dna_plugin)
|
||||
if entry.single_value.get('nsslapd-pluginenabled') == 'off':
|
||||
return False, ()
|
||||
except errors.NotFound:
|
||||
logger.error("Could not find DNA plugin entry: %s",
|
||||
dna_config_base)
|
||||
return False, ()
|
||||
|
||||
try:
|
||||
entry = conn.get_entry(dna_config_base)
|
||||
except errors.NotFound:
|
||||
logger.error("Could not find DNA config entry: %s",
|
||||
dna_config_base)
|
||||
return False, ()
|
||||
|
||||
sharedcfgdn = entry.single_value.get("dnaSharedCfgDN")
|
||||
if sharedcfgdn is not None:
|
||||
sharedcfgdn = DN(sharedcfgdn)
|
||||
else:
|
||||
logger.error(
|
||||
"Could not find DNA shared config DN in entry: %s",
|
||||
dna_config_base)
|
||||
return False, ()
|
||||
|
||||
#
|
||||
# Update the shared config entry related to that host
|
||||
#
|
||||
# If the shared config entry already exists (like upgrade)
|
||||
# the update occurs immediately without sleep.
|
||||
#
|
||||
# If the shared config entry does not exist (fresh install)
|
||||
# DS server waits for 30s after its startup to create it.
|
||||
# Startup likely occurred few sec before this function is
|
||||
# called so this loop will wait for 30s max.
|
||||
#
|
||||
# In case the server is not able to create the entry
|
||||
# The loop gives a grace period of 60s before logging
|
||||
# the failure to update the shared config entry and return
|
||||
#
|
||||
max_wait = 30
|
||||
fqdn = self.api.env.host
|
||||
for _i in range(0, max_wait + 1):
|
||||
try:
|
||||
entries = conn.get_entries(
|
||||
sharedcfgdn, scope=ldap.SCOPE_ONELEVEL,
|
||||
filter='dnaHostname=%s' % fqdn
|
||||
)
|
||||
break
|
||||
except errors.NotFound:
|
||||
logger.debug(
|
||||
"Unable to find DNA shared config entry for "
|
||||
"dnaHostname=%s (under %s) so far. Retry in 2 sec.",
|
||||
fqdn, sharedcfgdn
|
||||
)
|
||||
time.sleep(2)
|
||||
else:
|
||||
logger.error(
|
||||
"Could not get dnaHostname entries in %s seconds",
|
||||
max_wait * 2
|
||||
)
|
||||
return False, ()
|
||||
|
||||
# If there are several entries, all of them will be updated
|
||||
# just log a debug msg. This is likely the result of #5510
|
||||
if len(entries) != 1:
|
||||
logger.debug(
|
||||
"%d entries dnaHostname=%s under %s. One expected",
|
||||
len(entries), fqdn, sharedcfgdn
|
||||
)
|
||||
|
||||
# time to set the bind method and the protocol in the
|
||||
# shared config entries
|
||||
for entry in entries:
|
||||
update = False
|
||||
if entry.single_value.get(dna_bind_method) != method:
|
||||
entry[dna_bind_method] = method
|
||||
update = True
|
||||
|
||||
if entry.single_value.get(dna_conn_protocol) != protocol:
|
||||
entry[dna_conn_protocol] = protocol
|
||||
update = True
|
||||
|
||||
if update:
|
||||
try:
|
||||
conn.update_entry(entry)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Failed to set SASL/GSSAPI bind method/protocol "
|
||||
"in entry %s: %s", entry, e
|
||||
)
|
||||
# no restart, no update
|
||||
return False, ()
|
||||
@@ -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, []
|
||||
179
ipaserver/install/plugins/update_idranges.py
Normal file
179
ipaserver/install/plugins/update_idranges.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# Authors:
|
||||
# Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 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 ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_idrange_type(Updater):
|
||||
"""
|
||||
Update all ID ranges that do not have ipaRangeType attribute filled.
|
||||
This applies to all ID ranges prior to IPA 3.3.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
base_dn = DN(self.api.env.container_ranges, self.api.env.basedn)
|
||||
search_filter = ("(&(objectClass=ipaIDrange)(!(ipaRangeType=*)))")
|
||||
logger.debug("update_idrange_type: search for ID ranges with no "
|
||||
"type set")
|
||||
|
||||
while True:
|
||||
# Run the search in loop to avoid issues when LDAP limits are hit
|
||||
# during update
|
||||
|
||||
try:
|
||||
(entries, truncated) = ldap.find_entries(search_filter,
|
||||
['objectclass'], base_dn, time_limit=0, size_limit=0)
|
||||
|
||||
except errors.NotFound:
|
||||
logger.debug("update_idrange_type: no ID range without "
|
||||
"type set found")
|
||||
return False, []
|
||||
|
||||
except errors.ExecutionError as e:
|
||||
logger.error("update_idrange_type: cannot retrieve list "
|
||||
"of ranges with no type set: %s", e)
|
||||
return False, []
|
||||
|
||||
if not entries:
|
||||
# No entry was returned, rather break than continue cycling
|
||||
logger.debug("update_idrange_type: no ID range was returned")
|
||||
return False, []
|
||||
|
||||
logger.debug("update_idrange_type: found %d "
|
||||
"idranges to update, truncated: %s",
|
||||
len(entries), truncated)
|
||||
|
||||
error = False
|
||||
|
||||
# Set the range type
|
||||
for entry in entries:
|
||||
objectclasses = [o.lower() for o
|
||||
in entry.get('objectclass', [])]
|
||||
|
||||
if 'ipatrustedaddomainrange' in objectclasses:
|
||||
# NOTICE: assumes every AD range does not use POSIX
|
||||
# attributes
|
||||
entry['ipaRangeType'] = ['ipa-ad-trust']
|
||||
elif 'ipadomainidrange' in objectclasses:
|
||||
entry['ipaRangeType'] = ['ipa-local']
|
||||
else:
|
||||
entry['ipaRangeType'] = ['unknown']
|
||||
logger.error("update_idrange_type: could not detect "
|
||||
"range type for entry: %s", str(entry.dn))
|
||||
logger.error("update_idrange_type: ID range type set "
|
||||
"to 'unknown' for entry: %s", str(entry.dn))
|
||||
|
||||
try:
|
||||
ldap.update_entry(entry)
|
||||
except (errors.EmptyModlist, errors.NotFound):
|
||||
pass
|
||||
except errors.ExecutionError as e:
|
||||
logger.debug("update_idrange_type: cannot "
|
||||
"update idrange type: %s", e)
|
||||
error = True
|
||||
|
||||
if error:
|
||||
# Exit loop to avoid infinite cycles
|
||||
logger.error("update_idrange_type: error(s) "
|
||||
"detected during idrange type update")
|
||||
return False, []
|
||||
|
||||
elif not truncated:
|
||||
# All affected entries updated, exit the loop
|
||||
logger.debug("update_idrange_type: all affected idranges "
|
||||
"were assigned types")
|
||||
return False, []
|
||||
|
||||
return False, []
|
||||
|
||||
|
||||
@register()
|
||||
class update_idrange_baserid(Updater):
|
||||
"""
|
||||
Update ipa-ad-trust-posix ranges' base RID to 0. This applies to AD trust
|
||||
posix ranges prior to IPA 4.1.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
base_dn = DN(self.api.env.container_ranges, self.api.env.basedn)
|
||||
search_filter = ("(&(objectClass=ipaTrustedADDomainRange)"
|
||||
"(ipaRangeType=ipa-ad-trust-posix)"
|
||||
"(!(ipaBaseRID=0)))")
|
||||
logger.debug(
|
||||
"update_idrange_baserid: search for ipa-ad-trust-posix ID ranges "
|
||||
"with ipaBaseRID != 0"
|
||||
)
|
||||
|
||||
try:
|
||||
(entries, _truncated) = ldap.find_entries(
|
||||
search_filter, ['ipabaserid'], base_dn,
|
||||
paged_search=True, time_limit=0, size_limit=0)
|
||||
|
||||
except errors.NotFound:
|
||||
logger.debug("update_idrange_baserid: no AD domain "
|
||||
"range with posix attributes found")
|
||||
return False, []
|
||||
|
||||
except errors.ExecutionError as e:
|
||||
logger.error("update_idrange_baserid: cannot retrieve "
|
||||
"list of affected ranges: %s", e)
|
||||
return False, []
|
||||
|
||||
logger.debug("update_idrange_baserid: found %d "
|
||||
"idranges possible to update",
|
||||
len(entries))
|
||||
|
||||
error = False
|
||||
|
||||
# Set the range type
|
||||
for entry in entries:
|
||||
entry['ipabaserid'] = 0
|
||||
try:
|
||||
logger.debug("Updating existing idrange: %s", entry.dn)
|
||||
ldap.update_entry(entry)
|
||||
logger.info("Done")
|
||||
except (errors.EmptyModlist, errors.NotFound):
|
||||
pass
|
||||
except errors.ExecutionError as e:
|
||||
logger.debug("update_idrange_type: cannot "
|
||||
"update idrange: %s", e)
|
||||
error = True
|
||||
|
||||
if error:
|
||||
logger.error("update_idrange_baserid: error(s) "
|
||||
"detected during idrange baserid update")
|
||||
else:
|
||||
# All affected entries updated, exit the loop
|
||||
logger.debug("update_idrange_baserid: all affected "
|
||||
"idranges updated")
|
||||
|
||||
return False, []
|
||||
38
ipaserver/install/plugins/update_ldap_server_list.py
Normal file
38
ipaserver/install/plugins/update_ldap_server_list.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipalib import Registry
|
||||
from ipalib import Updater
|
||||
from ipalib import errors
|
||||
from ipapython.dn import DN
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_ldap_server_list(Updater):
|
||||
"""
|
||||
Update defaultServerList, an option that helps Solaris
|
||||
clients discover LDAP server replicas.
|
||||
"""
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
dn = DN(('cn', 'default'), ('ou', 'profile'), self.api.env.basedn)
|
||||
try:
|
||||
entry = ldap.get_entry(dn)
|
||||
srvlist = entry.single_value.get('defaultServerList', '')
|
||||
srvlist = srvlist.split()
|
||||
if not self.api.env.host in srvlist:
|
||||
srvlist.append(self.api.env.host)
|
||||
attr = ' '.join(srvlist)
|
||||
entry['defaultServerList'] = attr
|
||||
ldap.update_entry(entry)
|
||||
except errors.NotFound:
|
||||
pass
|
||||
except ldap.TYPE_OR_VALUE_EXISTS:
|
||||
pass
|
||||
|
||||
# no restart, no updates
|
||||
return False, ()
|
||||
746
ipaserver/install/plugins/update_managed_permissions.py
Normal file
746
ipaserver/install/plugins/update_managed_permissions.py
Normal file
@@ -0,0 +1,746 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 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/>.
|
||||
|
||||
"""
|
||||
Plugin for updating managed permissions.
|
||||
|
||||
The permissions are declared in Object plugins in the "managed_permissions"
|
||||
attribute, which is a dictionary mapping permission names to a "template"
|
||||
for the updater.
|
||||
For example, an entry could look like this:
|
||||
|
||||
managed_permissions = {
|
||||
'System: Read Object A': {
|
||||
'ipapermbindruletype': 'all',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {'cn', 'description'},
|
||||
'replaces_global_anonymous_aci': True,
|
||||
},
|
||||
}
|
||||
|
||||
For permissions not tied to an object plugin, a NONOBJECT_PERMISSIONS
|
||||
dict of the same format is defined in this module.
|
||||
|
||||
The permission name must start with the "System:" prefix.
|
||||
|
||||
The template dictionary can have the following keys:
|
||||
* ipapermtarget, ipapermtargetfilter, ipapermlocation, ipapermright, ,ipapermtargetto, ipapermtargetfrom, objectclass
|
||||
- Directly used as attributes on the permission.
|
||||
- Replaced when upgrading an existing permission
|
||||
- If not specified, these default to the defaults of a permission of the
|
||||
corresponding --type, or, if non_object is specified, or if not on an
|
||||
object, to general permission defaults .
|
||||
- ipapermlocation, ipatargetto, ipapermtargetfrom, ipapermtarget must be DNs
|
||||
- ipapermtargetfilter and objectclass must be iterables of strings
|
||||
* ipapermbindruletype
|
||||
- Directly used as attribute on the permission.
|
||||
- Not replaced when upgrading an existing permission.
|
||||
* ipapermdefaultattr
|
||||
- Used as attribute of the permission.
|
||||
- When upgrading, only new values are added; all old values are kept.
|
||||
* default_privileges
|
||||
- Names of privileges to add the permission to
|
||||
- Only applied on newly created permissions
|
||||
* replaces_global_anonymous_aci
|
||||
- If true, any attributes specified (denied) in the legacy global anonymous
|
||||
read ACI will be added to excluded_attributes of the new permission.
|
||||
- Has no effect when existing permissions are updated.
|
||||
* non_object
|
||||
- If true, no object-specific defaults are used (e.g. for
|
||||
ipapermtargetfilter, ipapermlocation).
|
||||
* replaces
|
||||
- A list of ACIs corresponding to legacy default permissions replaced
|
||||
by this permission.
|
||||
* replaces_system
|
||||
- A list of names of old SYSTEM permissions this replaces.
|
||||
* fixup_function
|
||||
- A callable that may modify the template in-place before it is applied.
|
||||
- Called with the permission name, template dict, and keyword arguments:
|
||||
- is_new: true if the permission was previously existing
|
||||
- anonymous_read_aci: the legacy 'Enable Anonymous access' ACI as
|
||||
an ipalib.aci.ACI object, or None if it does not exist
|
||||
Extra keyword arguments must be ignored, since this list may grow
|
||||
in the future.
|
||||
|
||||
No other keys are allowed in the template
|
||||
|
||||
The plugin also deletes permissions specified in OBSOLETE_PERMISSIONS.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import six
|
||||
|
||||
from ipalib import api, errors
|
||||
from ipapython.dn import DN
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.aci import ACI
|
||||
from ipalib import Updater
|
||||
from ipapython import ipautil
|
||||
from ipaserver.plugins import aci
|
||||
from ipaserver.plugins.permission import permission, permission_del
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
OBSOLETE_PERMISSIONS = {
|
||||
# These permissions will be removed on upgrade, if they exist.
|
||||
# Any modifications the user might have made to them are not taken
|
||||
# into account. This should be used sparingly.
|
||||
'System: Read Timestamp and USN Operational Attributes',
|
||||
'System: Read Creator and Modifier Operational Attributes',
|
||||
}
|
||||
|
||||
NONOBJECT_PERMISSIONS = {
|
||||
'System: Read IPA Masters': {
|
||||
'replaces_global_anonymous_aci': True,
|
||||
'ipapermlocation': DN('cn=masters,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=nscontainer)'},
|
||||
'ipapermbindruletype': 'permission',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'cn', 'objectclass', 'ipaconfigstring',
|
||||
},
|
||||
'default_privileges': {'IPA Masters Readers'},
|
||||
},
|
||||
'System: Compat Tree ID View targets': {
|
||||
'replaces_global_anonymous_aci': True,
|
||||
'ipapermlocation': api.env.basedn,
|
||||
'ipapermtarget': DN('cn=*,cn=compat', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=ipaOverrideTarget)'},
|
||||
'ipapermbindruletype': 'anonymous',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'ipaAnchorUUID',
|
||||
},
|
||||
},
|
||||
'System: Read DNA Configuration': {
|
||||
'replaces_global_anonymous_aci': True,
|
||||
'ipapermlocation': DN('cn=dna,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=dnasharedconfig)'},
|
||||
'ipapermbindruletype': 'all',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'cn', 'objectclass', 'dnaHostname', 'dnaPortNum',
|
||||
'dnaSecurePortNum', 'dnaRemoteBindMethod', 'dnaRemoteConnProtocol',
|
||||
'dnaRemainingValues',
|
||||
},
|
||||
},
|
||||
'System: Read CA Renewal Information': {
|
||||
'replaces_global_anonymous_aci': True,
|
||||
'ipapermlocation': DN('cn=ca_renewal,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=pkiuser)'},
|
||||
'ipapermbindruletype': 'all',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'cn', 'objectclass', 'usercertificate',
|
||||
},
|
||||
},
|
||||
'System: Add CA Certificate For Renewal': {
|
||||
'ipapermlocation': DN('cn=ca_renewal,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtarget': DN(
|
||||
'cn=caSigningCert cert-pki-ca,cn=ca_renewal,cn=ipa,cn=etc',
|
||||
api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=pkiuser)'},
|
||||
'ipapermbindruletype': 'permission',
|
||||
'ipapermright': {'add'},
|
||||
'default_privileges': {'Certificate Administrators'},
|
||||
},
|
||||
'System: Modify CA Certificate For Renewal': {
|
||||
'ipapermlocation': DN('cn=ca_renewal,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtarget': DN(
|
||||
'cn=caSigningCert cert-pki-ca,cn=ca_renewal,cn=ipa,cn=etc',
|
||||
api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=pkiuser)'},
|
||||
'ipapermbindruletype': 'permission',
|
||||
'ipapermright': {'write'},
|
||||
'ipapermdefaultattr': {
|
||||
'usercertificate',
|
||||
},
|
||||
'default_privileges': {'Certificate Administrators'},
|
||||
},
|
||||
'System: Read CA Certificate': {
|
||||
'replaces_global_anonymous_aci': True,
|
||||
'ipapermlocation': DN('cn=CAcert,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=pkica)'},
|
||||
'ipapermbindruletype': 'anonymous',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'cn', 'objectclass', 'cacertificate', 'certificaterevocationlist',
|
||||
'authorityrevocationlist', 'crosscertificatepair',
|
||||
},
|
||||
},
|
||||
'System: Modify CA Certificate': {
|
||||
'ipapermlocation': DN('cn=CAcert,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=pkica)'},
|
||||
'ipapermbindruletype': 'permission',
|
||||
'ipapermright': {'write'},
|
||||
'ipapermdefaultattr': {
|
||||
'cacertificate',
|
||||
},
|
||||
'default_privileges': {'Certificate Administrators'},
|
||||
},
|
||||
'System: Read Certificate Store Entries': {
|
||||
'ipapermlocation': DN('cn=certificates,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=ipacertificate)'},
|
||||
'ipapermbindruletype': 'anonymous',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'cn', 'objectclass', 'ipacertsubject', 'ipacertissuerserial',
|
||||
'ipapublickey', 'ipaconfigstring', 'cacertificate', 'ipakeytrust',
|
||||
'ipakeyusage', 'ipakeyextusage',
|
||||
},
|
||||
},
|
||||
'System: Add Certificate Store Entry': {
|
||||
'ipapermlocation': DN('cn=certificates,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=ipacertificate)'},
|
||||
'ipapermbindruletype': 'permission',
|
||||
'ipapermright': {'add'},
|
||||
'default_privileges': {'Certificate Administrators'},
|
||||
},
|
||||
'System: Modify Certificate Store Entry': {
|
||||
'ipapermlocation': DN('cn=certificates,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=ipacertificate)'},
|
||||
'ipapermbindruletype': 'permission',
|
||||
'ipapermright': {'write'},
|
||||
'ipapermdefaultattr': {
|
||||
'ipacertissuerserial', 'ipaconfigstring', 'cacertificate',
|
||||
'ipakeytrust', 'ipakeyusage', 'ipakeyextusage',
|
||||
},
|
||||
'default_privileges': {'Certificate Administrators'},
|
||||
},
|
||||
'System: Remove Certificate Store Entry': {
|
||||
'ipapermlocation': DN('cn=certificates,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=ipacertificate)'},
|
||||
'ipapermbindruletype': 'permission',
|
||||
'ipapermright': {'delete'},
|
||||
'default_privileges': {'Certificate Administrators'},
|
||||
},
|
||||
'System: Read Replication Information': {
|
||||
'replaces_global_anonymous_aci': True,
|
||||
'ipapermlocation': DN('cn=replication,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=nsds5replica)'},
|
||||
'ipapermbindruletype': 'all',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'cn', 'objectclass', 'nsds5replicaroot', 'nsds5replicaid',
|
||||
'nsds5replicacleanruv', 'nsds5replicaabortcleanruv',
|
||||
'nsds5replicatype', 'nsds5replicabinddn', 'nsstate',
|
||||
'nsds5replicaname', 'nsds5flags', 'nsds5task',
|
||||
'nsds5replicareferral', 'nsds5replicaautoreferral',
|
||||
'nsds5replicapurgedelay', 'nsds5replicatombstonepurgeinterval',
|
||||
'nsds5replicachangecount', 'nsds5replicalegacyconsumer',
|
||||
'nsds5replicaprotocoltimeout', 'nsds5replicabackoffmin',
|
||||
'nsds5replicabackoffmax',
|
||||
},
|
||||
},
|
||||
'System: Read AD Domains': {
|
||||
'replaces_global_anonymous_aci': True,
|
||||
'ipapermlocation': DN('cn=etc', api.env.basedn),
|
||||
'ipapermtarget': DN('cn=ad,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=ipantdomainattrs)'},
|
||||
'ipapermbindruletype': 'all',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'cn', 'objectclass', 'ipantsecurityidentifier', 'ipantflatname',
|
||||
'ipantdomainguid', 'ipantfallbackprimarygroup',
|
||||
},
|
||||
},
|
||||
'System: Read DUA Profile': {
|
||||
'ipapermlocation': DN('ou=profile', api.env.basedn),
|
||||
'ipapermtargetfilter': {
|
||||
'(|'
|
||||
'(objectclass=organizationalUnit)'
|
||||
'(objectclass=DUAConfigProfile)'
|
||||
')'
|
||||
},
|
||||
'ipapermbindruletype': 'anonymous',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'objectclass', 'ou', 'cn', 'defaultServerList',
|
||||
'preferredServerList', 'defaultSearchBase', 'defaultSearchScope',
|
||||
'searchTimeLimit', 'bindTimeLimit', 'credentialLevel',
|
||||
'authenticationMethod', 'followReferrals', 'dereferenceAliases',
|
||||
'serviceSearchDescriptor', 'serviceCredentialLevel',
|
||||
'serviceAuthenticationMethod', 'objectclassMap', 'attributeMap',
|
||||
'profileTTL'
|
||||
},
|
||||
},
|
||||
'System: Read Domain Level': {
|
||||
'ipapermlocation': DN('cn=Domain Level,cn=ipa,cn=etc', api.env.basedn),
|
||||
'ipapermtargetfilter': {'(objectclass=ipadomainlevelconfig)'},
|
||||
'ipapermbindruletype': 'all',
|
||||
'ipapermright': {'read', 'search', 'compare'},
|
||||
'ipapermdefaultattr': {
|
||||
'ipadomainlevel', 'objectclass',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class IncompatibleACIModification(Exception):
|
||||
"""User has made a legacy default perm modification we can't handle"""
|
||||
|
||||
|
||||
@register()
|
||||
class update_managed_permissions(Updater):
|
||||
"""Update managed permissions after an update.
|
||||
|
||||
Update managed permissions according to templates specified in plugins.
|
||||
For read permissions, puts any attributes specified in the legacy
|
||||
Anonymous access ACI in the exclude list when creating the permission.
|
||||
"""
|
||||
|
||||
def get_anonymous_read_aci(self, ldap):
|
||||
aciname = u'Enable Anonymous access'
|
||||
aciprefix = u'none'
|
||||
|
||||
base_entry = ldap.get_entry(self.api.env.basedn, ['aci'])
|
||||
|
||||
acistrs = base_entry.get('aci', [])
|
||||
acilist = aci._convert_strings_to_acis(acistrs)
|
||||
try:
|
||||
return aci._find_aci_by_name(acilist, aciprefix, aciname)
|
||||
except errors.NotFound:
|
||||
return None
|
||||
|
||||
def remove_anonymous_read_aci(self, ldap, anonymous_read_aci):
|
||||
base_entry = ldap.get_entry(self.api.env.basedn, ['aci'])
|
||||
|
||||
acistrs = base_entry.get('aci', [])
|
||||
|
||||
for acistr in acistrs:
|
||||
if ACI(acistr).isequal(anonymous_read_aci):
|
||||
logger.debug('Removing anonymous ACI: %s', acistr)
|
||||
acistrs.remove(acistr)
|
||||
break
|
||||
else:
|
||||
return
|
||||
|
||||
ldap.update_entry(base_entry)
|
||||
|
||||
def get_templates(self):
|
||||
"""Return (name, template, obj) triples for all managed permissions
|
||||
|
||||
If the permission is not defined in an object plugin, obj is None.
|
||||
Entries with the same obj are returned consecutively.
|
||||
"""
|
||||
for obj in sorted(self.api.Object(), key=lambda o: o.name):
|
||||
managed_permissions = getattr(obj, 'managed_permissions', {})
|
||||
for name, template in sorted(managed_permissions.items()):
|
||||
yield name, template, obj
|
||||
|
||||
for name, template in sorted(NONOBJECT_PERMISSIONS.items()):
|
||||
yield name, template, None
|
||||
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
anonymous_read_aci = self.get_anonymous_read_aci(ldap)
|
||||
|
||||
if anonymous_read_aci:
|
||||
logger.debug('Anonymous read ACI: %s', anonymous_read_aci)
|
||||
else:
|
||||
logger.debug('Anonymous ACI not found')
|
||||
|
||||
current_obj = () # initially distinct from any obj value, even None
|
||||
for name, template, obj in self.get_templates():
|
||||
if current_obj != obj:
|
||||
if obj:
|
||||
logger.debug('Updating managed permissions for %s',
|
||||
obj.name)
|
||||
else:
|
||||
logger.debug('Updating non-object managed permissions')
|
||||
current_obj = obj
|
||||
|
||||
self.update_permission(ldap,
|
||||
obj,
|
||||
unicode(name),
|
||||
template,
|
||||
anonymous_read_aci)
|
||||
|
||||
if anonymous_read_aci:
|
||||
self.remove_anonymous_read_aci(ldap, anonymous_read_aci)
|
||||
|
||||
for obsolete_name in OBSOLETE_PERMISSIONS:
|
||||
logger.debug('Deleting obsolete permission %s', obsolete_name)
|
||||
try:
|
||||
self.api.Command[permission_del](unicode(obsolete_name),
|
||||
force=True,
|
||||
version=u'2.101')
|
||||
except errors.NotFound:
|
||||
logger.debug('Obsolete permission not found')
|
||||
else:
|
||||
logger.debug('Obsolete permission deleted: %s', obsolete_name)
|
||||
|
||||
return False, ()
|
||||
|
||||
def update_permission(self, ldap, obj, name, template, anonymous_read_aci):
|
||||
"""Update the given permission and the corresponding ACI"""
|
||||
assert name.startswith('System:')
|
||||
|
||||
dn = self.api.Object[permission].get_dn(name)
|
||||
permission_plugin = self.api.Object[permission]
|
||||
|
||||
try:
|
||||
attrs_list = list(permission_plugin.default_attributes)
|
||||
attrs_list.remove('memberindirect')
|
||||
entry = ldap.get_entry(dn, attrs_list)
|
||||
is_new = False
|
||||
except errors.NotFound:
|
||||
entry = ldap.make_entry(dn)
|
||||
is_new = True
|
||||
|
||||
self.update_entry(obj, entry, template,
|
||||
anonymous_read_aci, is_new=is_new)
|
||||
|
||||
remove_legacy = False
|
||||
if 'replaces' in template:
|
||||
sub_dict = {
|
||||
'SUFFIX': str(self.api.env.basedn),
|
||||
'REALM': str(self.api.env.realm),
|
||||
}
|
||||
legacy_acistrs = [ipautil.template_str(r, sub_dict)
|
||||
for r in template['replaces']]
|
||||
|
||||
legacy_aci = ACI(legacy_acistrs[0])
|
||||
prefix, sep, legacy_name = legacy_aci.name.partition(':')
|
||||
assert prefix == 'permission' and sep
|
||||
|
||||
legacy_dn = permission_plugin.get_dn(legacy_name)
|
||||
try:
|
||||
legacy_entry = ldap.get_entry(legacy_dn,
|
||||
['ipapermissiontype', 'cn'])
|
||||
except errors.NotFound:
|
||||
logger.debug("Legacy permission %s not found", legacy_name)
|
||||
else:
|
||||
if 'ipapermissiontype' not in legacy_entry:
|
||||
if is_new:
|
||||
_acientry, acistr = (
|
||||
permission_plugin._get_aci_entry_and_string(
|
||||
legacy_entry, notfound_ok=True))
|
||||
try:
|
||||
included, excluded = self.get_upgrade_attr_lists(
|
||||
acistr, legacy_acistrs)
|
||||
except IncompatibleACIModification:
|
||||
logger.error(
|
||||
"Permission '%s' has been modified from its "
|
||||
"default; not updating it to '%s'.",
|
||||
legacy_name, name)
|
||||
return
|
||||
else:
|
||||
logger.debug("Merging attributes from legacy "
|
||||
"permission '%s'", legacy_name)
|
||||
logger.debug("Included attrs: %s",
|
||||
', '.join(sorted(included)))
|
||||
logger.debug("Excluded attrs: %s",
|
||||
', '.join(sorted(excluded)))
|
||||
entry['ipapermincludedattr'] = list(included)
|
||||
entry['ipapermexcludedattr'] = list(excluded)
|
||||
remove_legacy = True
|
||||
else:
|
||||
logger.debug("Ignoring attributes in legacy "
|
||||
"permission '%s' because '%s' exists",
|
||||
legacy_name, name)
|
||||
remove_legacy = True
|
||||
else:
|
||||
logger.debug("Ignoring V2 permission named '%s'",
|
||||
legacy_name)
|
||||
|
||||
update_aci = True
|
||||
logger.debug('Updating managed permission: %s', name)
|
||||
if is_new:
|
||||
ldap.add_entry(entry)
|
||||
else:
|
||||
try:
|
||||
ldap.update_entry(entry)
|
||||
except errors.EmptyModlist:
|
||||
logger.debug('No changes to permission: %s', name)
|
||||
update_aci = False
|
||||
|
||||
if update_aci:
|
||||
logger.debug('Updating ACI for managed permission: %s', name)
|
||||
permission_plugin.update_aci(entry)
|
||||
|
||||
if remove_legacy:
|
||||
logger.debug("Removing legacy permission '%s'", legacy_name)
|
||||
self.api.Command[permission_del](unicode(legacy_name))
|
||||
|
||||
for name in template.get('replaces_system', ()):
|
||||
name = unicode(name)
|
||||
try:
|
||||
entry = ldap.get_entry(permission_plugin.get_dn(name),
|
||||
['ipapermissiontype'])
|
||||
except errors.NotFound:
|
||||
logger.debug("Legacy permission '%s' not found", name)
|
||||
else:
|
||||
flags = entry.get('ipapermissiontype', [])
|
||||
if list(flags) == ['SYSTEM']:
|
||||
logger.debug("Removing legacy permission '%s'", name)
|
||||
self.api.Command[permission_del](name, force=True)
|
||||
else:
|
||||
logger.debug("Ignoring V2 permission '%s'", name)
|
||||
|
||||
def get_upgrade_attr_lists(self, current_acistring, default_acistrings):
|
||||
"""Compute included and excluded attributes for a new permission
|
||||
|
||||
:param current_acistring: ACI is in LDAP currently
|
||||
:param default_acistrings:
|
||||
List of all default ACIs IPA historically used for this permission
|
||||
:return:
|
||||
(ipapermincludedattr, ipapermexcludedattr) for the upgraded
|
||||
permission
|
||||
|
||||
An attribute will be included if the user has it in LDAP but it does
|
||||
not appear in *any* historic ACI.
|
||||
It will be excluded if it is in *all* historic ACIs but not in LDAP.
|
||||
Rationale: When we don't know which version of an ACI the user is
|
||||
upgrading from, we only consider attributes where all the versions
|
||||
agree. For other attrs we'll use the default from the new managed perm.
|
||||
|
||||
If the ACIs differ in something else than the list of attributes,
|
||||
raise IncompatibleACIModification. This means manual action is needed
|
||||
(either delete the old permission or change it to resemble the default
|
||||
again, then re-run ipa-ldap-updater).
|
||||
|
||||
In case there are multiple historic default ACIs, and some of them
|
||||
are compatible with the current but other ones aren't, we deduce that
|
||||
the user is upgrading from one of the compatible ones.
|
||||
The incompatible ones are removed from consideration, both for
|
||||
compatibility and attribute lists.
|
||||
"""
|
||||
assert default_acistrings
|
||||
|
||||
def _pop_targetattr(aci):
|
||||
"""Return the attr list it as a set, clear it in the ACI object
|
||||
"""
|
||||
targetattr = aci.target.get('targetattr')
|
||||
if targetattr:
|
||||
attrs = targetattr['expression']
|
||||
targetattr['expression'] = []
|
||||
return set(t.lower() for t in attrs)
|
||||
else:
|
||||
return set()
|
||||
|
||||
current_aci = ACI(current_acistring)
|
||||
current_attrs = _pop_targetattr(current_aci)
|
||||
logger.debug("Current ACI for '%s': %s",
|
||||
current_aci.name, current_acistring)
|
||||
|
||||
attrs_in_all_defaults = None
|
||||
attrs_in_any_defaults = set()
|
||||
all_incompatible = True
|
||||
for default_acistring in default_acistrings:
|
||||
default_aci = ACI(default_acistring)
|
||||
default_attrs = _pop_targetattr(default_aci)
|
||||
logger.debug("Default ACI for '%s': %s",
|
||||
default_aci.name, default_acistring)
|
||||
|
||||
if current_aci != default_aci:
|
||||
logger.debug('ACIs not compatible')
|
||||
continue
|
||||
else:
|
||||
all_incompatible = False
|
||||
|
||||
if attrs_in_all_defaults is None:
|
||||
attrs_in_all_defaults = set(default_attrs)
|
||||
else:
|
||||
attrs_in_all_defaults &= attrs_in_all_defaults
|
||||
attrs_in_any_defaults |= default_attrs
|
||||
|
||||
if all_incompatible:
|
||||
logger.debug('All old default ACIs are incompatible')
|
||||
raise(IncompatibleACIModification())
|
||||
|
||||
included = current_attrs - attrs_in_any_defaults
|
||||
excluded = attrs_in_all_defaults - current_attrs
|
||||
|
||||
return included, excluded
|
||||
|
||||
def update_entry(self, obj, entry, template,
|
||||
anonymous_read_aci, is_new):
|
||||
"""Update the given permission Entry (without contacting LDAP)"""
|
||||
|
||||
[name_ava] = entry.dn[0]
|
||||
assert name_ava.attr == 'cn'
|
||||
name = name_ava.value
|
||||
entry.single_value['cn'] = name
|
||||
|
||||
template = dict(template)
|
||||
template.pop('replaces', None)
|
||||
template.pop('replaces_system', None)
|
||||
template.pop('replaces_permissions', None)
|
||||
template.pop('replaces_acis', None)
|
||||
|
||||
fixup_function = template.pop('fixup_function', None)
|
||||
if fixup_function:
|
||||
fixup_function(name, template,
|
||||
is_new=is_new,
|
||||
anonymous_read_aci=anonymous_read_aci)
|
||||
|
||||
if template.pop('non_object', False):
|
||||
obj = None
|
||||
|
||||
entry['ipapermissiontype'] = [u'SYSTEM', u'V2', u'MANAGED']
|
||||
|
||||
# Attributes with defaults
|
||||
objectclass = template.pop('objectclass', None)
|
||||
if objectclass is None:
|
||||
objectclass = self.api.Object[permission].object_class
|
||||
entry['objectclass'] = list(objectclass)
|
||||
|
||||
ldap_filter = template.pop('ipapermtargetfilter', None)
|
||||
if obj and ldap_filter is None:
|
||||
ldap_filter = [self.api.Object[permission].make_type_filter(obj)]
|
||||
entry['ipapermtargetfilter'] = list(ldap_filter or [])
|
||||
|
||||
ipapermlocation = template.pop('ipapermlocation', None)
|
||||
if ipapermlocation is None:
|
||||
assert obj
|
||||
ipapermlocation = DN(obj.container_dn, self.api.env.basedn)
|
||||
entry.single_value['ipapermlocation'] = ipapermlocation
|
||||
|
||||
# Optional attributes
|
||||
ipapermtarget = template.pop('ipapermtarget', None)
|
||||
if ipapermtarget is not None:
|
||||
entry['ipapermtarget'] = ipapermtarget
|
||||
|
||||
ipapermtargetto = template.pop('ipapermtargetto', None)
|
||||
if ipapermtargetto is not None:
|
||||
entry['ipapermtargetto'] = ipapermtargetto
|
||||
|
||||
ipapermtargetfrom = template.pop('ipapermtargetfrom', None)
|
||||
if ipapermtargetfrom is not None:
|
||||
entry['ipapermtargetfrom'] = ipapermtargetfrom
|
||||
|
||||
# Attributes from template
|
||||
bindruletype = template.pop('ipapermbindruletype', 'permission')
|
||||
if is_new:
|
||||
entry.single_value['ipapermbindruletype'] = bindruletype
|
||||
|
||||
entry['ipapermright'] = list(template.pop('ipapermright'))
|
||||
|
||||
default_privileges = template.pop('default_privileges', None)
|
||||
if is_new and default_privileges:
|
||||
entry['member'] = list(
|
||||
DN(('cn', privilege_name),
|
||||
self.api.env.container_privilege,
|
||||
self.api.env.basedn)
|
||||
for privilege_name in default_privileges)
|
||||
|
||||
# Add to the set of default attributes
|
||||
attributes = set(template.pop('ipapermdefaultattr', ()))
|
||||
attributes.update(entry.get('ipapermdefaultattr', ()))
|
||||
attributes = set(a.lower() for a in attributes)
|
||||
entry['ipapermdefaultattr'] = list(attributes)
|
||||
|
||||
# Exclude attributes filtered from the global read ACI
|
||||
replaces_ga_aci = template.pop('replaces_global_anonymous_aci', False)
|
||||
if replaces_ga_aci and is_new and anonymous_read_aci:
|
||||
read_blacklist = set(
|
||||
a.lower() for a in
|
||||
anonymous_read_aci.target['targetattr']['expression'])
|
||||
read_blacklist &= attributes
|
||||
if read_blacklist:
|
||||
logger.debug('Excluded attributes for %s: %s',
|
||||
name, ', '.join(read_blacklist))
|
||||
entry['ipapermexcludedattr'] = list(read_blacklist)
|
||||
|
||||
# Sanity check
|
||||
if template:
|
||||
raise ValueError(
|
||||
'Unknown key(s) in managed permission template %s: %s' % (
|
||||
name, ', '.join(template.keys())))
|
||||
|
||||
|
||||
@register()
|
||||
class update_read_replication_agreements_permission(Updater):
|
||||
"""'Read replication agreements' permission must not be managed permission
|
||||
|
||||
https://fedorahosted.org/freeipa/ticket/5631
|
||||
|
||||
Existing permission "cn=System: Read Replication Agreements" must be moved
|
||||
to non-managed permission "cn=Read Replication Agreements" using modrdn
|
||||
ldap operation to keep current membership of the permission set by user.
|
||||
|
||||
ACI is updated via update files
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
old_perm_dn = DN(
|
||||
('cn', 'System: Read Replication Agreements'),
|
||||
self.api.env.container_permission,
|
||||
self.api.env.basedn
|
||||
)
|
||||
|
||||
new_perm_dn = DN(
|
||||
('cn', 'Read Replication Agreements'),
|
||||
self.api.env.container_permission,
|
||||
self.api.env.basedn
|
||||
)
|
||||
|
||||
try:
|
||||
perm_entry = ldap.get_entry(old_perm_dn)
|
||||
except errors.NotFound:
|
||||
logger.debug("Old permission not found")
|
||||
return False, ()
|
||||
|
||||
try:
|
||||
ldap.get_entry(new_perm_dn)
|
||||
except errors.NotFound:
|
||||
# we can happily upgrade
|
||||
pass
|
||||
else:
|
||||
logger.error("Permission '%s' cannot be upgraded. "
|
||||
"Permission with target name '%s' already "
|
||||
"exists", old_perm_dn, new_perm_dn)
|
||||
return False, ()
|
||||
|
||||
# values are case insensitive
|
||||
for t in list(perm_entry['ipapermissiontype']):
|
||||
if t.lower() in ['managed', 'v2']:
|
||||
perm_entry['ipapermissiontype'].remove(t)
|
||||
|
||||
for o in list(perm_entry['objectclass']):
|
||||
if o.lower() == 'ipapermissionv2':
|
||||
# remove permission V2 objectclass and related attributes
|
||||
perm_entry['objectclass'].remove(o)
|
||||
perm_entry['ipapermdefaultattr'] = []
|
||||
perm_entry['ipapermright'] = []
|
||||
perm_entry['ipapermbindruletype'] = []
|
||||
perm_entry['ipapermlocation'] = []
|
||||
perm_entry['ipapermtargetfilter'] = []
|
||||
|
||||
logger.debug("Removing MANAGED attributes from permission %s",
|
||||
old_perm_dn)
|
||||
try:
|
||||
ldap.update_entry(perm_entry)
|
||||
except errors.EmptyModlist:
|
||||
pass
|
||||
|
||||
# do modrdn on permission
|
||||
logger.debug("modrdn: %s -> %s", old_perm_dn, new_perm_dn)
|
||||
ldap.move_entry(old_perm_dn, new_perm_dn)
|
||||
return False, ()
|
||||
90
ipaserver/install/plugins/update_nis.py
Normal file
90
ipaserver/install/plugins/update_nis.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib import errors
|
||||
from ipalib import Updater
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.dn import DN
|
||||
from ipaserver.install import sysupgrade
|
||||
from ipaserver.install.ldapupdate import LDAPUpdate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_nis_configuration(Updater):
|
||||
"""Update NIS configuration
|
||||
|
||||
NIS configuration can be updated only if NIS Server was configured via
|
||||
ipa-nis-manage command.
|
||||
"""
|
||||
|
||||
def __recover_from_missing_maps(self, ldap):
|
||||
# https://fedorahosted.org/freeipa/ticket/5507
|
||||
# if all following DNs are missing, but 'NIS Server' container exists
|
||||
# we are experiencig bug and maps should be fixed
|
||||
|
||||
if sysupgrade.get_upgrade_state('nis',
|
||||
'done_recover_from_missing_maps'):
|
||||
# this recover must be done only once, a user may deleted some
|
||||
# maps, we do not want to restore them again
|
||||
return
|
||||
|
||||
logger.debug("Recovering from missing NIS maps bug")
|
||||
|
||||
suffix = "cn=NIS Server,cn=plugins,cn=config"
|
||||
domain = self.api.env.domain
|
||||
missing_dn_list = [
|
||||
DN(nis_map.format(domain=domain, suffix=suffix)) for nis_map in [
|
||||
"nis-domain={domain}+nis-map=passwd.byname,{suffix}",
|
||||
"nis-domain={domain}+nis-map=passwd.byuid,{suffix}",
|
||||
"nis-domain={domain}+nis-map=group.byname,{suffix}",
|
||||
"nis-domain={domain}+nis-map=group.bygid,{suffix}",
|
||||
"nis-domain={domain}+nis-map=netid.byname,{suffix}",
|
||||
"nis-domain={domain}+nis-map=netgroup,{suffix}",
|
||||
]
|
||||
]
|
||||
|
||||
for dn in missing_dn_list:
|
||||
try:
|
||||
ldap.get_entry(dn, attrs_list=['cn'])
|
||||
except errors.NotFound:
|
||||
pass
|
||||
else:
|
||||
# bug is not effective, at least one of 'possible missing'
|
||||
# maps was detected
|
||||
return
|
||||
|
||||
sysupgrade.set_upgrade_state('nis', 'done_recover_from_missing_maps',
|
||||
True)
|
||||
|
||||
# bug is effective run update to recreate missing maps
|
||||
ld = LDAPUpdate(sub_dict={}, ldapi=True)
|
||||
ld.update([paths.NIS_ULDIF])
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
dn = DN(('cn', 'NIS Server'), ('cn', 'plugins'), ('cn', 'config'))
|
||||
try:
|
||||
ldap.get_entry(dn, attrs_list=['cn'])
|
||||
except errors.NotFound:
|
||||
# NIS is not configured on system, do not execute update
|
||||
logger.debug("Skipping NIS update, NIS Server is not configured")
|
||||
|
||||
# container does not exist, bug #5507 is not effective
|
||||
sysupgrade.set_upgrade_state(
|
||||
'nis', 'done_recover_from_missing_maps', True)
|
||||
else:
|
||||
self.__recover_from_missing_maps(ldap)
|
||||
|
||||
logger.debug("Executing NIS Server update")
|
||||
ld = LDAPUpdate(sub_dict={}, ldapi=True)
|
||||
ld.update([paths.NIS_UPDATE_ULDIF])
|
||||
|
||||
return False, ()
|
||||
59
ipaserver/install/plugins/update_pacs.py
Normal file
59
ipaserver/install/plugins/update_pacs.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# Authors:
|
||||
# Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 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 ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_pacs(Updater):
|
||||
"""
|
||||
Includes default nfs:None only if no nfs: PAC present in ipakrbauthzdata.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
try:
|
||||
dn = DN('cn=ipaConfig', 'cn=etc', self.api.env.basedn)
|
||||
entry = ldap.get_entry(dn, ['ipakrbauthzdata'])
|
||||
pacs = entry.get('ipakrbauthzdata', [])
|
||||
except errors.NotFound:
|
||||
logger.warning('Error retrieving: %s', str(dn))
|
||||
return False, []
|
||||
|
||||
nfs_pac_set = any(pac.startswith('nfs:') for pac in pacs)
|
||||
|
||||
if not nfs_pac_set:
|
||||
logger.debug('Adding nfs:NONE to default PAC types')
|
||||
|
||||
updated_pacs = pacs + [u'nfs:NONE']
|
||||
entry['ipakrbauthzdata'] = updated_pacs
|
||||
ldap.update_entry(entry)
|
||||
else:
|
||||
logger.debug('PAC for nfs is already set, not adding nfs:NONE.')
|
||||
|
||||
return False, []
|
||||
81
ipaserver/install/plugins/update_passsync.py
Normal file
81
ipaserver/install/plugins/update_passsync.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#
|
||||
# Copyright (C) 2014 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from ipalib import Registry, errors
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
from ipaserver.install import sysupgrade
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_passync_privilege_check(Updater):
|
||||
|
||||
def execute(self, **options):
|
||||
update_done = sysupgrade.get_upgrade_state('winsync', 'passsync_privilege_updated')
|
||||
if update_done:
|
||||
logger.debug("PassSync privilege update pre-check not needed")
|
||||
return False, []
|
||||
|
||||
logger.debug("Check if there is existing PassSync privilege")
|
||||
|
||||
passsync_privilege_dn = DN(('cn','PassSync Service'),
|
||||
self.api.env.container_privilege,
|
||||
self.api.env.basedn)
|
||||
|
||||
ldap = self.api.Backend.ldap2
|
||||
try:
|
||||
ldap.get_entry(passsync_privilege_dn, [''])
|
||||
except errors.NotFound:
|
||||
logger.debug("PassSync privilege not found, this is a new update")
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', False)
|
||||
else:
|
||||
logger.debug("PassSync privilege found, skip updating PassSync")
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True)
|
||||
|
||||
return False, []
|
||||
|
||||
|
||||
@register()
|
||||
class update_passync_privilege_update(Updater):
|
||||
"""
|
||||
Add PassSync user as a member of PassSync privilege, if it exists
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
update_done = sysupgrade.get_upgrade_state('winsync', 'passsync_privilege_updated')
|
||||
if update_done:
|
||||
logger.debug("PassSync privilege update not needed")
|
||||
return False, []
|
||||
|
||||
logger.debug("Add PassSync user as a member of PassSync privilege")
|
||||
ldap = self.api.Backend.ldap2
|
||||
passsync_dn = DN(('uid','passsync'), ('cn', 'sysaccounts'), ('cn', 'etc'),
|
||||
self.api.env.basedn)
|
||||
passsync_privilege_dn = DN(('cn','PassSync Service'),
|
||||
self.api.env.container_privilege,
|
||||
self.api.env.basedn)
|
||||
|
||||
try:
|
||||
ldap.get_entry(passsync_dn, [''])
|
||||
except errors.NotFound:
|
||||
logger.debug("PassSync user not found, no update needed")
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True)
|
||||
return False, []
|
||||
else:
|
||||
logger.debug("PassSync user found, do update")
|
||||
|
||||
update = {'dn': passsync_privilege_dn,
|
||||
'updates': [
|
||||
dict(action='add', attr='member', value=passsync_dn),
|
||||
]
|
||||
}
|
||||
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True)
|
||||
return False, [update]
|
||||
64
ipaserver/install/plugins/update_ra_cert_store.py
Normal file
64
ipaserver/install/plugins/update_ra_cert_store.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from ipalib import Registry
|
||||
from ipalib import Updater
|
||||
from ipalib.install import certmonger
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.certdb import NSSDatabase
|
||||
from ipaserver.install import cainstance
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_ra_cert_store(Updater):
|
||||
"""
|
||||
Moves the ipaCert store from /etc/httpd/alias RA_AGENT_PEM, RA_AGENT_KEY
|
||||
files
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ra_nick = 'ipaCert'
|
||||
ca_enabled = self.api.Command.ca_is_enabled()['result']
|
||||
if not ca_enabled:
|
||||
return False, []
|
||||
|
||||
certdb = NSSDatabase(nssdir=paths.HTTPD_ALIAS_DIR)
|
||||
if not certdb.has_nickname(ra_nick):
|
||||
# Nothign to do
|
||||
return False, []
|
||||
elif os.path.exists(paths.RA_AGENT_PEM):
|
||||
# even though the certificate file exists, we will overwrite it
|
||||
# as it's probabably something wrong anyway
|
||||
logger.warning(
|
||||
"A certificate with the nickname 'ipaCert' exists in "
|
||||
"the old '%s' NSS database as well as in the new "
|
||||
"PEM file '%s'",
|
||||
paths.HTTPD_ALIAS_DIR, paths.RA_AGENT_PEM)
|
||||
|
||||
_fd, p12file = tempfile.mkstemp(dir=certdb.secdir)
|
||||
# no password is necessary as we will be saving it in clear anyway
|
||||
certdb.export_pkcs12(ra_nick, p12file, pkcs12_passwd='')
|
||||
|
||||
# stop tracking the old cert and remove it
|
||||
certmonger.stop_tracking(paths.HTTPD_ALIAS_DIR, nickname=ra_nick)
|
||||
certdb.delete_cert(ra_nick)
|
||||
if os.path.exists(paths.OLD_KRA_AGENT_PEM):
|
||||
os.remove(paths.OLD_KRA_AGENT_PEM)
|
||||
|
||||
# get the private key and certificate from the file and start
|
||||
# tracking it in certmonger
|
||||
ca = cainstance.CAInstance()
|
||||
ca.import_ra_cert(p12file)
|
||||
|
||||
os.remove(p12file)
|
||||
|
||||
return False, []
|
||||
92
ipaserver/install/plugins/update_referint.py
Normal file
92
ipaserver/install/plugins/update_referint.py
Normal file
@@ -0,0 +1,92 @@
|
||||
#
|
||||
# Copyright (C) 2014 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from ipalib import Registry, errors
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_referint(Updater):
|
||||
"""
|
||||
Update referential integrity configuration to new style
|
||||
http://directory.fedoraproject.org/docs/389ds/design/ri-plugin-configuration.html
|
||||
|
||||
old attr -> new attr
|
||||
nsslapd-pluginArg0 -> referint-update-delay
|
||||
nsslapd-pluginArg1 -> referint-logfile
|
||||
nsslapd-pluginArg2 -> referint-logchanges
|
||||
nsslapd-pluginArg3..N -> referint-membership-attr [3..N]
|
||||
|
||||
Old and new style cannot be mixed, all nslapd-pluginArg* attrs have to be removed
|
||||
"""
|
||||
|
||||
referint_dn = DN(('cn', 'referential integrity postoperation'),
|
||||
('cn', 'plugins'), ('cn', 'config'))
|
||||
|
||||
def execute(self, **options):
|
||||
|
||||
logger.debug("Upgrading referential integrity plugin configuration")
|
||||
ldap = self.api.Backend.ldap2
|
||||
try:
|
||||
entry = ldap.get_entry(self.referint_dn)
|
||||
except errors.NotFound:
|
||||
logger.error("Referential integrity configuration not found")
|
||||
return False, []
|
||||
|
||||
referint_membership_attrs = []
|
||||
|
||||
logger.debug("Initial value: %s", repr(entry))
|
||||
|
||||
# nsslapd-pluginArg0 -> referint-update-delay
|
||||
update_delay = entry.get('nsslapd-pluginArg0')
|
||||
if update_delay:
|
||||
logger.debug("add: referint-update-delay: %s", update_delay)
|
||||
entry['referint-update-delay'] = update_delay
|
||||
entry['nsslapd-pluginArg0'] = None
|
||||
else:
|
||||
logger.debug("Plugin already uses new style, skipping")
|
||||
return False, []
|
||||
|
||||
# nsslapd-pluginArg1 -> referint-logfile
|
||||
logfile = entry.get('nsslapd-pluginArg1')
|
||||
if logfile:
|
||||
logger.debug("add: referint-logfile: %s", logfile)
|
||||
entry['referint-logfile'] = logfile
|
||||
entry['nsslapd-pluginArg1'] = None
|
||||
|
||||
# nsslapd-pluginArg2 -> referint-logchanges
|
||||
logchanges = entry.get('nsslapd-pluginArg2')
|
||||
if logchanges:
|
||||
logger.debug("add: referint-logchanges: %s", logchanges)
|
||||
entry['referint-logchanges'] = logchanges
|
||||
entry['nsslapd-pluginArg2'] = None
|
||||
|
||||
# nsslapd-pluginArg3..N -> referint-membership-attr [3..N]
|
||||
for key in list(entry):
|
||||
if key.lower().startswith('nsslapd-pluginarg'):
|
||||
arg_val = entry.single_value[key]
|
||||
if arg_val:
|
||||
referint_membership_attrs.append(arg_val)
|
||||
entry[key] = None
|
||||
|
||||
if referint_membership_attrs:
|
||||
# entry['referint-membership-attr'] is None, plugin doesn't allow
|
||||
# mixing old and new style
|
||||
entry['referint-membership-attr'] = referint_membership_attrs
|
||||
|
||||
logger.debug("Final value: %s", repr(entry))
|
||||
try:
|
||||
ldap.update_entry(entry)
|
||||
except errors.EmptyModlist:
|
||||
logger.debug("No modifications required")
|
||||
return False, []
|
||||
|
||||
return False, []
|
||||
96
ipaserver/install/plugins/update_services.py
Normal file
96
ipaserver/install/plugins/update_services.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# Authors:
|
||||
# Martin Kosek <mkosek@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2012 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 ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_service_principalalias(Updater):
|
||||
"""
|
||||
Update all services which do not have ipakrbprincipalalias attribute
|
||||
used for case-insensitive principal searches filled. This applies for
|
||||
all services created prior IPA 3.0.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
base_dn = DN(self.api.env.container_service, self.api.env.basedn)
|
||||
search_filter = ("(&(objectclass=krbprincipal)(objectclass=ipaservice)"
|
||||
"(!(objectclass=ipakrbprincipal)))")
|
||||
logger.debug("update_service_principalalias: search for affected "
|
||||
"services")
|
||||
|
||||
while True:
|
||||
# run the search in loop to avoid issues when LDAP limits are hit
|
||||
# during update
|
||||
try:
|
||||
(entries, truncated) = ldap.find_entries(search_filter,
|
||||
['objectclass', 'krbprincipalname'], base_dn,
|
||||
time_limit=0, size_limit=0)
|
||||
except errors.NotFound:
|
||||
logger.debug("update_service_principalalias: no service "
|
||||
"to update found")
|
||||
return False, []
|
||||
except errors.ExecutionError as e:
|
||||
logger.error("update_service_principalalias: cannot "
|
||||
"retrieve list of affected services: %s", e)
|
||||
return False, []
|
||||
if not entries:
|
||||
# no entry was returned, rather break than continue cycling
|
||||
logger.debug("update_service_principalalias: no service "
|
||||
"was returned")
|
||||
return False, []
|
||||
logger.debug("update_service_principalalias: found %d "
|
||||
"services to update, truncated: %s",
|
||||
len(entries), truncated)
|
||||
|
||||
error = False
|
||||
for entry in entries:
|
||||
entry['objectclass'] = (entry['objectclass'] +
|
||||
['ipakrbprincipal'])
|
||||
entry['ipakrbprincipalalias'] = entry['krbprincipalname']
|
||||
try:
|
||||
ldap.update_entry(entry)
|
||||
except (errors.EmptyModlist, errors.NotFound):
|
||||
pass
|
||||
except errors.ExecutionError as e:
|
||||
logger.debug("update_service_principalalias: cannot "
|
||||
"update service: %s", e)
|
||||
error = True
|
||||
|
||||
if error:
|
||||
# exit loop to avoid infinite cycles
|
||||
logger.error("update_service_principalalias: error(s)"
|
||||
"detected during service update")
|
||||
return False, []
|
||||
elif not truncated:
|
||||
# all affected entries updated, exit the loop
|
||||
logger.debug("update_service_principalalias: all affected"
|
||||
" services updated")
|
||||
return False, []
|
||||
return False, []
|
||||
226
ipaserver/install/plugins/update_uniqueness.py
Normal file
226
ipaserver/install/plugins/update_uniqueness.py
Normal file
@@ -0,0 +1,226 @@
|
||||
# Authors:
|
||||
# Alexander Bokovoy <abokovoy@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 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 ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_uniqueness_plugins_to_new_syntax(Updater):
|
||||
"""
|
||||
Migrate uniqueness plugins to new style syntax
|
||||
|
||||
* OLD: *
|
||||
nsslapd-pluginarg0: uid
|
||||
nsslapd-pluginarg1: dc=people,dc=example,dc=com
|
||||
nsslapd-pluginarg2: dc=sales, dc=example,dc=com
|
||||
|
||||
or
|
||||
|
||||
nsslapd-pluginarg0: attribute=uid
|
||||
nsslapd-pluginarg1: markerobjectclass=organizationalUnit
|
||||
nsslapd-pluginarg2: requiredobjectclass=person
|
||||
|
||||
* NEW: *
|
||||
uniqueness-attribute-name: uid
|
||||
uniqueness-subtrees: dc=people,dc=example,dc=com
|
||||
uniqueness-subtrees: dc=sales, dc=example,dc=com
|
||||
uniqueness-across-all-subtrees: on
|
||||
|
||||
or
|
||||
|
||||
uniqueness-attribute-name: uid
|
||||
uniqueness-top-entry-oc: organizationalUnit
|
||||
uniqueness-subtree-entries-oc: person
|
||||
"""
|
||||
|
||||
plugins_dn = DN(('cn', 'plugins'), ('cn', 'config'))
|
||||
|
||||
def __remove_update(self, update, key, value):
|
||||
statement = dict(action='remove', attr=key, value=value)
|
||||
update.setdefault('updates', []).append(statement)
|
||||
|
||||
def __add_update(self, update, key, value):
|
||||
statement = dict(action='add', attr=key, value=value)
|
||||
update.setdefault('updates', []).append(statement)
|
||||
|
||||
def __subtree_style(self, entry):
|
||||
"""
|
||||
old attr -> new attr
|
||||
nsslapd-pluginArg0 -> uniqueness-attribute-name
|
||||
nsslapd-pluginArg1..N -> uniqueness-subtrees[1..N]
|
||||
"""
|
||||
update = {
|
||||
'dn': entry.dn,
|
||||
'updates': [],
|
||||
}
|
||||
|
||||
# nsslapd-pluginArg0 -> referint-update-delay
|
||||
attribute = entry.single_value['nsslapd-pluginArg0']
|
||||
if not attribute:
|
||||
raise ValueError("'nsslapd-pluginArg0' not found")
|
||||
self.__remove_update(update, 'nsslapd-pluginArg0', attribute)
|
||||
self.__add_update(update, 'uniqueness-attribute-name', attribute)
|
||||
entry['nsslapd-pluginArg0'] = None
|
||||
|
||||
# nsslapd-pluginArg1..N -> uniqueness-subtrees[1..N]
|
||||
for key in entry.keys():
|
||||
if key.lower().startswith('nsslapd-pluginarg'):
|
||||
subtree_dn = entry.single_value[key]
|
||||
if subtree_dn:
|
||||
self.__remove_update(update, key, subtree_dn)
|
||||
self.__add_update(update, 'uniqueness-subtrees', subtree_dn)
|
||||
|
||||
return update
|
||||
|
||||
def __objectclass_style(self, entry):
|
||||
"""
|
||||
old attr -> new attr
|
||||
nsslapd-pluginArg?[attribute] -> uniqueness-attribute-name
|
||||
nsslapd-pluginArg?[markerobjectclass] -> uniqueness-top-entry-oc
|
||||
nsslapd-pluginArg?[requiredobjectclass](optional)
|
||||
-> uniqueness-subtree-entries-oc
|
||||
nsslapd-pluginArg?[others] -> ERROR: unexpected args
|
||||
|
||||
Single value attributes.
|
||||
"""
|
||||
|
||||
update = {
|
||||
'dn': entry.dn,
|
||||
'updates': [],
|
||||
}
|
||||
|
||||
attribute = None
|
||||
markerobjectclass = None
|
||||
requiredobjectclass = None
|
||||
|
||||
for key in entry.keys():
|
||||
if key.lower().startswith('nsslapd-pluginarg'):
|
||||
try:
|
||||
# split argument name and value
|
||||
value = entry.single_value[key]
|
||||
arg_name, arg_val = value.split('=', 1)
|
||||
except ValueError:
|
||||
# unable to split
|
||||
raise ValueError("unexpected argument %s: %s" %
|
||||
(key, value))
|
||||
arg_name = arg_name.lower()
|
||||
if arg_name == 'attribute':
|
||||
if attribute:
|
||||
raise ValueError("single value argument 'attribute' "
|
||||
"is specified mutliple times")
|
||||
attribute = arg_val
|
||||
self.__remove_update(update, key, value)
|
||||
elif arg_name == 'markerobjectclass':
|
||||
if markerobjectclass:
|
||||
raise ValueError("single value argument "
|
||||
"'markerobjectclass' "
|
||||
"is specified mutliple times")
|
||||
markerobjectclass = arg_val
|
||||
self.__remove_update(update, key, value)
|
||||
elif arg_name == 'requiredobjectclass':
|
||||
if requiredobjectclass:
|
||||
raise ValueError("single value argument "
|
||||
"'requiredobjectclass' "
|
||||
"is specified mutliple times")
|
||||
requiredobjectclass = arg_val
|
||||
self.__remove_update(update, key, value)
|
||||
else:
|
||||
raise ValueError("unexpected argument '%s: %s'" %
|
||||
(key, value))
|
||||
|
||||
if not attribute:
|
||||
raise ValueError("missing required argument 'attribute'")
|
||||
if not markerobjectclass:
|
||||
raise ValueError("missing required argument 'markerobjectclass'")
|
||||
|
||||
self.__add_update(update, 'uniqueness-attribute-name', attribute)
|
||||
self.__add_update(update, 'uniqueness-top-entry-oc', markerobjectclass)
|
||||
|
||||
if requiredobjectclass:
|
||||
# optional argument
|
||||
self.__add_update(update, 'uniqueness-subtree-entries-oc',
|
||||
requiredobjectclass)
|
||||
|
||||
return update
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
old_style_plugin_search_filter = (
|
||||
"(&"
|
||||
"(objectclass=nsSlapdPlugin)"
|
||||
"(nsslapd-pluginId=NSUniqueAttr)"
|
||||
"(nsslapd-pluginPath=libattr-unique-plugin)"
|
||||
"(nsslapd-pluginarg0=*)" # only entries with old configuration
|
||||
")"
|
||||
)
|
||||
|
||||
try:
|
||||
entries, _truncated = ldap.find_entries(
|
||||
filter=old_style_plugin_search_filter,
|
||||
base_dn=self.plugins_dn,
|
||||
)
|
||||
except errors.NotFound:
|
||||
logger.debug("No uniqueness plugin entries with old style "
|
||||
"configuration found")
|
||||
return False, []
|
||||
|
||||
update_list = []
|
||||
new_attributes = [
|
||||
'uniqueness-subtree-entries-oc',
|
||||
'uniqueness-top-entry-oc',
|
||||
'uniqueness-attribute-name',
|
||||
'uniqueness-subtrees',
|
||||
'uniqueness-across-all-subtrees',
|
||||
]
|
||||
|
||||
for entry in entries:
|
||||
# test for mixed configuration
|
||||
if any(attr in entry for attr in new_attributes):
|
||||
logger.critical("Mixed old and new style configuration "
|
||||
"for plugin %s. Plugin will not work. "
|
||||
"Skipping plugin migration, please fix it "
|
||||
"manually",
|
||||
entry.dn)
|
||||
continue
|
||||
logger.debug("Configuration of plugin %s will be migrated "
|
||||
"to new style", entry.dn)
|
||||
try:
|
||||
# detect which configuration was used
|
||||
arg0 = entry.get('nsslapd-pluginarg0')
|
||||
if '=' in arg0:
|
||||
update = self.__objectclass_style(entry)
|
||||
else:
|
||||
update = self.__subtree_style(entry)
|
||||
except ValueError as e:
|
||||
logger.error("Unable to migrate configuration of "
|
||||
"plugin %s (%s)",
|
||||
entry.dn, e)
|
||||
else:
|
||||
update_list.append(update)
|
||||
|
||||
return False, update_list
|
||||
120
ipaserver/install/plugins/upload_cacrt.py
Normal file
120
ipaserver/install/plugins/upload_cacrt.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# Authors:
|
||||
# Alexander Bokovoy <abokovoy@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2012 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.install import certstore
|
||||
from ipaplatform.paths import paths
|
||||
from ipaserver.install import certs
|
||||
from ipalib import Registry, errors
|
||||
from ipalib import Updater
|
||||
from ipapython import certdb
|
||||
from ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_upload_cacrt(Updater):
|
||||
"""
|
||||
Upload public CA certificate to LDAP
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
db = certs.CertDB(self.api.env.realm, paths.HTTPD_ALIAS_DIR)
|
||||
ca_cert = None
|
||||
|
||||
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()
|
||||
if server_certs:
|
||||
ca_chain = db.find_root_cert(server_certs[0][0])[:-1]
|
||||
if ca_chain:
|
||||
ca_nickname = ca_chain[-1]
|
||||
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
for nickname, trust_flags in db.list_certs():
|
||||
if trust_flags.has_key:
|
||||
continue
|
||||
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'),
|
||||
('cn','etc'), self.api.env.basedn)
|
||||
entry = ldap.make_entry(dn)
|
||||
|
||||
try:
|
||||
certstore.init_ca_entry(entry, cert, nickname, trust, eku)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to create entry for %s: %s",
|
||||
nickname, e)
|
||||
continue
|
||||
if nickname == ca_nickname:
|
||||
ca_cert = cert
|
||||
config = entry.setdefault('ipaConfigString', [])
|
||||
if ca_enabled:
|
||||
config.append('ipaCa')
|
||||
config.append('ipaCa')
|
||||
|
||||
try:
|
||||
ldap.add_entry(entry)
|
||||
except errors.DuplicateEntry:
|
||||
if nickname == ca_nickname and ca_enabled:
|
||||
try:
|
||||
ldap.update_entry(entry)
|
||||
except errors.EmptyModlist:
|
||||
pass
|
||||
|
||||
if ca_cert:
|
||||
dn = DN(('cn', 'CACert'), ('cn', 'ipa'), ('cn','etc'),
|
||||
self.api.env.basedn)
|
||||
try:
|
||||
entry = ldap.get_entry(dn)
|
||||
except errors.NotFound:
|
||||
entry = ldap.make_entry(dn)
|
||||
entry['objectclass'] = ['nsContainer', 'pkiCA']
|
||||
entry.single_value['cn'] = 'CAcert'
|
||||
entry.single_value['cACertificate;binary'] = ca_cert
|
||||
ldap.add_entry(entry)
|
||||
else:
|
||||
if b'' in entry['cACertificate;binary']:
|
||||
entry.single_value['cACertificate;binary'] = ca_cert
|
||||
ldap.update_entry(entry)
|
||||
|
||||
return False, []
|
||||
Reference in New Issue
Block a user