mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Server Upgrade: Allow base64 encoded values
This patch allows to use base64 encoded values in update files. Double colon ('::') must be used as separator between attribute name and base64 encoded value. add:attr::<base64-value> replace:attr::<old-base64-value>::<new-base64-value> https://fedorahosted.org/freeipa/ticket/4984 Reviewed-By: Jan Cholasta <jcholast@redhat.com>
This commit is contained in:
parent
5783d0c832
commit
520bbd001b
@ -39,7 +39,7 @@ There are 7 keywords:
|
||||
* only: set an attribute to this
|
||||
* onlyifexist: set an attribute to this only if the entry exists
|
||||
* deleteentry: remove the entry
|
||||
* replace: replace an existing value, format is old: new
|
||||
* replace: replace an existing value, format is old::new
|
||||
* addifnew: add a new attribute and value only if the attribute doesn't already exist. Only works with single\-value attributes.
|
||||
* addifexist: add a new attribute and value only if the entry exists. This is used to update optional entries.
|
||||
|
||||
@ -57,6 +57,12 @@ The available template variables are:
|
||||
* $LIBARCH \- set to 64 on x86_64 systems to be used for plugin paths
|
||||
* $TIME \- an integer representation of current time
|
||||
|
||||
For base64 encoded values a double colon ('::') must be used between attribute and value.
|
||||
|
||||
Base64 format examples:
|
||||
add:binaryattr::d2UgbG92ZSBiYXNlNjQ=
|
||||
replace:binaryattr::SVBBIGlzIGdyZWF0::SVBBIGlzIHJlYWxseSBncmVhdA==
|
||||
|
||||
A few rules:
|
||||
|
||||
1. Only one rule per line
|
||||
|
@ -22,6 +22,7 @@
|
||||
# TODO
|
||||
# save undo files?
|
||||
|
||||
import base64
|
||||
import sys
|
||||
import uuid
|
||||
import platform
|
||||
@ -106,9 +107,31 @@ def safe_output(attr, values):
|
||||
return ['XXXXXXX'] * len(values)
|
||||
else:
|
||||
return 'XXXXXXXX'
|
||||
else:
|
||||
|
||||
if values is None:
|
||||
return
|
||||
|
||||
is_list = type(values) in (tuple, list)
|
||||
|
||||
if is_list and None in values:
|
||||
return values
|
||||
|
||||
if not is_list:
|
||||
values = [values]
|
||||
|
||||
try:
|
||||
all(v.decode('ascii') for v in values)
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
values = [base64.b64encode(v) for v in values]
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if not is_list:
|
||||
values = values[0]
|
||||
return values
|
||||
|
||||
|
||||
class LDAPUpdate:
|
||||
action_keywords = ["default", "add", "remove", "only", "onlyifexist", "deleteentry", "replace", "addifnew", "addifexist"]
|
||||
|
||||
@ -136,18 +159,28 @@ class LDAPUpdate:
|
||||
all_updates = [
|
||||
{
|
||||
'dn': 'cn=config,dc=example,dc=com',
|
||||
'default': ['attr1':default1'],
|
||||
'updates': ['action:attr1:value1',
|
||||
'action:attr2:value2]
|
||||
'default': [
|
||||
dict(attr='attr1', value='default1'),
|
||||
],
|
||||
'updates': [
|
||||
dict(action='action', attr='attr1', value='value1'),
|
||||
dict(action='replace', attr='attr2', value=['old', 'new']),
|
||||
]
|
||||
},
|
||||
{
|
||||
'dn': 'cn=bob,ou=people,dc=example,dc=com',
|
||||
'default': ['attr3':default3'],
|
||||
'updates': ['action:attr3:value3',
|
||||
'action:attr4:value4],
|
||||
'default': [
|
||||
dict(attr='attr3', value='default3'),
|
||||
],
|
||||
'updates': [
|
||||
dict(action='action', attr='attr3', value='value3'),
|
||||
dict(action='action', attr='attr4', value='value4'),
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Please notice the replace action requires two values in list
|
||||
|
||||
The default and update lists are "dispositions"
|
||||
|
||||
Plugins:
|
||||
@ -181,11 +214,15 @@ class LDAPUpdate:
|
||||
Generates this list which contain the update dictionary:
|
||||
|
||||
[
|
||||
dict(
|
||||
{
|
||||
'dn': 'cn=global_policy,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||
'updates': ['replace:krbPwdLockoutDuration:10::600',
|
||||
'replace:krbPwdMaxFailure:3::6']
|
||||
)
|
||||
'updates': [
|
||||
dict(action='replace', attr='krbPwdLockoutDuration',
|
||||
value=['10','600']),
|
||||
dict(action='replace', attr='krbPwdMaxFailure',
|
||||
value=['3','6']),
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Here is another example showing how a default entry is configured:
|
||||
@ -198,13 +235,14 @@ class LDAPUpdate:
|
||||
This generates:
|
||||
|
||||
[
|
||||
dict(
|
||||
{
|
||||
'dn': 'cn=Managed Entries,cn=etc,dc=example,dc=com',
|
||||
'default': ['objectClass:nsContainer',
|
||||
'objectClass:top',
|
||||
'cn:Managed Entries'
|
||||
]
|
||||
)
|
||||
'default': [
|
||||
dict(attr='objectClass', value='nsContainer'),
|
||||
dict(attr='objectClass', value='top'),
|
||||
dict(attr='cn', value='Managed Entries'),
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Note that the variable substitution in both examples has been completed.
|
||||
@ -348,13 +386,52 @@ class LDAPUpdate:
|
||||
raise BadSyntax, "Bad formatting on line %s:%d: %s" % (data_source_name, lcount, logical_line)
|
||||
|
||||
attr = items[1].strip()
|
||||
value = items[2].strip()
|
||||
# do not strip here, we need detect '::' due to base64 encoded
|
||||
# values, strip may result into fake detection
|
||||
value = items[2]
|
||||
|
||||
# detect base64 encoding
|
||||
# value which start with ':' are base64 encoded
|
||||
# decode it as a binary value
|
||||
if value.startswith(':'):
|
||||
value = value[1:]
|
||||
binary = True
|
||||
else:
|
||||
binary = False
|
||||
value = value.strip()
|
||||
|
||||
if action == 'replace':
|
||||
try:
|
||||
value = value.split('::', 1)
|
||||
except ValueError:
|
||||
raise BadSyntax(
|
||||
"Bad syntax in replace on line %s:%d: %s, needs to "
|
||||
"be in the format old::new in %s" % (
|
||||
data_source_name, lcount, logical_line, value)
|
||||
)
|
||||
else:
|
||||
value = [value]
|
||||
|
||||
if binary:
|
||||
for i, v in enumerate(value):
|
||||
try:
|
||||
value[i] = base64.b64decode(v)
|
||||
except TypeError as e:
|
||||
raise BadSyntax(
|
||||
"Base64 encoded value %s on line %s:%d: %s is "
|
||||
"incorrect (%s)" % (v, data_source_name,
|
||||
lcount, logical_line, e)
|
||||
)
|
||||
|
||||
if action != 'replace':
|
||||
value = value[0]
|
||||
|
||||
if action == "default":
|
||||
new_value = attr + ":" + value
|
||||
new_value = {'attr': attr, 'value': value}
|
||||
disposition = "default"
|
||||
else:
|
||||
new_value = action + ":" + attr + ":" + value
|
||||
new_value = {'action': action, "attr": attr,
|
||||
'value': value}
|
||||
disposition = "updates"
|
||||
|
||||
disposition_list = update.setdefault(disposition, [])
|
||||
@ -520,7 +597,9 @@ class LDAPUpdate:
|
||||
|
||||
for item in default:
|
||||
# We already do syntax-parsing so this is safe
|
||||
(attr, value) = item.split(':',1)
|
||||
attr = item['attr']
|
||||
value = item['value']
|
||||
|
||||
e = entry.get(attr)
|
||||
if e:
|
||||
# multi-valued attribute
|
||||
@ -558,8 +637,11 @@ class LDAPUpdate:
|
||||
only = {}
|
||||
for update in updates:
|
||||
# We already do syntax-parsing so this is safe
|
||||
(action, attr, update_value) = update.split(':', 2)
|
||||
entry_values = entry.get(attr, [])
|
||||
action = update['action']
|
||||
attr = update['attr']
|
||||
update_value = update['value']
|
||||
|
||||
entry_values = entry.raw.get(attr, [])
|
||||
if action == 'remove':
|
||||
self.debug("remove: '%s' from %s, current value %s", safe_output(attr, update_value), attr, safe_output(attr,entry_values))
|
||||
try:
|
||||
@ -620,11 +702,9 @@ class LDAPUpdate:
|
||||
# skip this update type, it occurs in __delete_entries()
|
||||
return None
|
||||
elif action == 'replace':
|
||||
# value has the format "old::new"
|
||||
try:
|
||||
(old, new) = update_value.split('::', 1)
|
||||
except ValueError:
|
||||
raise BadSyntax, "bad syntax in replace, needs to be in the format old::new in %s" % update_value
|
||||
# replace values were store as list
|
||||
old, new = update_value
|
||||
|
||||
try:
|
||||
entry_values.remove(old)
|
||||
except ValueError:
|
||||
@ -642,7 +722,7 @@ class LDAPUpdate:
|
||||
if message:
|
||||
self.debug("%s", message)
|
||||
self.debug("dn: %s", e.dn)
|
||||
for a, value in e.items():
|
||||
for a, value in e.raw.items():
|
||||
self.debug('%s:', a)
|
||||
for l in value:
|
||||
self.debug("\t%s", safe_output(a, l))
|
||||
|
@ -55,14 +55,15 @@ class update_default_range(Updater):
|
||||
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'),
|
||||
]
|
||||
|
||||
dn = DN(('cn', '%s_id_range' % self.api.env.realm),
|
||||
self.api.env.container_ranges, self.api.env.basedn)
|
||||
@ -129,12 +130,12 @@ class update_default_trust_view(Updater):
|
||||
self.api.env.basedn)
|
||||
|
||||
default_trust_view_entry = [
|
||||
'objectclass:top',
|
||||
'objectclass:ipaIDView',
|
||||
'cn:Default Trust View',
|
||||
'description:Default Trust View for AD users. '
|
||||
'Should not be deleted.',
|
||||
]
|
||||
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']:
|
||||
|
@ -99,7 +99,10 @@ class update_ca_renewal_master(Updater):
|
||||
dn = DN(('cn', 'CA'), ('cn', self.api.env.host), base_dn)
|
||||
update = {
|
||||
'dn': dn,
|
||||
'updates': ['add:ipaConfigString: caRenewalMaster'],
|
||||
'updates': [
|
||||
dict(action='add', attr='ipaConfigString',
|
||||
value='caRenewalMaster')
|
||||
],
|
||||
}
|
||||
|
||||
return False, [update]
|
||||
|
@ -129,7 +129,8 @@ class update_dns_limits(Updater):
|
||||
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))
|
||||
|
||||
dnsupdate = {'dn': dns_service_dn, 'updates': limit_updates}
|
||||
root_logger.debug("DNS: limits for service %s will be updated" % dns_service_dn)
|
||||
|
@ -34,9 +34,9 @@ def entry_to_update(entry):
|
||||
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])))
|
||||
update.append(dict(attr=str(attr), value=str(entry[attr][i])))
|
||||
else:
|
||||
update.append('%s:%s' % (str(attr), str(entry[attr])))
|
||||
update.append(dict(attr=str(attr), value=str(entry[attr])))
|
||||
|
||||
return update
|
||||
|
||||
|
@ -65,7 +65,10 @@ class update_passync_privilege_update(Updater):
|
||||
root_logger.debug("PassSync user found, do update")
|
||||
|
||||
update = {'dn': passsync_privilege_dn,
|
||||
'updates': ["add:member:'%s'" % passsync_dn]}
|
||||
'updates': [
|
||||
dict(action='add', attr='member', value=passsync_dn),
|
||||
]
|
||||
}
|
||||
|
||||
sysupgrade.set_upgrade_state('winsync', 'passsync_privilege_updated', True)
|
||||
return False, [update]
|
||||
|
@ -54,11 +54,11 @@ class update_uniqueness_plugins_to_new_syntax(Updater):
|
||||
plugins_dn = DN(('cn', 'plugins'), ('cn', 'config'))
|
||||
|
||||
def __remove_update(self, update, key, value):
|
||||
statement = "remove:%s:%s" % (key, value)
|
||||
statement = dict(action='remove', attr=key, value=value)
|
||||
update.setdefault('updates', []).append(statement)
|
||||
|
||||
def __add_update(self, update, key, value):
|
||||
statement = "add:%s:%s" % (key, value)
|
||||
statement = dict(action='add', attr=key, value=value)
|
||||
update.setdefault('updates', []).append(statement)
|
||||
|
||||
def __subtree_style(self, entry):
|
||||
|
Loading…
Reference in New Issue
Block a user