Add support defaultNamingContext and add --basedn to migrate-ds

There are two sides to this, the server and client side.

On the server side we attempt to add a defaultNamingContext on already
installed servers. This will fail on older 389-ds instances but the
failure is not fatal. New installations on versions of 389-ds that
support this attribute will have it already defined.

On the client side we need to look for both defaultNamingContext and
namingContexts. We still need to check that the defaultNamingContext
is an IPA server (info=IPAV2).

The migration change also takes advantage of this and adds a new
option which allows one to provide a basedn to use instead of trying
to detect it.

https://fedorahosted.org/freeipa/ticket/1919
https://fedorahosted.org/freeipa/ticket/2314
This commit is contained in:
Rob Crittenden 2012-01-30 16:29:32 -05:00 committed by Martin Kosek
parent 37cdbae234
commit e889b82599
6 changed files with 100 additions and 42 deletions

View File

@ -1893,7 +1893,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('value', <type 'unicode'>, None) output: Output('value', <type 'unicode'>, None)
command: migrate_ds command: migrate_ds
args: 2,14,3 args: 2,15,3
arg: Str('ldapuri', cli_name='ldap_uri') arg: Str('ldapuri', cli_name='ldap_uri')
arg: Password('bindpw', cli_name='password', confirm=False) arg: Password('bindpw', cli_name='password', confirm=False)
option: Str('binddn?', autofill=True, cli_name='bind_dn', default=u'cn=directory manager') option: Str('binddn?', autofill=True, cli_name='bind_dn', default=u'cn=directory manager')
@ -1908,6 +1908,7 @@ option: Str('groupignoreattribute*', autofill=True, cli_name='group_ignore_attri
option: Flag('groupoverwritegid', autofill=True, cli_name='group_overwrite_gid', default=False) option: Flag('groupoverwritegid', autofill=True, cli_name='group_overwrite_gid', default=False)
option: StrEnum('schema?', autofill=True, cli_name='schema', default=u'RFC2307bis', values=(u'RFC2307bis', u'RFC2307')) option: StrEnum('schema?', autofill=True, cli_name='schema', default=u'RFC2307bis', values=(u'RFC2307bis', u'RFC2307'))
option: Flag('continue?', autofill=True, default=False) option: Flag('continue?', autofill=True, default=False)
option: Str('basedn?', cli_name='base_dn')
option: Str('exclude_groups*', autofill=True, cli_name='exclude_groups', csv=True, default=()) option: Str('exclude_groups*', autofill=True, cli_name='exclude_groups', csv=True, default=())
option: Str('exclude_users*', autofill=True, cli_name='exclude_users', csv=True, default=()) option: Str('exclude_users*', autofill=True, cli_name='exclude_users', csv=True, default=())
output: Output('result', <type 'dict'>, None) output: Output('result', <type 'dict'>, None)

View File

@ -79,4 +79,4 @@ IPA_DATA_VERSION=20100614120000
# # # #
######################################################## ########################################################
IPA_API_VERSION_MAJOR=2 IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=27 IPA_API_VERSION_MINOR=28

View File

@ -32,3 +32,9 @@ default:nsLookThroughLimit: 5000
dn: cn=config dn: cn=config
add:nsslapd-anonlimitsdn:cn=anonymous-limits,cn=etc,$SUFFIX add:nsslapd-anonlimitsdn:cn=anonymous-limits,cn=etc,$SUFFIX
# Add a defaultNamingContext if one hasn't already been set. This was
# introduced in 389-ds-base-1.2.10-0.9.a8. Adding this to a server that
# doesn't support it generates a non-fatal error.
dn: cn=config
add:nsslapd-defaultNamingContext:'$SUFFIX'

View File

@ -276,16 +276,52 @@ fail:
return NULL; return NULL;
} }
/*
* Given a list of naming contexts check each one to see if it has
* an IPA v2 server in it. The first one we find wins.
*/
static int
check_ipa_server(LDAP *ld, char **ldap_base, struct berval **vals)
{
struct berval **infovals;
LDAPMessage *entry, *res = NULL;
char *info_attrs[] = {"info", NULL};
int i, ret = 0;
for (i = 0; !*ldap_base && vals[i]; i++) {
ret = ldap_search_ext_s(ld, vals[i]->bv_val,
LDAP_SCOPE_BASE, "(info=IPA*)", info_attrs,
0, NULL, NULL, NULL, 0, &res);
if (ret != LDAP_SUCCESS) {
break;
}
entry = ldap_first_entry(ld, res);
infovals = ldap_get_values_len(ld, entry, info_attrs[0]);
if (!strcmp(infovals[0]->bv_val, "IPA V2.0"))
*ldap_base = strdup(vals[i]->bv_val);
ldap_msgfree(res);
res = NULL;
}
return ret;
}
/*
* Determine the baseDN of the remote server. Look first for a
* defaultNamingContext, otherwise fall back to reviewing each
* namingContext.
*/
static int static int
get_root_dn(const char *ipaserver, char **ldap_base) get_root_dn(const char *ipaserver, char **ldap_base)
{ {
LDAP *ld = NULL; LDAP *ld = NULL;
char *root_attrs[] = {"namingContexts", NULL}; char *root_attrs[] = {"namingContexts", "defaultNamingContext", NULL};
char *info_attrs[] = {"info", NULL};
LDAPMessage *entry, *res = NULL; LDAPMessage *entry, *res = NULL;
struct berval **ncvals; struct berval **ncvals;
struct berval **infovals; struct berval **defvals;
int i, ret, rval = 0; int ret, rval = 0;
ld = connect_ldap(ipaserver, NULL, NULL); ld = connect_ldap(ipaserver, NULL, NULL);
if (!ld) { if (!ld) {
@ -306,33 +342,27 @@ get_root_dn(const char *ipaserver, char **ldap_base)
*ldap_base = NULL; *ldap_base = NULL;
/* loop through to find the IPA context */
entry = ldap_first_entry(ld, res); entry = ldap_first_entry(ld, res);
ncvals = ldap_get_values_len(ld, entry, root_attrs[0]);
if (!ncvals) {
fprintf(stderr, _("No values for %s"), root_attrs[0]);
rval = 14;
goto done;
}
for (i = 0; !*ldap_base && ncvals[i]; i++) {
ret = ldap_search_ext_s(ld, ncvals[i]->bv_val,
LDAP_SCOPE_BASE, "(info=IPA*)", info_attrs,
0, NULL, NULL, NULL, 0, &res);
if (ret != LDAP_SUCCESS) { defvals = ldap_get_values_len(ld, entry, root_attrs[1]);
break; if (defvals) {
ret = check_ipa_server(ld, ldap_base, defvals);
}
ldap_value_free_len(defvals);
/* loop through to find the IPA context */
if (ret == LDAP_SUCCESS && !*ldap_base) {
ncvals = ldap_get_values_len(ld, entry, root_attrs[0]);
if (!ncvals) {
fprintf(stderr, _("No values for %s"), root_attrs[0]);
rval = 14;
ldap_value_free_len(ncvals);
goto done;
} }
ret = check_ipa_server(ld, ldap_base, ncvals);
entry = ldap_first_entry(ld, res); ldap_value_free_len(ncvals);
infovals = ldap_get_values_len(ld, entry, info_attrs[0]);
if (!strcmp(infovals[0]->bv_val, "IPA V2.0"))
*ldap_base = strdup(ncvals[i]->bv_val);
ldap_msgfree(res);
res = NULL;
} }
ldap_value_free_len(ncvals);
if (ret != LDAP_SUCCESS) { if (ret != LDAP_SUCCESS) {
fprintf(stderr, _("Search for IPA namingContext failed with error %d\n"), ret); fprintf(stderr, _("Search for IPA namingContext failed with error %d\n"), ret);
rval = 14; rval = 14;

View File

@ -62,6 +62,10 @@ enable it:
ipa config-mod --enable-migration=TRUE ipa config-mod --enable-migration=TRUE
If a base DN is not provided with --basedn then IPA will use either
the value of defaultNamingContext if it is set or the first value
in namingContexts set in the root of the remote LDAP server.
EXAMPLES: EXAMPLES:
The simplest migration, accepting all defaults: The simplest migration, accepting all defaults:
@ -353,14 +357,14 @@ class migrate_ds(Command):
Str('usercontainer?', Str('usercontainer?',
cli_name='user_container', cli_name='user_container',
label=_('User container'), label=_('User container'),
doc=_('RDN of container for users in DS'), doc=_('RDN of container for users in DS relative to base DN'),
default=u'ou=people', default=u'ou=people',
autofill=True, autofill=True,
), ),
Str('groupcontainer?', Str('groupcontainer?',
cli_name='group_container', cli_name='group_container',
label=_('Group container'), label=_('Group container'),
doc=_('RDN of container for groups in DS'), doc=_('RDN of container for groups in DS relative to base DN'),
default=u'ou=groups', default=u'ou=groups',
autofill=True, autofill=True,
), ),
@ -431,6 +435,11 @@ class migrate_ds(Command):
doc=_('Continuous operation mode. Errors are reported but the process continues'), doc=_('Continuous operation mode. Errors are reported but the process continues'),
default=False, default=False,
), ),
Str('basedn?',
cli_name='base_dn',
label=_('Base DN'),
doc=_('Base DN on remote LDAP server'),
),
) )
has_output = ( has_output = (
@ -526,14 +535,14 @@ can use their Kerberos accounts.''')
try: try:
(entries, truncated) = ds_ldap.find_entries( (entries, truncated) = ds_ldap.find_entries(
search_filter, ['*'], search_bases[ldap_obj_name], search_filter, ['*'], search_bases[ldap_obj_name],
ds_ldap.SCOPE_ONELEVEL, _ldap.SCOPE_ONELEVEL,
time_limit=0, size_limit=-1, time_limit=0, size_limit=-1,
search_refs=True # migrated DS may contain search references search_refs=True # migrated DS may contain search references
) )
except errors.NotFound: except errors.NotFound:
if not options.get('continue',False): if not options.get('continue',False):
raise errors.NotFound( raise errors.NotFound(
reason=_('Container for %(container)s not found') % {'container': ldap_obj_name} reason=_('Container for %(container)s not found at %(search_base)s') % {'container': ldap_obj_name, 'search_base': search_bases[ldap_obj_name]}
) )
else: else:
truncated = False truncated = False
@ -627,6 +636,8 @@ can use their Kerberos accounts.''')
config = ldap.get_ipa_config()[1] config = ldap.get_ipa_config()[1]
ds_base_dn = options.get('basedn')
# check if migration mode is enabled # check if migration mode is enabled
if config.get('ipamigrationenabled', ('FALSE', ))[0] == 'FALSE': if config.get('ipamigrationenabled', ('FALSE', ))[0] == 'FALSE':
return dict(result={}, failed={}, enabled=False) return dict(result={}, failed={}, enabled=False)
@ -635,15 +646,19 @@ can use their Kerberos accounts.''')
ds_ldap = ldap2(shared_instance=False, ldap_uri=ldapuri, base_dn='') ds_ldap = ldap2(shared_instance=False, ldap_uri=ldapuri, base_dn='')
ds_ldap.connect(bind_dn=options['binddn'], bind_pw=bindpw) ds_ldap.connect(bind_dn=options['binddn'], bind_pw=bindpw)
# retrieve DS base DN if not ds_base_dn:
(entries, truncated) = ds_ldap.find_entries( # retrieve base DN from remote LDAP server
'', ['namingcontexts'], '', ds_ldap.SCOPE_BASE, (entries, truncated) = ds_ldap.find_entries(
size_limit=-1, time_limit=0, '', ['namingcontexts', 'defaultnamingcontext'], '',
) _ldap.SCOPE_BASE, size_limit=-1, time_limit=0,
try: )
ds_base_dn = entries[0][1]['namingcontexts'][0] if 'defaultnamingcontext' in entries[0][1]:
except (IndexError, KeyError), e: ds_base_dn = entries[0][1]['defaultnamingcontext'][0]
raise StandardError(str(e)) else:
try:
ds_base_dn = entries[0][1]['namingcontexts'][0]
except (IndexError, KeyError), e:
raise StandardError(str(e))
# migrate! # migrate!
(migrated, failed) = self.migrate( (migrated, failed) = self.migrate(

View File

@ -1191,10 +1191,16 @@ def get_ipa_basedn(conn):
:param conn: Bound LDAP connection that will be used for searching :param conn: Bound LDAP connection that will be used for searching
""" """
entries = conn.search_ext_s( entries = conn.search_ext_s(
'', scope=ldap.SCOPE_BASE, attrlist=['namingcontexts'] '', scope=ldap.SCOPE_BASE, attrlist=['defaultnamingcontext', 'namingcontexts']
) )
contexts = entries[0][1]['namingcontexts'] contexts = entries[0][1]['namingcontexts']
if entries[0][1].get('defaultnamingcontext'):
# If there is a defaultNamingContext examine that one first
default = entries[0][1]['defaultnamingcontext'][0]
if default in contexts:
contexts.remove(default)
contexts.insert(0, entries[0][1]['defaultnamingcontext'][0])
for context in contexts: for context in contexts:
root_logger.debug("Check if naming context '%s' is for IPA" % context) root_logger.debug("Check if naming context '%s' is for IPA" % context)
try: try: