Blob Blame History Raw
From d1b87904462e890a855ac9d3b68ed02e089450d8 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Wed, 23 Dec 2015 15:04:40 +0200
Subject: [PATCH 07/12] slapi-nis: add support to resolve external members of
 IPA groups

FreeIPA allows to include external (non-LDAP) members into POSIX groups.
To define external members, an attribute ipaExternalMember is set to
the list of references to external members. Currently both FreeIPA and
SSSD support only references done with SIDs (Security Identifiers) from
the forests trusted by FreeIPA.

Resolving external members of FreeIPA groups requires resolving SIDs to
user and group names. However, since this resolution is already
implemented by SSSD for the group in question, slapi-nis can use the
fact that there is non-empty ipaExternalMember attribute's value to
trigger lookup of the FreeIPA group via SSSD and then copy over
memberUid attribute value set.

This logic requires that ipaExternalMember attribute value is present in
the entry to be put into the map cache. Thus, an additional
configuration is needed for the groups container:

schema-compat-entry-attribute: ipaexternalmember=%deref_r("member","ipaexternalmember")

Note that resolving external members of IPA groups requires to use
version of slapi-nis that populates the map cache after LDAP server
startup, as SSSD needs to talk back to the LDAP server in the process of
resolving external group members and that is not possible at the time
when slapi-nis plugin starts up as the LDAP server is not yet listenting
for incoming connections at that point.
---
 doc/ipa/sch-ipa.txt |  15 +++++++
 src/back-sch.c      | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 127 insertions(+)

diff --git a/doc/ipa/sch-ipa.txt b/doc/ipa/sch-ipa.txt
index 106e6cc..eb6238a 100644
--- a/doc/ipa/sch-ipa.txt
+++ b/doc/ipa/sch-ipa.txt
@@ -81,6 +81,21 @@ and groups from trusted domains. No additional configuration is needed.
 ipa-adtrust-install, however, will not set the minimal numeric id for user or
 group.
 
+Additionally, group area configuration should include following two attributes to
+allow resolving external members of IPA groups:
+
+schema-compat-entry-attribute: ipaexternalmember=%deref_r("member","ipaexternalmember")
+schema-compat-entry-attribute: objectclass=ipaexternalgroup
+
+When 'ipaExternalMember' attribute is present in the group entry generated by
+Schema Compatibility plugin, the plugin will attempt to retrieve the group
+members from SSSD daemon. If group has non-empty list of members, these new
+members will replace the original ones as they will include both IPA and external
+group members.
+
+SSSD greater than 1.13.3 is required to produce correct behavior due to bug
+https://fedorahosted.org/sssd/ticket/2522
+
 == Authentication of the trusted domains' users ==
 
 When the Schema Compatibility Plugin is configured to expose users from trusted
diff --git a/src/back-sch.c b/src/back-sch.c
index 98542c5..04fe667 100644
--- a/src/back-sch.c
+++ b/src/back-sch.c
@@ -419,6 +419,115 @@ backend_set_operational_attributes(Slapi_Entry *e,
 	}
 }
 
+#ifdef USE_NSSWITCH
+#define IPA_ATTR_EXTERNAL_MEMBER "ipaExternalMember"
+#define IPA_ATTR_MEMBERUID "memberUid"
+static void
+backend_set_process_external_members(Slapi_PBlock *pb,
+				   Slapi_Entry *e,
+				   struct plugin_state *state,
+				   struct backend_set_data *data)
+{
+	Slapi_Attr *attr = NULL;
+	Slapi_ValueSet *valueset = NULL;
+	bool_t is_attr_exists, is_group_exists;
+	struct backend_staged_search staged = {0, };
+	struct backend_search_cbdata cbdata = {0, };
+	char *plugin_id = state->plugin_desc->spd_id;
+
+	is_attr_exists = slapi_entry_attr_find(e, IPA_ATTR_EXTERNAL_MEMBER, &attr) == 0;
+
+	if (!is_attr_exists || attr == NULL) {
+		return;
+	}
+
+	/* There are external members in this entry, do group lookup via SSSD
+	 * and update entry's memberUid attribute */
+
+	staged.name = slapi_entry_attr_get_charptr(e, "cn");
+	staged.type = SCH_NSSWITCH_GROUP;
+	staged.search_members = FALSE;
+	staged.is_id = FALSE;
+	staged.is_sid = FALSE;
+	staged.container_sdn = (char*) slapi_sdn_get_dn(data->container_sdn);
+	staged.entries = NULL;
+	staged.count = 0;
+	cbdata.nsswitch_buffer_len = MAX(16384, MAX(sysconf(_SC_GETPW_R_SIZE_MAX), sysconf(_SC_GETGR_R_SIZE_MAX)));
+	cbdata.nsswitch_buffer = malloc(cbdata.nsswitch_buffer_len);
+	cbdata.state = state;
+	cbdata.staged = &staged;
+
+	slapi_log_error(SLAPI_LOG_PLUGIN, plugin_id,
+			"refreshing group membership for group \"%s\"\n", staged.name);
+
+	do {
+		/* This group must exist because it exists in the original tree
+		 * but as dirsrv was restarted, SSSD might still consider its domain offline. */
+		is_group_exists = backend_retrieve_from_nsswitch(&staged, &cbdata);
+		if (!is_group_exists) {
+			slapi_log_error(SLAPI_LOG_PLUGIN, plugin_id,
+					"group \"%s\" does not exist because SSSD is offline.",
+					staged.name);
+			if (state->ready_to_serve == 0) {
+				/* Only wait for SSSD when we populate the original set */
+				slapi_log_error(SLAPI_LOG_PLUGIN, plugin_id,
+						"waiting for SSSD to become online...");
+				DS_Sleep(PR_SecondsToInterval(35));
+			} else {
+				break;
+			}
+		}
+	} while (!is_group_exists);
+
+	if (staged.entries != NULL && staged.entries[0] != NULL) {
+			attr = NULL;
+			if (slapi_entry_attr_find(staged.entries[0], IPA_ATTR_MEMBERUID, &attr) == 0) {
+#if 0
+				/* Debug output of original and updated memberUid values */
+				char **ary1, **ary2;
+				ary1 = slapi_entry_attr_get_charray(e, "memberUid");
+				ary2 = slapi_entry_attr_get_charray(staged.entries[0], "memberUid");
+
+				slapi_log_error(SLAPI_LOG_FATAL, plugin_id,
+						"original group \"%s\":\n", staged.name);
+				for (int i = 0; ary1 && ary1[i] != NULL; ++i) {
+					slapi_log_error(SLAPI_LOG_FATAL, plugin_id,
+							"\t> %s\n", ary1[i]);
+				}
+				slapi_log_error(SLAPI_LOG_FATAL, plugin_id,
+						"new group \"%s\":\n", staged.name);
+				for (int i = 0; ary2 && ary2[i] != NULL; ++i) {
+					slapi_log_error(SLAPI_LOG_FATAL, plugin_id,
+							"\t> %s\n", ary2[i]);
+				}
+				slapi_ch_array_free(ary2);
+				slapi_ch_array_free(ary1);
+#endif
+
+				(void)slapi_attr_get_valueset(attr, &valueset);
+
+				if (slapi_entry_attr_find(e, IPA_ATTR_MEMBERUID, &attr) == 0) {
+					(void) slapi_entry_attr_delete(e, IPA_ATTR_MEMBERUID);
+				}
+				(void) slapi_entry_add_valueset(e, IPA_ATTR_MEMBERUID, valueset);
+				slapi_valueset_free(valueset);
+			} else {
+				slapi_log_error(SLAPI_LOG_PLUGIN, plugin_id,
+						"group \"%s\" doesn't have memberUid attribute\n", staged.name);
+			}
+			slapi_entry_free(staged.entries[0]);
+	}
+
+	if (staged.entries != NULL) {
+		free(staged.entries);
+	}
+
+	(void)slapi_entry_attr_delete(e, IPA_ATTR_EXTERNAL_MEMBER);
+	free(cbdata.nsswitch_buffer);
+	slapi_ch_free_string(&staged.name);
+}
+#endif
+
 /* Given a map-entry directory entry, determine a key, a value, and extra data
  * to be stored in the map cache, and add them to the map cache. */
 static void
@@ -613,6 +722,9 @@ backend_set_entry_from(Slapi_PBlock *pb, enum backend_entry_source source,
 		slapi_entry_add_string(entry,
 				       "objectClass", "extensibleObject");
 	}
+#ifdef USE_NSSWITCH
+	backend_set_process_external_members(pb, entry, data->common.state, data);
+#endif
 	/* Clean up the entry by doing a round trip through the LDIF parser. */
 	ldif = slapi_entry2str(entry, &len);
 	slapi_entry_free(entry);
-- 
2.5.0