Imported Upstream version 4.6.2

This commit is contained in:
Mario Fetka
2021-07-25 07:32:41 +02:00
commit 8ff3be4216
1788 changed files with 1900965 additions and 0 deletions

View 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.
"""

View 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, ()

View 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]

View 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, []

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

View 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

View 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, []

View 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, ()

View File

@@ -0,0 +1,84 @@
# Authors:
# Florence Blanc-Renaud <flo@redhat.com>
#
# Copyright (C) 2017 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from ipalib import Registry, errors
from ipalib import Updater
from ipalib.install import certstore
from ipapython.dn import DN
from ipapython.certdb import get_ca_nickname
logger = logging.getLogger(__name__)
register = Registry()
@register()
class update_fix_duplicate_cacrt_in_ldap(Updater):
"""
When multiple entries exist for IPA CA cert in ldap, remove the duplicate
After this plugin, ds needs to be restarted. This ensures that
the attribute uniqueness plugin is working and prevents
other plugins from adding duplicates.
"""
def execute(self, **options):
# If CA is disabled, no need to check for duplicates of IPA CA
ca_enabled = self.api.Command.ca_is_enabled()['result']
if not ca_enabled:
return True, []
# Look for the IPA CA cert subject
ldap = self.api.Backend.ldap2
cacert_subject = certstore.get_ca_subject(
ldap,
self.api.env.container_ca,
self.api.env.basedn)
# Find if there are other certificates with the same subject
# They are duplicates resulting of BZ 1480102
base_dn = DN(('cn', 'certificates'), ('cn', 'ipa'), ('cn', 'etc'),
self.api.env.basedn)
try:
filter = ldap.make_filter({'ipaCertSubject': cacert_subject})
result, _truncated = ldap.find_entries(
base_dn=base_dn,
filter=filter,
attrs_list=[])
except errors.NotFound:
# No duplicate, we're good
logger.debug("No duplicates for IPA CA in LDAP")
return True, []
logger.debug("Found %d entrie(s) for IPA CA in LDAP", len(result))
cacert_dn = DN(('cn', get_ca_nickname(self.api.env.realm)), base_dn)
for entry in result:
if entry.dn == cacert_dn:
continue
# Remove the duplicate
try:
ldap.delete_entry(entry)
logger.debug("Removed the duplicate %s", entry.dn)
except Exception as e:
logger.warning("Failed to remove the duplicate %s: %s",
entry.dn, e)
return True, []

View File

@@ -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, []

View 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, ()

View 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, ()

View 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, ()

View 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, []

View 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]

View 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, []

View 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, []

View 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, []

View 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

View 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, []