Imported Upstream version 4.7.2

This commit is contained in:
Mario Fetka
2021-08-09 20:54:00 +02:00
parent 3bfaa6e020
commit a791de49a2
2175 changed files with 1764288 additions and 331861 deletions

View File

@@ -1,22 +0,0 @@
NULL =
appdir = $(pythondir)/ipaserver/install
app_PYTHON = \
__init__.py \
baseupdate.py \
fix_replica_agreements.py \
rename_managed.py \
dns.py \
updateclient.py \
update_services.py \
update_anonymous_aci.py \
update_pacs.py \
ca_renewal_master.py \
$(NULL)
EXTRA_DIST = \
$(NULL)
MAINTAINERCLEANFILES = \
*~ \
Makefile.in

View File

@@ -20,10 +20,3 @@
"""
Provide a separate api for updates.
"""
PRE_SCHEMA_UPDATE = 0
PRE_UPDATE = 1
POST_UPDATE = 2
FIRST = 1
MIDDLE = 2
LAST = 4

View File

@@ -17,59 +17,69 @@
# 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 ipaserver.install.plugins import MIDDLE
from ipaserver.install.plugins.baseupdate import PostUpdate
from ipalib import api, errors
import logging
from ipalib import Registry, errors
from ipalib import Updater
from ipapython.dn import DN
from ipapython.ipa_log_manager import *
from ipaserver.install import sysupgrade
from ipaserver.install.adtrustinstance import (
ADTRUSTInstance, map_Guests_to_nobody)
logger = logging.getLogger(__name__)
register = Registry()
DEFAULT_ID_RANGE_SIZE = 200000
class update_default_range(PostUpdate):
@register()
class update_default_range(Updater):
"""
Create default ID range for upgraded servers.
"""
order=MIDDLE
def execute(self, **options):
ldap = self.obj.backend
ldap = self.api.Backend.ldap2
dn = DN(api.env.container_ranges, api.env.basedn)
dn = DN(self.api.env.container_ranges, self.api.env.basedn)
search_filter = "objectclass=ipaDomainIDRange"
try:
(entries, truncated) = ldap.find_entries(search_filter, [], dn)
ldap.find_entries(search_filter, [], dn)
except errors.NotFound:
pass
else:
root_logger.debug("default_range: ipaDomainIDRange entry found, skip plugin")
return (False, False, [])
logger.debug("default_range: ipaDomainIDRange entry found, skip "
"plugin")
return False, []
dn = DN(('cn', 'admins'), api.env.container_group, api.env.basedn)
dn = DN(('cn', 'admins'), self.api.env.container_group,
self.api.env.basedn)
try:
admins_entry = ldap.get_entry(dn, ['gidnumber'])
except errors.NotFound:
root_logger.error("default_range: No local ID range and no admins "
"group found. Cannot create default ID range")
return (False, False, [])
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' % api.env.realm
id_range_name = '%s_id_range' % self.api.env.realm
id_range_size = DEFAULT_ID_RANGE_SIZE
range_entry = ['objectclass:top',
'objectclass:ipaIDrange',
'objectclass:ipaDomainIDRange',
'cn:%s' % id_range_name,
'ipabaseid:%s' % id_range_base_id,
'ipaidrangesize:%s' % id_range_size,
'iparangetype:ipa-local',
]
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'),
]
updates = {}
dn = DN(('cn', '%s_id_range' % api.env.realm),
api.env.container_ranges, api.env.basedn)
dn = DN(('cn', '%s_id_range' % self.api.env.realm),
self.api.env.container_ranges, self.api.env.basedn)
updates[dn] = {'dn': dn, 'default': range_entry}
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
@@ -77,14 +87,14 @@ class update_default_range(PostUpdate):
# 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(api.env.container_dna_posix_ids, api.env.basedn)
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)
(entries, _truncated) = ldap.find_entries(search_filter, attrs, dn)
except errors.NotFound:
root_logger.warning("default_range: no dnaSharedConfig object found. "
"Cannot check default range size.")
logger.warning("default_range: no dnaSharedConfig object found. "
"Cannot check default range size.")
else:
masters = set()
remaining_values_sum = 0
@@ -96,8 +106,9 @@ class update_default_range(PostUpdate):
try:
remaining_values = int(remaining_values)
except ValueError:
root_logger.warning("default_range: could not parse "
"remaining values from '%s'", remaining_values)
logger.warning("default_range: could not parse "
"remaining values from '%s'",
remaining_values)
continue
else:
remaining_values_sum += remaining_values
@@ -113,8 +124,279 @@ class update_default_range(PostUpdate):
' RANGE_SIZE = (--idmax) - (--idstart) + 1'
]
root_logger.error("default_range: %s", "\n".join(msg))
logger.error("default_range: %s", "\n".join(msg))
return (False, True, [updates])
return False, [update]
api.register(update_default_range)
@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, ()
@register()
class update_mapping_Guests_to_nobody(Updater):
"""
Map BUILTIN\\Guests group to nobody
Samba 4.9 became more strict on availability of builtin Guests group
"""
def execute(self, **options):
# 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, []
map_Guests_to_nobody()
return False, []

View File

@@ -1,89 +0,0 @@
# 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/>.
from ipalib import api
from ipalib import Updater, Object
from ipaserver.install import service
from ipaserver.install.plugins import (PRE_UPDATE, POST_UPDATE,
PRE_SCHEMA_UPDATE, MIDDLE)
class DSRestart(service.Service):
"""
Restart the 389-ds service.
"""
def __init__(self):
"""
This class is present to provide ldapupdate the means to
restart 389-ds.
"""
service.Service.__init__(self, "dirsrv")
def start(self, instance_name="", capture_output=True, wait=True):
"""
During upgrades the server is listening only on the socket so
we don't want to wait on ports. The caller is responsible for
waiting for the socket to be ready.
"""
super(DSRestart, self).start(wait=False)
def create_instance(self):
self.step("stopping directory server", self.stop)
self.step("starting directory server", self.start)
self.start_creation(start_message="Restarting Directory server "
"to apply updates", show_service_name=False)
class update(Object):
"""
Generic object used to register all updates into a single namespace.
"""
backend_name = 'ldap2'
api.register(update)
class PreSchemaUpdate(Updater):
"""
Base class for updates that run after file processing.
"""
updatetype = PRE_SCHEMA_UPDATE
order = MIDDLE
def __init__(self):
super(PreSchemaUpdate, self).__init__()
class PreUpdate(Updater):
"""
Base class for updates that run prior to file processing.
"""
updatetype = PRE_UPDATE
order = MIDDLE
def __init__(self):
super(PreUpdate, self).__init__()
class PostUpdate(Updater):
"""
Base class for updates that run after file processing.
"""
updatetype = POST_UPDATE
order = MIDDLE
def __init__(self):
super(PostUpdate, self).__init__()

