Imported Upstream version 4.7.2
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -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, []
|
||||
|
||||
@@ -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__()
|
||||
Binary file not shown.
@@ -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]
|
||||
|
||||
@@ -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, []
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
62
ipaserver/install/plugins/update_ca_topology.py
Normal file
62
ipaserver/install/plugins/update_ca_topology.py
Normal 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, []
|
||||
128
ipaserver/install/plugins/update_dna_shared_config.py
Normal file
128
ipaserver/install/plugins/update_dna_shared_config.py
Normal file
@@ -0,0 +1,128 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
import time
|
||||
import ldap
|
||||
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib import errors
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_dna_shared_config(Updater):
|
||||
def execute(self, **options):
|
||||
method = options.get('method', "SASL/GSSAPI")
|
||||
protocol = options.get('protocol', "LDAP")
|
||||
|
||||
dna_bind_method = "dnaRemoteBindMethod"
|
||||
dna_conn_protocol = "dnaRemoteConnProtocol"
|
||||
dna_plugin = DN(('cn', 'Distributed Numeric Assignment Plugin'),
|
||||
('cn', 'plugins'),
|
||||
('cn', 'config'))
|
||||
dna_config_base = DN(('cn', 'posix IDs'), dna_plugin)
|
||||
|
||||
conn = self.api.Backend.ldap2
|
||||
|
||||
# Check the plugin is enabled else it is useless to update
|
||||
# the shared entry
|
||||
try:
|
||||
entry = conn.get_entry(dna_plugin)
|
||||
if entry.single_value.get('nsslapd-pluginenabled') == 'off':
|
||||
return False, ()
|
||||
except errors.NotFound:
|
||||
logger.error("Could not find DNA plugin entry: %s",
|
||||
dna_config_base)
|
||||
return False, ()
|
||||
|
||||
try:
|
||||
entry = conn.get_entry(dna_config_base)
|
||||
except errors.NotFound:
|
||||
logger.error("Could not find DNA config entry: %s",
|
||||
dna_config_base)
|
||||
return False, ()
|
||||
|
||||
sharedcfgdn = entry.single_value.get("dnaSharedCfgDN")
|
||||
if sharedcfgdn is not None:
|
||||
sharedcfgdn = DN(sharedcfgdn)
|
||||
else:
|
||||
logger.error(
|
||||
"Could not find DNA shared config DN in entry: %s",
|
||||
dna_config_base)
|
||||
return False, ()
|
||||
|
||||
#
|
||||
# Update the shared config entry related to that host
|
||||
#
|
||||
# If the shared config entry already exists (like upgrade)
|
||||
# the update occurs immediately without sleep.
|
||||
#
|
||||
# If the shared config entry does not exist (fresh install)
|
||||
# DS server waits for 30s after its startup to create it.
|
||||
# Startup likely occurred few sec before this function is
|
||||
# called so this loop will wait for 30s max.
|
||||
#
|
||||
# In case the server is not able to create the entry
|
||||
# The loop gives a grace period of 60s before logging
|
||||
# the failure to update the shared config entry and return
|
||||
#
|
||||
max_wait = 30
|
||||
fqdn = self.api.env.host
|
||||
for _i in range(0, max_wait + 1):
|
||||
try:
|
||||
entries = conn.get_entries(
|
||||
sharedcfgdn, scope=ldap.SCOPE_ONELEVEL,
|
||||
filter='dnaHostname=%s' % fqdn
|
||||
)
|
||||
break
|
||||
except errors.NotFound:
|
||||
logger.debug(
|
||||
"Unable to find DNA shared config entry for "
|
||||
"dnaHostname=%s (under %s) so far. Retry in 2 sec.",
|
||||
fqdn, sharedcfgdn
|
||||
)
|
||||
time.sleep(2)
|
||||
else:
|
||||
logger.error(
|
||||
"Could not get dnaHostname entries in %s seconds",
|
||||
max_wait * 2
|
||||
)
|
||||
return False, ()
|
||||
|
||||
# If there are several entries, all of them will be updated
|
||||
# just log a debug msg. This is likely the result of #5510
|
||||
if len(entries) != 1:
|
||||
logger.debug(
|
||||
"%d entries dnaHostname=%s under %s. One expected",
|
||||
len(entries), fqdn, sharedcfgdn
|
||||
)
|
||||
|
||||
# time to set the bind method and the protocol in the
|
||||
# shared config entries
|
||||
for entry in entries:
|
||||
update = False
|
||||
if entry.single_value.get(dna_bind_method) != method:
|
||||
entry[dna_bind_method] = method
|
||||
update = True
|
||||
|
||||
if entry.single_value.get(dna_conn_protocol) != protocol:
|
||||
entry[dna_conn_protocol] = protocol
|
||||
update = True
|
||||
|
||||
if update:
|
||||
try:
|
||||
conn.update_entry(entry)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Failed to set SASL/GSSAPI bind method/protocol "
|
||||
"in entry %s: %s", entry, e
|
||||
)
|
||||
# no restart, no update
|
||||
return False, ()
|
||||
@@ -0,0 +1,84 @@
|
||||
# Authors:
|
||||
# Florence Blanc-Renaud <flo@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2017 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
from ipalib import Registry, errors
|
||||
from ipalib import Updater
|
||||
from ipalib.install import certstore
|
||||
from ipapython.dn import DN
|
||||
from ipapython.certdb import get_ca_nickname
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_fix_duplicate_cacrt_in_ldap(Updater):
|
||||
"""
|
||||
When multiple entries exist for IPA CA cert in ldap, remove the duplicate
|
||||
|
||||
After this plugin, ds needs to be restarted. This ensures that
|
||||
the attribute uniqueness plugin is working and prevents
|
||||
other plugins from adding duplicates.
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
# If CA is disabled, no need to check for duplicates of IPA CA
|
||||
ca_enabled = self.api.Command.ca_is_enabled()['result']
|
||||
if not ca_enabled:
|
||||
return True, []
|
||||
|
||||
# Look for the IPA CA cert subject
|
||||
ldap = self.api.Backend.ldap2
|
||||
cacert_subject = certstore.get_ca_subject(
|
||||
ldap,
|
||||
self.api.env.container_ca,
|
||||
self.api.env.basedn)
|
||||
|
||||
# Find if there are other certificates with the same subject
|
||||
# They are duplicates resulting of BZ 1480102
|
||||
base_dn = DN(('cn', 'certificates'), ('cn', 'ipa'), ('cn', 'etc'),
|
||||
self.api.env.basedn)
|
||||
try:
|
||||
filter = ldap.make_filter({'ipaCertSubject': cacert_subject})
|
||||
result, _truncated = ldap.find_entries(
|
||||
base_dn=base_dn,
|
||||
filter=filter,
|
||||
attrs_list=[])
|
||||
except errors.NotFound:
|
||||
# No duplicate, we're good
|
||||
logger.debug("No duplicates for IPA CA in LDAP")
|
||||
return True, []
|
||||
|
||||
logger.debug("Found %d entrie(s) for IPA CA in LDAP", len(result))
|
||||
cacert_dn = DN(('cn', get_ca_nickname(self.api.env.realm)), base_dn)
|
||||
for entry in result:
|
||||
if entry.dn == cacert_dn:
|
||||
continue
|
||||
# Remove the duplicate
|
||||
try:
|
||||
ldap.delete_entry(entry)
|
||||
logger.debug("Removed the duplicate %s", entry.dn)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to remove the duplicate %s: %s",
|
||||
entry.dn, e)
|
||||
|
||||
return True, []
|
||||
@@ -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, []
|
||||
|
||||
38
ipaserver/install/plugins/update_ldap_server_list.py
Normal file
38
ipaserver/install/plugins/update_ldap_server_list.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from ipalib import Registry
|
||||
from ipalib import Updater
|
||||
from ipalib import errors
|
||||
from ipapython.dn import DN
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_ldap_server_list(Updater):
|
||||
"""
|
||||
Update defaultServerList, an option that helps Solaris
|
||||
clients discover LDAP server replicas.
|
||||
"""
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
dn = DN(('cn', 'default'), ('ou', 'profile'), self.api.env.basedn)
|
||||
try:
|
||||
entry = ldap.get_entry(dn)
|
||||
srvlist = entry.single_value.get('defaultServerList', '')
|
||||
srvlist = srvlist.split()
|
||||
if not self.api.env.host in srvlist:
|
||||
srvlist.append(self.api.env.host)
|
||||
attr = ' '.join(srvlist)
|
||||
entry['defaultServerList'] = attr
|
||||
ldap.update_entry(entry)
|
||||
except errors.NotFound:
|
||||
pass
|
||||
except ldap.TYPE_OR_VALUE_EXISTS:
|
||||
pass
|
||||
|
||||
# no restart, no updates
|
||||
return False, ()
|
||||
@@ -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, ()
|
||||
|
||||
Binary file not shown.
92
ipaserver/install/plugins/update_nis.py
Normal file
92
ipaserver/install/plugins/update_nis.py
Normal 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, ()
|
||||
@@ -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, []
|
||||
|
||||
81
ipaserver/install/plugins/update_passsync.py
Normal file
81
ipaserver/install/plugins/update_passsync.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#
|
||||
# Copyright (C) 2014 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from ipalib import Registry, errors
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
from ipaserver.install import sysupgrade
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_passync_privilege_check(Updater):
|
||||
|
||||
def execute(self, **options):
|
||||
update_done = sysupgrade.get_upgrade_state('winsync', 'passsync_privilege_updated')
|
||||
if update_done:
|
||||
logger.debug("PassSync privilege update pre-check not needed")
|
||||
return False, []
|
||||
|
||||
logger.debug("Check if there is existing PassSync privilege")
|
||||
|
||||
passsync_privilege_dn = DN(('cn','PassSync Service'),
|
||||
self.api.env.container_privilege,
|
||||
self.api.env.basedn)
|
||||
|
||||
ldap = self.api.Backend.ldap2
|
||||
try:
|
||||
ldap.get_entry(passsync_privilege_dn, [''])
|
||||
except errors.NotFound:
|
||||
logger.debug("PassSync privilege not found, this is a new update")
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', False)
|
||||
else:
|
||||
logger.debug("PassSync privilege found, skip updating PassSync")
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True)
|
||||
|
||||
return False, []
|
||||
|
||||
|
||||
@register()
|
||||
class update_passync_privilege_update(Updater):
|
||||
"""
|
||||
Add PassSync user as a member of PassSync privilege, if it exists
|
||||
"""
|
||||
|
||||
def execute(self, **options):
|
||||
update_done = sysupgrade.get_upgrade_state('winsync', 'passsync_privilege_updated')
|
||||
if update_done:
|
||||
logger.debug("PassSync privilege update not needed")
|
||||
return False, []
|
||||
|
||||
logger.debug("Add PassSync user as a member of PassSync privilege")
|
||||
ldap = self.api.Backend.ldap2
|
||||
passsync_dn = DN(('uid','passsync'), ('cn', 'sysaccounts'), ('cn', 'etc'),
|
||||
self.api.env.basedn)
|
||||
passsync_privilege_dn = DN(('cn','PassSync Service'),
|
||||
self.api.env.container_privilege,
|
||||
self.api.env.basedn)
|
||||
|
||||
try:
|
||||
ldap.get_entry(passsync_dn, [''])
|
||||
except errors.NotFound:
|
||||
logger.debug("PassSync user not found, no update needed")
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True)
|
||||
return False, []
|
||||
else:
|
||||
logger.debug("PassSync user found, do update")
|
||||
|
||||
update = {'dn': passsync_privilege_dn,
|
||||
'updates': [
|
||||
dict(action='add', attr='member', value=passsync_dn),
|
||||
]
|
||||
}
|
||||
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True)
|
||||
return False, [update]
|
||||
66
ipaserver/install/plugins/update_ra_cert_store.py
Normal file
66
ipaserver/install/plugins/update_ra_cert_store.py
Normal 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, []
|
||||
92
ipaserver/install/plugins/update_referint.py
Normal file
92
ipaserver/install/plugins/update_referint.py
Normal file
@@ -0,0 +1,92 @@
|
||||
#
|
||||
# Copyright (C) 2014 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from ipalib import Registry, errors
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_referint(Updater):
|
||||
"""
|
||||
Update referential integrity configuration to new style
|
||||
http://directory.fedoraproject.org/docs/389ds/design/ri-plugin-configuration.html
|
||||
|
||||
old attr -> new attr
|
||||
nsslapd-pluginArg0 -> referint-update-delay
|
||||
nsslapd-pluginArg1 -> referint-logfile
|
||||
nsslapd-pluginArg2 -> referint-logchanges
|
||||
nsslapd-pluginArg3..N -> referint-membership-attr [3..N]
|
||||
|
||||
Old and new style cannot be mixed, all nslapd-pluginArg* attrs have to be removed
|
||||
"""
|
||||
|
||||
referint_dn = DN(('cn', 'referential integrity postoperation'),
|
||||
('cn', 'plugins'), ('cn', 'config'))
|
||||
|
||||
def execute(self, **options):
|
||||
|
||||
logger.debug("Upgrading referential integrity plugin configuration")
|
||||
ldap = self.api.Backend.ldap2
|
||||
try:
|
||||
entry = ldap.get_entry(self.referint_dn)
|
||||
except errors.NotFound:
|
||||
logger.error("Referential integrity configuration not found")
|
||||
return False, []
|
||||
|
||||
referint_membership_attrs = []
|
||||
|
||||
logger.debug("Initial value: %s", repr(entry))
|
||||
|
||||
# nsslapd-pluginArg0 -> referint-update-delay
|
||||
update_delay = entry.get('nsslapd-pluginArg0')
|
||||
if update_delay:
|
||||
logger.debug("add: referint-update-delay: %s", update_delay)
|
||||
entry['referint-update-delay'] = update_delay
|
||||
entry['nsslapd-pluginArg0'] = None
|
||||
else:
|
||||
logger.debug("Plugin already uses new style, skipping")
|
||||
return False, []
|
||||
|
||||
# nsslapd-pluginArg1 -> referint-logfile
|
||||
logfile = entry.get('nsslapd-pluginArg1')
|
||||
if logfile:
|
||||
logger.debug("add: referint-logfile: %s", logfile)
|
||||
entry['referint-logfile'] = logfile
|
||||
entry['nsslapd-pluginArg1'] = None
|
||||
|
||||
# nsslapd-pluginArg2 -> referint-logchanges
|
||||
logchanges = entry.get('nsslapd-pluginArg2')
|
||||
if logchanges:
|
||||
logger.debug("add: referint-logchanges: %s", logchanges)
|
||||
entry['referint-logchanges'] = logchanges
|
||||
entry['nsslapd-pluginArg2'] = None
|
||||
|
||||
# nsslapd-pluginArg3..N -> referint-membership-attr [3..N]
|
||||
for key in list(entry):
|
||||
if key.lower().startswith('nsslapd-pluginarg'):
|
||||
arg_val = entry.single_value[key]
|
||||
if arg_val:
|
||||
referint_membership_attrs.append(arg_val)
|
||||
entry[key] = None
|
||||
|
||||
if referint_membership_attrs:
|
||||
# entry['referint-membership-attr'] is None, plugin doesn't allow
|
||||
# mixing old and new style
|
||||
entry['referint-membership-attr'] = referint_membership_attrs
|
||||
|
||||
logger.debug("Final value: %s", repr(entry))
|
||||
try:
|
||||
ldap.update_entry(entry)
|
||||
except errors.EmptyModlist:
|
||||
logger.debug("No modifications required")
|
||||
return False, []
|
||||
|
||||
return False, []
|
||||
@@ -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, []
|
||||
|
||||
226
ipaserver/install/plugins/update_uniqueness.py
Normal file
226
ipaserver/install/plugins/update_uniqueness.py
Normal file
@@ -0,0 +1,226 @@
|
||||
# Authors:
|
||||
# Alexander Bokovoy <abokovoy@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
from ipalib import Registry, errors
|
||||
from ipalib import Updater
|
||||
from ipapython.dn import DN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
@register()
|
||||
class update_uniqueness_plugins_to_new_syntax(Updater):
|
||||
"""
|
||||
Migrate uniqueness plugins to new style syntax
|
||||
|
||||
* OLD: *
|
||||
nsslapd-pluginarg0: uid
|
||||
nsslapd-pluginarg1: dc=people,dc=example,dc=com
|
||||
nsslapd-pluginarg2: dc=sales, dc=example,dc=com
|
||||
|
||||
or
|
||||
|
||||
nsslapd-pluginarg0: attribute=uid
|
||||
nsslapd-pluginarg1: markerobjectclass=organizationalUnit
|
||||
nsslapd-pluginarg2: requiredobjectclass=person
|
||||
|
||||
* NEW: *
|
||||
uniqueness-attribute-name: uid
|
||||
uniqueness-subtrees: dc=people,dc=example,dc=com
|
||||
uniqueness-subtrees: dc=sales, dc=example,dc=com
|
||||
uniqueness-across-all-subtrees: on
|
||||
|
||||
or
|
||||
|
||||
uniqueness-attribute-name: uid
|
||||
uniqueness-top-entry-oc: organizationalUnit
|
||||
uniqueness-subtree-entries-oc: person
|
||||
"""
|
||||
|
||||
plugins_dn = DN(('cn', 'plugins'), ('cn', 'config'))
|
||||
|
||||
def __remove_update(self, update, key, value):
|
||||
statement = dict(action='remove', attr=key, value=value)
|
||||
update.setdefault('updates', []).append(statement)
|
||||
|
||||
def __add_update(self, update, key, value):
|
||||
statement = dict(action='add', attr=key, value=value)
|
||||
update.setdefault('updates', []).append(statement)
|
||||
|
||||
def __subtree_style(self, entry):
|
||||
"""
|
||||
old attr -> new attr
|
||||
nsslapd-pluginArg0 -> uniqueness-attribute-name
|
||||
nsslapd-pluginArg1..N -> uniqueness-subtrees[1..N]
|
||||
"""
|
||||
update = {
|
||||
'dn': entry.dn,
|
||||
'updates': [],
|
||||
}
|
||||
|
||||
# nsslapd-pluginArg0 -> referint-update-delay
|
||||
attribute = entry.single_value['nsslapd-pluginArg0']
|
||||
if not attribute:
|
||||
raise ValueError("'nsslapd-pluginArg0' not found")
|
||||
self.__remove_update(update, 'nsslapd-pluginArg0', attribute)
|
||||
self.__add_update(update, 'uniqueness-attribute-name', attribute)
|
||||
entry['nsslapd-pluginArg0'] = None
|
||||
|
||||
# nsslapd-pluginArg1..N -> uniqueness-subtrees[1..N]
|
||||
for key in entry.keys():
|
||||
if key.lower().startswith('nsslapd-pluginarg'):
|
||||
subtree_dn = entry.single_value[key]
|
||||
if subtree_dn:
|
||||
self.__remove_update(update, key, subtree_dn)
|
||||
self.__add_update(update, 'uniqueness-subtrees', subtree_dn)
|
||||
|
||||
return update
|
||||
|
||||
def __objectclass_style(self, entry):
|
||||
"""
|
||||
old attr -> new attr
|
||||
nsslapd-pluginArg?[attribute] -> uniqueness-attribute-name
|
||||
nsslapd-pluginArg?[markerobjectclass] -> uniqueness-top-entry-oc
|
||||
nsslapd-pluginArg?[requiredobjectclass](optional)
|
||||
-> uniqueness-subtree-entries-oc
|
||||
nsslapd-pluginArg?[others] -> ERROR: unexpected args
|
||||
|
||||
Single value attributes.
|
||||
"""
|
||||
|
||||
update = {
|
||||
'dn': entry.dn,
|
||||
'updates': [],
|
||||
}
|
||||
|
||||
attribute = None
|
||||
markerobjectclass = None
|
||||
requiredobjectclass = None
|
||||
|
||||
for key in entry.keys():
|
||||
if key.lower().startswith('nsslapd-pluginarg'):
|
||||
try:
|
||||
# split argument name and value
|
||||
value = entry.single_value[key]
|
||||
arg_name, arg_val = value.split('=', 1)
|
||||
except ValueError:
|
||||
# unable to split
|
||||
raise ValueError("unexpected argument %s: %s" %
|
||||
(key, value))
|
||||
arg_name = arg_name.lower()
|
||||
if arg_name == 'attribute':
|
||||
if attribute:
|
||||
raise ValueError("single value argument 'attribute' "
|
||||
"is specified mutliple times")
|
||||
attribute = arg_val
|
||||
self.__remove_update(update, key, value)
|
||||
elif arg_name == 'markerobjectclass':
|
||||
if markerobjectclass:
|
||||
raise ValueError("single value argument "
|
||||
"'markerobjectclass' "
|
||||
"is specified mutliple times")
|
||||
markerobjectclass = arg_val
|
||||
self.__remove_update(update, key, value)
|
||||
elif arg_name == 'requiredobjectclass':
|
||||
if requiredobjectclass:
|
||||
raise ValueError("single value argument "
|
||||
"'requiredobjectclass' "
|
||||
"is specified mutliple times")
|
||||
requiredobjectclass = arg_val
|
||||
self.__remove_update(update, key, value)
|
||||
else:
|
||||
raise ValueError("unexpected argument '%s: %s'" %
|
||||
(key, value))
|
||||
|
||||
if not attribute:
|
||||
raise ValueError("missing required argument 'attribute'")
|
||||
if not markerobjectclass:
|
||||
raise ValueError("missing required argument 'markerobjectclass'")
|
||||
|
||||
self.__add_update(update, 'uniqueness-attribute-name', attribute)
|
||||
self.__add_update(update, 'uniqueness-top-entry-oc', markerobjectclass)
|
||||
|
||||
if requiredobjectclass:
|
||||
# optional argument
|
||||
self.__add_update(update, 'uniqueness-subtree-entries-oc',
|
||||
requiredobjectclass)
|
||||
|
||||
return update
|
||||
|
||||
def execute(self, **options):
|
||||
ldap = self.api.Backend.ldap2
|
||||
|
||||
old_style_plugin_search_filter = (
|
||||
"(&"
|
||||
"(objectclass=nsSlapdPlugin)"
|
||||
"(nsslapd-pluginId=NSUniqueAttr)"
|
||||
"(nsslapd-pluginPath=libattr-unique-plugin)"
|
||||
"(nsslapd-pluginarg0=*)" # only entries with old configuration
|
||||
")"
|
||||
)
|
||||
|
||||
try:
|
||||
entries, _truncated = ldap.find_entries(
|
||||
filter=old_style_plugin_search_filter,
|
||||
base_dn=self.plugins_dn,
|
||||
)
|
||||
except errors.NotFound:
|
||||
logger.debug("No uniqueness plugin entries with old style "
|
||||
"configuration found")
|
||||
return False, []
|
||||
|
||||
update_list = []
|
||||
new_attributes = [
|
||||
'uniqueness-subtree-entries-oc',
|
||||
'uniqueness-top-entry-oc',
|
||||
'uniqueness-attribute-name',
|
||||
'uniqueness-subtrees',
|
||||
'uniqueness-across-all-subtrees',
|
||||
]
|
||||
|
||||
for entry in entries:
|
||||
# test for mixed configuration
|
||||
if any(attr in entry for attr in new_attributes):
|
||||
logger.critical("Mixed old and new style configuration "
|
||||
"for plugin %s. Plugin will not work. "
|
||||
"Skipping plugin migration, please fix it "
|
||||
"manually",
|
||||
entry.dn)
|
||||
continue
|
||||
logger.debug("Configuration of plugin %s will be migrated "
|
||||
"to new style", entry.dn)
|
||||
try:
|
||||
# detect which configuration was used
|
||||
arg0 = entry.get('nsslapd-pluginarg0')
|
||||
if '=' in arg0:
|
||||
update = self.__objectclass_style(entry)
|
||||
else:
|
||||
update = self.__subtree_style(entry)
|
||||
except ValueError as e:
|
||||
logger.error("Unable to migrate configuration of "
|
||||
"plugin %s (%s)",
|
||||
entry.dn, e)
|
||||
else:
|
||||
update_list.append(update)
|
||||
|
||||
return False, update_list
|
||||
@@ -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)
|
||||
@@ -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, []
|
||||
|
||||
Reference in New Issue
Block a user