View File

@@ -17,31 +17,39 @@
# 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 ipaserver.install.plugins.baseupdate import PostUpdate
from ipaserver.install import installutils, certs, cainstance
from __future__ import absolute_import
import logging
from ipaserver.install import cainstance
from ipalib import errors
from ipalib import Updater
from ipalib.install import certmonger
from ipalib.plugable import Registry
from ipapython import certmonger, dogtag
from ipaplatform.paths import paths
from ipapython.dn import DN
from ipapython import directivesetter
logger = logging.getLogger(__name__)
register = Registry()
@register()
class update_ca_renewal_master(PostUpdate):
class update_ca_renewal_master(Updater):
"""
Set CA renewal master in LDAP.
"""
def execute(self, **options):
ca = cainstance.CAInstance(self.api.env.realm, certs.NSS_DIR)
ca = cainstance.CAInstance(self.api.env.realm)
if not ca.is_configured():
self.debug("CA is not configured on this host")
return (False, False, [])
logger.debug("CA is not configured on this host")
return False, []
ldap = self.obj.backend
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,
@@ -49,59 +57,77 @@ class update_ca_renewal_master(PostUpdate):
except errors.NotFound:
pass
else:
self.debug("found CA renewal master %s", entries[0].dn[1].value)
return (False, False, [])
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-database': paths.HTTPD_ALIAS_DIR,
'cert-nickname': 'ipaCert',
'cert-file': paths.RA_AGENT_PEM,
}
request_id = certmonger.get_request_id(criteria)
if request_id is not None:
self.debug("found certmonger request for ipaCert")
logger.debug("found certmonger request for RA cert")
ca_name = certmonger.get_request_value(request_id, 'ca-name')
if ca_name is None:
self.warning(
"certmonger request for ipaCert is missing ca_name, "
logger.warning(
"certmonger request for RA cert is missing ca_name, "
"assuming local CA is renewal slave")
return (False, False, [])
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, False, [])
return False, []
elif ca_name == 'dogtag-ipa-ca-renew-agent':
return (False, False, [])
return False, []
else:
self.warning(
"certmonger request for ipaCert has unknown ca_name '%s', "
logger.warning(
"certmonger request for RA cert has unknown ca_name '%s', "
"assuming local CA is renewal slave", ca_name)
return (False, False, [])
return False, []
else:
self.debug("certmonger request for ipaCert not found")
logger.debug("certmonger request for RA cert not found")
config = installutils.get_directive(
dogtag.configured_constants().CS_CFG_PATH,
'subsystem.select', '=')
config = directivesetter.get_directive(
paths.CA_CS_CFG_PATH, 'subsystem.select', '=')
if config == 'New':
pass
elif config == 'Clone':
return (False, False, [])
return False, []
else:
self.warning(
logger.warning(
"CS.cfg has unknown subsystem.select value '%s', "
"assuming local CA is renewal slave", config)
return (False, False, [])
dn = DN(('cn', 'CA'), ('cn', self.api.env.host), base_dn)
update = {
dn: {
'dn': dn,
'updates': ['add:ipaConfigString: caRenewalMaster'],
},
'updates': [
dict(action='add', attr='ipaConfigString',
value='caRenewalMaster')
],
}
return (False, True, [update])
return False, [update]

View File

@@ -17,23 +17,149 @@
# 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 ldap as _ldap
from __future__ import absolute_import
import logging
import dns.exception
import re
import traceback
import time
from ldif import LDIFWriter
from ipaserver.install.plugins import MIDDLE, LAST
from ipaserver.install.plugins.baseupdate import (PostUpdate, PreUpdate,
PreSchemaUpdate)
from ipaserver.install import sysupgrade
from ipalib import api, errors, util
from ipalib import Registry, errors, util
from ipalib import Updater
from ipapython.dn import DN
from ipalib.plugins.dns import dns_container_exists
from ipapython.ipa_log_manager import *
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 update_dnszones(PostUpdate):
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(r"^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
@@ -57,18 +183,17 @@ class update_dnszones(PostUpdate):
This module extends the original policy to allow the SSHFP updates.
"""
order=MIDDLE
def execute(self, **options):
ldap = self.obj.backend
ldap = self.api.Backend.ldap2
if not dns_container_exists(ldap):
return (False, False, [])
return False, []
try:
zones = api.Command.dnszone_find(all=True)['result']
zones = self.api.Command.dnszone_find(all=True)['result']
except errors.NotFound:
self.log.info('No DNS zone to update found')
return (False, False, [])
logger.debug('No DNS zone to update found')
return False, []
for zone in zones:
update = {}
@@ -80,20 +205,22 @@ class update_dnszones(PostUpdate):
# do not open zone transfers by default
update['idnsallowtransfer'] = u'none;'
old_policy = util.get_dns_forward_zone_update_policy(api.env.realm, ('A', 'AAAA'))
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(\
api.env.realm)
self.api.env.realm)
if update:
api.Command.dnszone_mod(zone[u'idnsname'][0], **update)
# FIXME: https://fedorahosted.org/freeipa/ticket/4722
self.api.Command.dnszone_mod(zone[u'idnsname'][0].make_absolute(),
**update)
return (False, False, [])
api.register(update_dnszones)
return False, []
class update_dns_limits(PostUpdate):
@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
@@ -104,10 +231,10 @@ class update_dns_limits(PostUpdate):
limit_value = '-1'
def execute(self, **options):
ldap = self.obj.backend
ldap = self.api.Backend.ldap2
if not dns_container_exists(ldap):
return (False, False, [])
return False, []
dns_principal = 'DNS/%s@%s' % (self.env.host, self.env.realm)
dns_service_dn = DN(('krbprincipalname', dns_principal),
@@ -118,57 +245,32 @@ class update_dns_limits(PostUpdate):
entry = ldap.get_entry(dns_service_dn, self.limit_attributes)
except errors.NotFound:
# this host may not have DNS service set
root_logger.debug("DNS: service %s not found, no need to update limits" % dns_service_dn)
return (False, False, [])
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):
root_logger.debug("DNS: limits for service %s already set" % dns_service_dn)
logger.debug("DNS: limits for service %s already set",
dns_service_dn)
# service is already updated
return (False, False, [])
return False, []
limit_updates = []
for limit in self.limit_attributes:
limit_updates.append('only:%s:%s' % (limit, self.limit_value))
limit_updates.append(dict(action='only', attr=limit,
value=self.limit_value))
dnsupdates = {}
dnsupdates[dns_service_dn] = {'dn': dns_service_dn,
'updates': limit_updates}
root_logger.debug("DNS: limits for service %s will be updated" % dns_service_dn)
dnsupdate = {'dn': dns_service_dn, 'updates': limit_updates}
logger.debug("DNS: limits for service %s will be updated",
dns_service_dn)
return (False, True, [dnsupdates])
api.register(update_dns_limits)
return False, [dnsupdate]
class update_check_forwardzones(PreSchemaUpdate):
"""
Check if the idnsforwardzone objectclass is in LDAP schema.
If not update is required (update_to_forward_zones), set sysupgrade state
'update_to_forward_zones' to True
"""
def execute(self, **options):
state = sysupgrade.get_upgrade_state('dns', 'update_to_forward_zones')
if state is False:
# no upgrade is needed
return (False, False, [])
ldap = self.obj.backend
if not dns_container_exists(ldap): # No DNS installed
return (False, False, [])
result = ldap.schema.get_obj(_ldap.schema.models.ObjectClass, 'idnsforwardzone')
if result is None:
sysupgrade.set_upgrade_state('dns', 'update_to_forward_zones', True)
self.log.info('Prepared upgrade to forward zones')
else:
sysupgrade.set_upgrade_state('dns', 'update_to_forward_zones', False)
return (False, False, [])
api.register(update_check_forwardzones)
class update_master_to_dnsforwardzones(PostUpdate):
@register()
class update_master_to_dnsforwardzones(DNSUpdater):
"""
Update all zones to meet requirements in the new FreeIPA versions
@@ -176,28 +278,47 @@ class update_master_to_dnsforwardzones(PostUpdate):
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
This should be applied only once,
and only if original version was lower than 4.0
"""
order = LAST
backup_dir = u'/var/lib/ipa/backup/'
backup_filename = u'dns-forward-zones-backup-%Y-%m-%d-%H-%M-%S.ldif'
backup_path = u'%s%s' % (backup_dir, backup_filename)
backup_filename = u'dns-master-to-forward-zones-%Y-%m-%d-%H-%M-%S.ldif'
def execute(self, **options):
ldap = self.obj.backend
if not sysupgrade.get_upgrade_state('dns', 'update_to_forward_zones'):
# forward zones was tranformed before, nothing to do
return (False, False, [])
# 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 = api.Command.dnszone_find(all=True,
zones = self.api.Command.dnszone_find(all=True,
raw=True,
sizelimit=0)['result']
except errors.NotFound:
self.log.info('No DNS zone to update found')
return (False, False, [])
pass
if not zones:
logger.debug('No DNS zone to update found')
return False, []
zones_to_transform = []
@@ -211,141 +332,222 @@ class update_master_to_dnsforwardzones(PostUpdate):
zones_to_transform.append(zone)
if zones_to_transform:
# add time to filename
self.backup_path = time.strftime(self.backup_path)
# DNs of privileges which contain dns managed permissions
privileges_to_ldif = set() # store priviledges only once
zone_to_privileges = {} # zone: [privileges cn]
self.log.info('Zones with specified forwarders with policy different'
' than none will be transformed to forward zones.')
self.log.info('Original zones will be saved in LDIF format in '
'%s file' % self.backup_path)
try:
with open(self.backup_path, 'w') as f:
writer = LDIFWriter(f)
for zone in zones_to_transform:
# save backup to ldif
try:
dn = str(zone['dn'])
del zone['dn'] # dn shouldn't be as attribute in ldif
writer.unparse(dn, zone)
if 'managedBy' in zone:
entry = ldap.get_entry(DN(zone['managedBy'][0]))
for privilege_member_dn in entry.get('member', []):
privileges_to_ldif.add(privilege_member_dn)
writer.unparse(str(entry.dn), dict(entry.raw))
# privileges where permission is used
if entry.get('member'):
zone_to_privileges[zone['idnsname'][0]] = entry['member']
# raw values are required to store into ldif
records = 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']
writer.unparse(dn, record)
except Exception, e:
self.log.error('Unable to backup zone %s' %
zone['idnsname'][0])
self.log.error(traceback.format_exc())
return (False, False, [])
for privilege_dn in privileges_to_ldif:
try:
entry = ldap.get_entry(privilege_dn)
writer.unparse(str(entry.dn), dict(entry.raw))
except Exception, e:
self.log.error('Unable to backup privilege %s' %
privilege_dn)
self.log.error(traceback.format_exc())
return (False, False, [])
f.close()
except Exception:
self.log.error('Unable to create backup file')
self.log.error(traceback.format_exc())
return (False, False, [])
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:
api.Command['dnszone_del'](zone['idnsname'])
except Exception, e:
self.log.error('Transform to forwardzone terminated: '
'removing zone %s failed (%s)' % (
zone['idnsname'][0], e)
)
self.log.error(traceback.format_exc())
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]
'idnsforwardpolicy': zone.get('idnsforwardpolicy',
[u'first'])[0],
'skip_overlap_check': True,
}
api.Command['dnsforwardzone_add'](zone['idnsname'][0], **kw)
except Exception, e:
self.log.error('Transform to forwardzone terminated: creating '
'forwardzone %s failed' %
zone['idnsname'][0])
self.log.error(traceback.format_exc())
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 = api.Command['dnsforwardzone_add_permission'](
perm_name = self.api.Command['dnsforwardzone_add_permission'](
zone['idnsname'][0])['value']
except Exception, e:
self.log.error('Transform to forwardzone terminated: '
'Adding managed by permission to forward zone'
' %s failed' % zone['idnsname'])
self.log.error(traceback.format_exc())
self.log.info('Zone %s was transformed to forward zone '
' without managed permissions',
zone['idnsname'][0])
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 zone_to_privileges:
if zone['idnsname'][0] in self.saved_zone_to_privilege:
privileges = [
dn[0].value for dn in zone_to_privileges[zone['idnsname'][0]]
dn[0].value for dn in self.saved_zone_to_privilege[zone['idnsname'][0]]
]
try:
api.Command['permission_add_member'](perm_name,
self.api.Command['permission_add_member'](perm_name,
privilege=privileges)
except Exception, e:
self.log.error('Unable to restore privileges for '
'permission %s, for zone %s'
% (perm_name, zone['idnsname']))
self.log.error(traceback.format_exc())
self.log.info('Zone %s was transformed to forward zone'
' without restored privileges',
zone['idnsname'][0])
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
self.log.info('Zone %s was sucessfully transformed to forward zone',
zone['idnsname'][0])
logger.debug('Zone %s was sucessfully transformed to forward '
'zone',
zone['idnsname'][0])
return False, []
sysupgrade.set_upgrade_state('dns', 'update_to_forward_zones', False)
@register()
class update_dnsforward_emptyzones(DNSUpdater):
"""
Migrate forward policies which conflict with automatic empty zones
(RFC 6303) to use forward policy = only.
return (False, False, [])
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'
api.register(update_master_to_dnsforwardzones)
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

@@ -17,42 +17,44 @@
# 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 os
import pwd
from ipapython import ipaldap
from ipaserver.install.plugins import MIDDLE
from ipaserver.install.plugins.baseupdate import PreUpdate
import logging
from ipaserver.install import replication
from ipalib import api
from ipalib import Registry
from ipalib import Updater
logger = logging.getLogger(__name__)
register = Registry()
EXCLUDE_TEMPLATE = '(objectclass=*) $ EXCLUDE %s'
class update_replica_attribute_lists(PreUpdate):
@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.
"""
order = MIDDLE
def execute(self, **options):
# We need an IPAdmin connection to the backend
self.log.debug("Start replication agreement exclude list update task")
conn = ipaldap.IPAdmin(api.env.host, ldapi=True, realm=api.env.realm)
conn.do_external_bind(pwd.getpwuid(os.geteuid()).pw_name)
# 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(api.env.realm, api.env.host,
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()
self.log.debug("Found %d agreement(s)", len(ipa_replicas))
logger.debug("Found %d agreement(s)", len(ipa_replicas))
for replica in ipa_replicas:
self.log.debug(replica.single_value.get('description'))
for desc in replica.get('description', []):
logger.debug('%s', desc)
self._update_attr(repl, replica,
'nsDS5ReplicatedAttributeList',
@@ -63,9 +65,9 @@ class update_replica_attribute_lists(PreUpdate):
self._update_attr(repl, replica,
'nsds5ReplicaStripAttrs', replication.STRIP_ATTRS)
self.log.debug("Done updating agreements")
logger.debug("Done updating agreements")
return (False, False, []) # No restart, no apply now, no updates
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
@@ -83,16 +85,16 @@ class update_replica_attribute_lists(PreUpdate):
"""
attrlist = replica.single_value.get(attribute)
if attrlist is None:
self.log.debug("Adding %s", attribute)
logger.debug("Adding %s", attribute)
# Need to add it altogether
replica[attribute] = [template % " ".join(values)]
try:
repl.conn.update_entry(replica)
self.log.debug("Updated")
except Exception, e:
self.log.error("Error caught updating replica: %s", str(e))
logger.debug("Updated")
except Exception as e:
logger.error("Error caught updating replica: %s", str(e))
else:
attrlist_normalized = attrlist.lower().split()
@@ -100,19 +102,17 @@ class update_replica_attribute_lists(PreUpdate):
if a.lower() not in attrlist_normalized]
if missing:
self.log.debug("%s needs updating (missing: %s)", attribute,
', '.join(missing))
logger.debug("%s needs updating (missing: %s)", attribute,
', '.join(missing))
replica[attribute] = [
'%s %s' % (attrlist, ' '.join(missing))]
try:
repl.conn.update_entry(replica)
self.log.debug("Updated %s", attribute)
except Exception, e:
self.log.error("Error caught updating %s: %s",
attribute, str(e))
logger.debug("Updated %s", attribute)
except Exception as e:
logger.error("Error caught updating %s: %s",
attribute, str(e))
else:
self.log.debug("%s: No update necessary" % attribute)
api.register(update_replica_attribute_lists)
logger.debug("%s: No update necessary", attribute)

View File

@@ -17,11 +17,22 @@
# 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 ipaserver.install.plugins import FIRST, LAST
from ipaserver.install.plugins.baseupdate import PreUpdate, PostUpdate
from ipalib import api, errors
import logging
import six
from ipalib import Registry, errors
from ipalib import Updater
from ipapython import ipautil
from ipapython.dn import DN, EditableDN
from ipapython.dn import DN
logger = logging.getLogger(__name__)
register = Registry()
if six.PY3:
unicode = str
def entry_to_update(entry):
"""
@@ -34,22 +45,34 @@ def entry_to_update(entry):
update = []
for attr in entry.keys():
if isinstance(entry[attr], list):
for i in xrange(len(entry[attr])):
update.append('%s:%s' % (str(attr), str(entry[attr][i])))
for item in entry[attr]:
update.append(dict(attr=str(attr), value=str(item)))
else:
update.append('%s:%s' % (str(attr), str(entry[attr])))
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.obj.backend
ldap = self.api.Backend.ldap2
suffix = ipautil.realm_to_suffix(api.env.realm)
suffix = ipautil.realm_to_suffix(self.api.env.realm)
searchfilter = '(objectclass=*)'
definitions_managed_entries = []
@@ -59,16 +82,15 @@ class GenerateUpdateMixin(object):
old_definition_container = DN(('cn', 'managed entries'), ('cn', 'plugins'), ('cn', 'config'), suffix)
new_definition_container = DN(('cn', 'Definitions'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix)
definitions_dn = DN(('cn', 'Definitions'))
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(
definitions_managed_entries, _truncated = ldap.find_entries(
searchfilter, ['*'], old_definition_container,
ldap.SCOPE_ONELEVEL)
except errors.NotFound, e:
except errors.NotFound:
return (False, update_list)
for entry in definitions_managed_entries:
@@ -78,18 +100,18 @@ class GenerateUpdateMixin(object):
assert isinstance(old_dn, DN)
try:
entry = ldap.get_entry(old_dn, ['*'])
except errors.NotFound, e:
except errors.NotFound:
pass
else:
# Compute the new dn by replacing the old container with the new container
new_dn = EditableDN(entry.dn)
if new_dn.replace(old_template_container, new_template_container) != 1:
self.error("unable to replace '%s' with '%s' in '%s'",
old_template_container, new_template_container, entry.dn)
try:
new_dn = self._dn_suffix_replace(
entry.dn,
old_suffix=old_template_container,
new_suffix=new_template_container)
except ValueError:
continue
new_dn = DN(new_dn)
# The old attributes become defaults for the new entry
new_update = {'dn': new_dn,
'default': entry_to_update(entry)}
@@ -98,64 +120,63 @@ class GenerateUpdateMixin(object):
old_update = {'dn': entry.dn, 'deleteentry': None}
# Add the delete and replacement updates to the list of all updates
update_list.append({entry.dn: old_update, new_dn: new_update})
update_list.append(old_update)
update_list.append(new_update)
else:
# Update the template dn by replacing the old containter with the new container
old_dn = entry['managedtemplate'][0]
new_dn = EditableDN(old_dn)
if new_dn.replace(old_template_container, new_template_container) != 1:
self.error("unable to replace '%s' with '%s' in '%s'",
old_template_container, new_template_container, old_dn)
try:
new_dn = self._dn_suffix_replace(
entry['managedtemplate'][0],
old_suffix=old_template_container,
new_suffix=new_template_container)
except ValueError:
continue
new_dn = DN(new_dn)
entry['managedtemplate'] = new_dn
# Edit the dn, then convert it back to an immutable DN
old_dn = entry.dn
new_dn = EditableDN(old_dn)
if new_dn.replace(old_definition_container, new_definition_container) != 1:
self.error("unable to replace '%s' with '%s' in '%s'",
old_definition_container, new_definition_container, old_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
new_dn = DN(new_dn)
# 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_dn: new_update})
update_list.append(new_update)
if len(update_list) > 0:
restart = True
update_list.sort(reverse=True)
update_list.sort(reverse=True, key=lambda x: x['dn'])
return (restart, update_list)
class update_managed_post_first(PreUpdate, GenerateUpdateMixin):
@register()
class update_managed_post_first(Updater, GenerateUpdateMixin):
"""
Update managed entries
"""
order=FIRST
def execute(self, **options):
# Never need to restart with the pre-update changes
(ignore, update_list) = self.generate_update(False)
_ignore, update_list = self.generate_update(False)
return (False, True, update_list)
return False, update_list
api.register(update_managed_post_first)
class update_managed_post(PostUpdate, GenerateUpdateMixin):
@register()
class update_managed_post(Updater, GenerateUpdateMixin):
"""
Update managed entries
"""
order=LAST
def execute(self, **options):
(restart, update_list) = self.generate_update(True)
return (restart, True, update_list)
api.register(update_managed_post)
return restart, update_list

View File

@@ -0,0 +1,62 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
from __future__ import absolute_import
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

@@ -17,28 +17,31 @@
# 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 ipaserver.install.plugins import MIDDLE
from ipaserver.install.plugins.baseupdate import PostUpdate
from ipalib import api, errors
import logging
from ipalib import Registry, errors
from ipalib import Updater
from ipapython.dn import DN
from ipapython.ipa_log_manager import *
logger = logging.getLogger(__name__)
register = Registry()
class update_idrange_type(PostUpdate):
@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.
"""
order = MIDDLE
def execute(self, **options):
ldap = self.obj.backend
ldap = self.api.Backend.ldap2
base_dn = DN(api.env.container_ranges, api.env.basedn)
base_dn = DN(self.api.env.container_ranges, self.api.env.basedn)
search_filter = ("(&(objectClass=ipaIDrange)(!(ipaRangeType=*)))")
root_logger.debug("update_idrange_type: search for ID ranges with no "
"type set")
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
@@ -49,24 +52,23 @@ class update_idrange_type(PostUpdate):
['objectclass'], base_dn, time_limit=0, size_limit=0)
except errors.NotFound:
root_logger.debug("update_idrange_type: no ID range without "
"type set found")
return (False, False, [])
logger.debug("update_idrange_type: no ID range without "
"type set found")
return False, []
except errors.ExecutionError, e:
root_logger.error("update_idrange_type: cannot retrieve list "
"of ranges with no type set: %s", e)
return (False, 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
root_logger.debug("update_idrange_type: no ID range was "
"returned")
return (False, False, [])
logger.debug("update_idrange_type: no ID range was returned")
return False, []
root_logger.debug("update_idrange_type: found %d "
"idranges to update, truncated: %s",
len(entries), truncated)
logger.debug("update_idrange_type: found %d "
"idranges to update, truncated: %s",
len(entries), truncated)
error = False
@@ -83,32 +85,95 @@ class update_idrange_type(PostUpdate):
entry['ipaRangeType'] = ['ipa-local']
else:
entry['ipaRangeType'] = ['unknown']
root_logger.error("update_idrange_type: could not detect "
"range type for entry: %s" % str(entry.dn))
root_logger.error("update_idrange_type: ID range type set "
"to 'unknown' for entry: %s" % str(entry.dn))
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, e:
root_logger.debug("update_idrange_type: cannot "
"update idrange type: %s", e)
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
root_logger.error("update_idrange_type: error(s) "
"detected during idrange type update")
return (False, False, [])
logger.error("update_idrange_type: error(s) "
"detected during idrange type update")
return False, []
elif not truncated:
# All affected entries updated, exit the loop
root_logger.debug("update_idrange_type: all affected idranges "
"were assigned types")
return (False, False, [])
logger.debug("update_idrange_type: all affected idranges "
"were assigned types")
return False, []
return (False, False, [])
return False, []
api.register(update_idrange_type)
@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

@@ -40,13 +40,13 @@ 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, objectclass
* 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 and ipapermtarget must be DNs
- ipapermlocation, ipatargetto, ipapermtargetfrom, ipapermtarget must be DNs
- ipapermtargetfilter and objectclass must be iterables of strings
* ipapermbindruletype
- Directly used as attribute on the permission.
@@ -83,17 +83,23 @@ 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.plugins import aci
from ipalib.plugins.permission import permission, permission_del
from ipalib.aci import ACI
from ipalib import Updater
from ipapython import ipautil
from ipaserver.plugins.ldap2 import ldap2
from ipaserver.install.plugins import LAST
from ipaserver.install.plugins.baseupdate import PostUpdate
from ipaserver.plugins import aci
from ipaserver.plugins.permission import permission, permission_del
if six.PY3:
unicode = str
logger = logging.getLogger(__name__)
register = Registry()
@@ -117,6 +123,17 @@ NONOBJECT_PERMISSIONS = {
},
'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),
@@ -139,6 +156,29 @@ NONOBJECT_PERMISSIONS = {
'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),
@@ -150,6 +190,52 @@ NONOBJECT_PERMISSIONS = {
'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),
@@ -180,67 +266,35 @@ NONOBJECT_PERMISSIONS = {
'ipantdomainguid', 'ipantfallbackprimarygroup',
},
},
'System: Read Replication Agreements': {
'ipapermlocation': DN('cn=config'),
'System: Read DUA Profile': {
'ipapermlocation': DN('ou=profile', api.env.basedn),
'ipapermtargetfilter': {
'(|'
'(objectclass=nsds5Replica)'
'(objectclass=nsds5replicationagreement)'
'(objectclass=nsDSWindowsReplicationAgreement)'
'(objectClass=nsMappingTree)'
'(objectclass=organizationalUnit)'
'(objectclass=DUAConfigProfile)'
')'
},
'ipapermbindruletype': 'permission',
'ipapermbindruletype': 'anonymous',
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'cn', 'objectclass',
# nsds5Replica
'nsds5replicaroot', 'nsds5replicaid', 'nsds5replicacleanruv',
'nsds5replicaabortcleanruv', 'nsds5replicatype',
'nsds5replicabinddn', 'nsstate', 'nsds5replicaname',
'nsds5flags', 'nsds5task', 'nsds5replicareferral',
'nsds5replicaautoreferral', 'nsds5replicapurgedelay',
'nsds5replicatombstonepurgeinterval', 'nsds5replicachangecount',
'nsds5replicalegacyconsumer', 'nsds5replicaprotocoltimeout',
'nsds5replicabackoffmin', 'nsds5replicabackoffmax',
# nsds5replicationagreement
'nsds5replicacleanruvnotified', 'nsds5replicahost',
'nsds5replicaport', 'nsds5replicatransportinfo',
'nsds5replicabinddn', 'nsds5replicacredentials',
'nsds5replicabindmethod', 'nsds5replicaroot',
'nsds5replicatedattributelist',
'nsds5replicatedattributelisttotal', 'nsds5replicaupdateschedule',
'nsds5beginreplicarefresh', 'description', 'nsds50ruv',
'nsruvreplicalastmodified', 'nsds5replicatimeout',
'nsds5replicachangessentsincestartup', 'nsds5replicalastupdateend',
'nsds5replicalastupdatestart', 'nsds5replicalastupdatestatus',
'nsds5replicaupdateinprogress', 'nsds5replicalastinitend',
'nsds5replicaenabled', 'nsds5replicalastinitstart',
'nsds5replicalastinitstatus', 'nsds5debugreplicatimeout',
'nsds5replicabusywaittime', 'nsds5replicastripattrs',
'nsds5replicasessionpausetime', 'nsds5replicaprotocoltimeout',
# nsDSWindowsReplicationAgreement
'nsds5replicahost', 'nsds5replicaport',
'nsds5replicatransportinfo', 'nsds5replicabinddn',
'nsds5replicacredentials', 'nsds5replicabindmethod',
'nsds5replicaroot', 'nsds5replicatedattributelist',
'nsds5replicaupdateschedule', 'nsds5beginreplicarefresh',
'description', 'nsds50ruv', 'nsruvreplicalastmodified',
'nsds5replicatimeout', 'nsds5replicachangessentsincestartup',
'nsds5replicalastupdateend', 'nsds5replicalastupdatestart',
'nsds5replicalastupdatestatus', 'nsds5replicaupdateinprogress',
'nsds5replicalastinitend', 'nsds5replicalastinitstart',
'nsds5replicalastinitstatus', 'nsds5debugreplicatimeout',
'nsds5replicabusywaittime', 'nsds5replicasessionpausetime',
'nsds7windowsreplicasubtree', 'nsds7directoryreplicasubtree',
'nsds7newwinusersyncenabled', 'nsds7newwingroupsyncenabled',
'nsds7windowsdomain', 'nsds7dirsynccookie', 'winsyncinterval',
'onewaysync', 'winsyncmoveaction', 'nsds5replicaenabled',
'winsyncdirectoryfilter', 'winsyncwindowsfilter',
'winsyncsubtreepair',
'objectclass', 'ou', 'cn', 'defaultServerList',
'preferredServerList', 'defaultSearchBase', 'defaultSearchScope',
'searchTimeLimit', 'bindTimeLimit', 'credentialLevel',
'authenticationMethod', 'followReferrals', 'dereferenceAliases',
'serviceSearchDescriptor', 'serviceCredentialLevel',
'serviceAuthenticationMethod', 'objectclassMap', 'attributeMap',
'profileTTL'
},
'default_privileges': {'Replication Administrators'},
}
},
'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',
},
},
}
@@ -249,14 +303,13 @@ class IncompatibleACIModification(Exception):
@register()
class update_managed_permissions(PostUpdate):
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.
"""
order = LAST
def get_anonymous_read_aci(self, ldap):
aciname = u'Enable Anonymous access'
@@ -278,7 +331,7 @@ class update_managed_permissions(PostUpdate):
for acistr in acistrs:
if ACI(acistr).isequal(anonymous_read_aci):
self.log.info('Removing anonymous ACI: %s', acistr)
logger.debug('Removing anonymous ACI: %s', acistr)
acistrs.remove(acistr)
break
else:
@@ -294,31 +347,31 @@ class update_managed_permissions(PostUpdate):
"""
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.iteritems()):
for name, template in sorted(managed_permissions.items()):
yield name, template, obj
for name, template in sorted(NONOBJECT_PERMISSIONS.iteritems()):
for name, template in sorted(NONOBJECT_PERMISSIONS.items()):
yield name, template, None
def execute(self, **options):
ldap = self.api.Backend[ldap2]
ldap = self.api.Backend.ldap2
anonymous_read_aci = self.get_anonymous_read_aci(ldap)
if anonymous_read_aci:
self.log.info('Anonymous read ACI: %s', anonymous_read_aci)
logger.debug('Anonymous read ACI: %s', anonymous_read_aci)
else:
self.log.info('Anonymous ACI not found')
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:
self.log.info('Updating managed permissions for %s',
obj.name)
logger.debug('Updating managed permissions for %s',
obj.name)
else:
self.log.info('Updating non-object managed permissions')
logger.debug('Updating non-object managed permissions')
current_obj = obj
self.update_permission(ldap,
@@ -331,17 +384,17 @@ class update_managed_permissions(PostUpdate):
self.remove_anonymous_read_aci(ldap, anonymous_read_aci)
for obsolete_name in OBSOLETE_PERMISSIONS:
self.log.debug('Deleting obsolete permission %s', obsolete_name)
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:
self.log.debug('Obsolete permission not found')
logger.debug('Obsolete permission not found')
else:
self.log.info('Obsolete permission deleted: %s', obsolete_name)
logger.debug('Obsolete permission deleted: %s', obsolete_name)
return False, False, ()
return False, ()
def update_permission(self, ldap, obj, name, template, anonymous_read_aci):
"""Update the given permission and the corresponding ACI"""
@@ -380,58 +433,58 @@ class update_managed_permissions(PostUpdate):
legacy_entry = ldap.get_entry(legacy_dn,
['ipapermissiontype', 'cn'])
except errors.NotFound:
self.log.debug("Legacy permission %s not found", legacy_name)
logger.debug("Legacy permission %s not found", legacy_name)
else:
if 'ipapermissiontype' not in legacy_entry:
if is_new:
acientry, acistr = (
_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:
self.log.error(
logger.error(
"Permission '%s' has been modified from its "
"default; not updating it to '%s'.",
legacy_name, name)
return
else:
self.log.debug("Merging attributes from legacy "
"permission '%s'", legacy_name)
self.log.debug("Included attrs: %s",
', '.join(sorted(included)))
self.log.debug("Excluded attrs: %s",
', '.join(sorted(excluded)))
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:
self.log.debug("Ignoring attributes in legacy "
"permission '%s' because '%s' exists",
legacy_name, name)
logger.debug("Ignoring attributes in legacy "
"permission '%s' because '%s' exists",
legacy_name, name)
remove_legacy = True
else:
self.log.debug("Ignoring V2 permission named '%s'" %
legacy_name)
logger.debug("Ignoring V2 permission named '%s'",
legacy_name)
update_aci = True
self.log.debug('Updating managed permission: %s', name)
logger.debug('Updating managed permission: %s', name)
if is_new:
ldap.add_entry(entry)
else:
try:
ldap.update_entry(entry)
except errors.EmptyModlist:
self.log.debug('No changes to permission: %s', name)
logger.debug('No changes to permission: %s', name)
update_aci = False
if update_aci:
self.log.debug('Updating ACI for managed permission: %s', name)
logger.debug('Updating ACI for managed permission: %s', name)
permission_plugin.update_aci(entry)
if remove_legacy:
self.log.info("Removing legacy permission '%s'", legacy_name)
logger.debug("Removing legacy permission '%s'", legacy_name)
self.api.Command[permission_del](unicode(legacy_name))
for name in template.get('replaces_system', ()):
@@ -440,14 +493,14 @@ class update_managed_permissions(PostUpdate):
entry = ldap.get_entry(permission_plugin.get_dn(name),
['ipapermissiontype'])
except errors.NotFound:
self.log.info("Legacy permission '%s' not found", name)
logger.debug("Legacy permission '%s' not found", name)
else:
flags = entry.get('ipapermissiontype', [])
if list(flags) == ['SYSTEM']:
self.log.info("Removing legacy permission '%s'", name)
logger.debug("Removing legacy permission '%s'", name)
self.api.Command[permission_del](name, force=True)
else:
self.log.info("Ignoring V2 permission '%s'", name)
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
@@ -492,8 +545,8 @@ class update_managed_permissions(PostUpdate):
current_aci = ACI(current_acistring)
current_attrs = _pop_targetattr(current_aci)
self.log.debug("Current ACI for '%s': %s",
current_aci.name, current_acistring)
logger.debug("Current ACI for '%s': %s",
current_aci.name, current_acistring)
attrs_in_all_defaults = None
attrs_in_any_defaults = set()
@@ -501,11 +554,11 @@ class update_managed_permissions(PostUpdate):
for default_acistring in default_acistrings:
default_aci = ACI(default_acistring)
default_attrs = _pop_targetattr(default_aci)
self.log.debug("Default ACI for '%s': %s",
default_aci.name, default_acistring)
logger.debug("Default ACI for '%s': %s",
default_aci.name, default_acistring)
if current_aci != default_aci:
self.log.debug('ACIs not compatible')
logger.debug('ACIs not compatible')
continue
else:
all_incompatible = False
@@ -517,7 +570,7 @@ class update_managed_permissions(PostUpdate):
attrs_in_any_defaults |= default_attrs
if all_incompatible:
self.log.debug('All old default ACIs are incompatible')
logger.debug('All old default ACIs are incompatible')
raise(IncompatibleACIModification())
included = current_attrs - attrs_in_any_defaults
@@ -573,6 +626,14 @@ class update_managed_permissions(PostUpdate):
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:
@@ -602,8 +663,8 @@ class update_managed_permissions(PostUpdate):
anonymous_read_aci.target['targetattr']['expression'])
read_blacklist &= attributes
if read_blacklist:
self.log.info('Excluded attributes for %s: %s',
name, ', '.join(read_blacklist))
logger.debug('Excluded attributes for %s: %s',
name, ', '.join(read_blacklist))
entry['ipapermexcludedattr'] = list(read_blacklist)
# Sanity check
@@ -611,3 +672,75 @@ class update_managed_permissions(PostUpdate):
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,92 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
from __future__ import absolute_import
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

@@ -17,41 +17,43 @@
# 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 ipaserver.install.plugins import MIDDLE
from ipaserver.install.plugins.baseupdate import PostUpdate
from ipalib import api, errors
import logging
from ipalib import Registry, errors
from ipalib import Updater
from ipapython.dn import DN
logger = logging.getLogger(__name__)
class update_pacs(PostUpdate):
register = Registry()
@register()
class update_pacs(Updater):
"""
Includes default nfs:None only if no nfs: PAC present in ipakrbauthzdata.
"""
order = MIDDLE
def execute(self, **options):
ldap = self.obj.backend
ldap = self.api.Backend.ldap2
try:
dn = DN('cn=ipaConfig', 'cn=etc', api.env.basedn)
dn = DN('cn=ipaConfig', 'cn=etc', self.api.env.basedn)
entry = ldap.get_entry(dn, ['ipakrbauthzdata'])
pacs = entry.get('ipakrbauthzdata', [])
except errors.NotFound:
self.log.warning('Error retrieving: %s' % str(dn))
return (False, False, [])
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:
self.log.debug('Adding nfs:NONE to default PAC types')
logger.debug('Adding nfs:NONE to default PAC types')
updated_pacs = pacs + [u'nfs:NONE']
entry['ipakrbauthzdata'] = updated_pacs
ldap.update_entry(entry)
else:
self.log.debug('PAC for nfs is already set, not adding nfs:NONE.')
logger.debug('PAC for nfs is already set, not adding nfs:NONE.')
return (False, False, [])
api.register(update_pacs)
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,66 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from __future__ import absolute_import
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

@@ -17,29 +17,33 @@
# 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 ipaserver.install.plugins import MIDDLE
from ipaserver.install.plugins.baseupdate import PostUpdate
from ipalib import api, errors
import logging
from ipalib import Registry, errors
from ipalib import Updater
from ipapython.dn import DN
from ipapython.ipa_log_manager import *
logger = logging.getLogger(__name__)
register = Registry()
class update_service_principalalias(PostUpdate):
@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.
"""
order = MIDDLE
def execute(self, **options):
ldap = self.obj.backend
ldap = self.api.Backend.ldap2
base_dn = DN(api.env.container_service, api.env.basedn)
base_dn = DN(self.api.env.container_service, self.api.env.basedn)
search_filter = ("(&(objectclass=krbprincipal)(objectclass=ipaservice)"
"(!(objectclass=ipakrbprincipal)))")
root_logger.debug("update_service_principalalias: search for affected "
"services")
logger.debug("update_service_principalalias: search for affected "
"services")
while True:
# run the search in loop to avoid issues when LDAP limits are hit
@@ -49,21 +53,21 @@ class update_service_principalalias(PostUpdate):
['objectclass', 'krbprincipalname'], base_dn,
time_limit=0, size_limit=0)
except errors.NotFound:
root_logger.debug("update_service_principalalias: no service "
"to update found")
return (False, False, [])
except errors.ExecutionError, e:
root_logger.error("update_service_principalalias: cannot "
"retrieve list of affected services: %s", e)
return (False, False, [])
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
root_logger.debug("update_service_principalalias: no service "
"was returned")
return (False, False, [])
root_logger.debug("update_service_principalalias: found %d "
"services to update, truncated: %s",
len(entries), truncated)
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:
@@ -74,21 +78,19 @@ class update_service_principalalias(PostUpdate):
ldap.update_entry(entry)
except (errors.EmptyModlist, errors.NotFound):
pass
except errors.ExecutionError, e:
root_logger.debug("update_service_principalalias: cannot "
"update service: %s", e)
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
root_logger.error("update_service_principalalias: error(s)"
"detected during service update")
return (False, False, [])
logger.error("update_service_principalalias: error(s)"
"detected during service update")
return False, []
elif not truncated:
# all affected entries updated, exit the loop
root_logger.debug("update_service_principalalias: all affected"
" services updated")
return (False, False, [])
return (False, False, [])
api.register(update_service_principalalias)
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

@@ -1,160 +0,0 @@
# 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/>.
#
from ipaserver.install.plugins.baseupdate import DSRestart
from ipaserver.install.ldapupdate import LDAPUpdate
from ipapython.ipautil import wait_for_open_socket
from ipalib import api
from ipalib import backend
from ipaplatform.paths import paths
from ipapython.dn import DN
class updateclient(backend.Executioner):
"""
Backend used for applying LDAP updates via plugins
An update plugin can be executed before the file-based plugins or
afterward. Each plugin returns three values:
1. restart: dirsrv needs to be restarted BEFORE this update is
applied.
2. apply_now: when True the update is applied when the plugin
returns. Otherwise the update is cached until all
plugins of that update type are complete, then they
are applied together.
3. updates: A dictionary of updates to be applied.
updates is a dictionary keyed on dn. The value of an update is a
dictionary with the following possible values:
- dn: DN, equal to the dn attribute
- updates: list of updates against the dn
- default: list of the default entry to be added if it doesn't
exist
- deleteentry: list of dn's to be deleted (typically single dn)
For example, this update file:
dn: cn=global_policy,cn=$REALM,cn=kerberos,$SUFFIX
replace:krbPwdLockoutDuration:10::600
replace: krbPwdMaxFailure:3::6
Generates this update dictionary:
dict('cn=global_policy,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com':
dict(
'dn': 'cn=global_policy,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
'updates': ['replace:krbPwdLockoutDuration:10::600',
'replace:krbPwdMaxFailure:3::6']
)
)
Here is another example showing how a default entry is configured:
dn: cn=Managed Entries,cn=etc,$SUFFIX
default: objectClass: nsContainer
default: objectClass: top
default: cn: Managed Entries
This generates:
dict('cn=Managed Entries,cn=etc,dc=example,dc=com',
dict(
'dn': 'cn=Managed Entries,cn=etc,dc=example,dc=com',
'default': ['objectClass:nsContainer',
'objectClass:top',
'cn:Managed Entries'
]
)
)
Note that the variable substitution in both examples has been completed.
A PRE_UPDATE plugin is executed before file-based updates.
A POST_UPDATE plugin is executed after file-based updates.
Plugins are executed automatically when ipa-ldap-updater is run
in upgrade mode (--upgrade). They are not executed normally otherwise.
To execute plugins as well use the --plugins flag.
Either may make changes directly in LDAP or can return updates in
update format.
"""
def create_context(self, dm_password):
if dm_password:
autobind = False
else:
autobind = True
self.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password, autobind=autobind)
def order(self, updatetype):
"""Return plugins of the given updatetype in sorted order.
"""
ordered = [plugin for plugin in api.Updater()
if plugin.updatetype == updatetype]
ordered.sort(key=lambda p: p.order)
return ordered
def update(self, updatetype, dm_password, ldapi, live_run):
"""
Execute all update plugins of type updatetype.
"""
self.create_context(dm_password)
kw = dict(live_run=live_run)
result = []
ld = LDAPUpdate(dm_password=dm_password, sub_dict={}, live_run=live_run, ldapi=ldapi)
for update in self.order(updatetype):
(restart, apply_now, res) = self.run(update.name, **kw)
if restart:
# connection has to be closed before restart, otherwise
# ld instance will try to reuse old non-valid connection
ld.close_connection()
self.restart(dm_password, live_run)
if apply_now:
updates = {}
for entry in res:
updates.update(entry)
ld.update_from_dict(updates)
elif res:
result.extend(res)
self.destroy_context()
return result
def run(self, method, **kw):
"""
Execute the update plugin.
"""
return self.Updater[method](**kw)
def restart(self, dm_password, live_run):
dsrestart = DSRestart()
socket_name = paths.SLAPD_INSTANCE_SOCKET_TEMPLATE % \
api.env.realm.replace('.','-')
if live_run:
self.destroy_context()
dsrestart.create_instance()
wait_for_open_socket(socket_name)
self.create_context(dm_password)
else:
self.log.warn("Test mode, skipping restart")
api.register(updateclient)

View File

@@ -17,39 +17,117 @@
# 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 ipaserver.install.plugins import MIDDLE
from ipaserver.install.plugins.baseupdate import PostUpdate
from ipaserver.install.dsinstance import realm_to_serverid, config_dirname
from ipaserver.install import certs
from ipalib import api
from ipapython.dn import DN
import base64
import logging
class update_upload_cacrt(PostUpdate):
from ipalib.install import certstore
from ipaserver.install import certs, dsinstance
from ipaserver.install.installutils import realm_to_serverid
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
"""
order=MIDDLE
def execute(self, **options):
ldap = self.obj.backend
ipa_config = ldap.get_ipa_config()
subject_base = ipa_config.get('ipacertificatesubjectbase', [None])[0]
dirname = config_dirname(realm_to_serverid(api.env.realm))
certdb = certs.CertDB(api.env.realm, nssdir=dirname, subject_base=subject_base)
serverid = realm_to_serverid(self.api.env.realm)
db = certs.CertDB(self.api.env.realm,
nssdir=dsinstance.config_dirname(serverid))
ca_cert = None
dercert = certdb.get_cert_from_db(certdb.cacert_name, pem=False)
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]
updates = {}
dn = DN(('cn', 'CACert'), ('cn', 'ipa'), ('cn','etc'), api.env.basedn)
ldap = self.api.Backend.ldap2
cacrt_entry = ['objectclass:nsContainer',
'objectclass:pkiCA',
'cn:CAcert',
'cACertificate;binary:%s' % dercert,
]
updates[dn] = {'dn': dn, 'default': cacrt_entry}
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)
return (False, True, [updates])
dn = DN(('cn', nickname), ('cn', 'certificates'), ('cn', 'ipa'),
('cn','etc'), self.api.env.basedn)
entry = ldap.make_entry(dn)
api.register(update_upload_cacrt)
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:
force_write = False
try:
_cert_bin = entry['cACertificate;binary']
except ValueError:
# BZ 1644874
# sometimes the cert is badly stored, twice encoded
# force write to fix the value
logger.debug('Fixing the value of cACertificate;binary '
'in entry %s', entry.dn)
force_write = True
if force_write or b'' in entry['cACertificate;binary']:
entry.single_value['cACertificate;binary'] = ca_cert
ldap.update_entry(entry)
return False, []