[sudo-workers] Update to ldap.c

Steven Soulen soulen3 at gmail.com
Fri Nov 21 16:03:42 MST 2014


Hello,



I support an environment with several thousand sudo rules and we are
listing netgroups as sudoUser.  We have recently discovered performance
issues while using LDAP for sudo configuration. These issues are caused by
the large number of netgroup lookups while checking rules that are returned
by the "sudoUser=+*" LDAP filter.


I found a couple of references to this on the mailing lists but couldn't
find a solution that worked in our environment. So to reduce the number of
of lookups, I’ve modified the ldap.c file to support running ldap searches
against a netgroup container and then skipping the "sudoUser=+*" search.
In our environment, this change has reduced sudo's run time in by a factor
of 10.


This required the addition of new configuration options to the ldap
configration file that point at a netgroup container.  I've based these
additions on the sudoers_base and sudoers_search_filter configuration
items.


I’ve added the sudo_netgroup_lookup function which runs during the
sudo_ldap_build_pass1 function.  The new function builds a list of
netgroups that the user is a member of, and then recursively searches for
netgroup that those netgroups are members of.  This list is then included
in first LDAP search filter and second pass is skipped. If the
netgroup_base configuration item is not listed there should be no change in
how sudo functions.



Can you please consider adding this to the next release?  Also please let
me know if anything needs clarification or needs to be changed.


Example Configuration:

netgroup_base  ou=netgroup ,dc=example,dc=com

netgroup_search_filter (objectClass=nisNetgroup)



Debugging Output:

~

sudo: Looking up netgroups

sudo: searching from netgroup_base 'ou=netgroup,dc=example,dc=com'

sudo: ldap netgroup search filter:
'(&(objectClass=nisNetgroup)(|(nisNetgroupTriple=\(,root,*\))(nisNetgroupTriple=\(hostname,root,*\))))'

sudo: Add negroup example to list for ou=netgroup,dc=example,dc=com

sudo: Looking up netgroups

sudo: Checking for nested netgroups from netgroup_base
'ou=netgroup,dc=example,dc=com'

sudo: ldap netgroup search filter:
'(&(objectClass=nisNetgroup)(|(memberNisNetgroup=example)))'

sudo: Add negroup example1 to list for ou=netgroup,dc=example,dc=com

sudo: Looking up netgroups

sudo: Checking for nested netgroups from netgroup_base
'ou=netgroup,dc=example,dc=com'

sudo: ldap netgroup search filter:
'(&(objectClass=nisNetgroup)(|(memberNisNetgroup=tncecom)(example1)))'

sudo: No new results found for ou=netgroup,dc=example,dc=com

sudo: ldap search
'(&(objectClass=sudoRole)(|(sudoUser=root)(sudoUser=%root)(sudoUser=+example)(sudoUser=+example1)(sudoUser=ALL)))'

sudo: searching from base 'ou=sudoers,dc=example,dc=com'

sudo: adding search result

 ~


Description of the new configuration Options:

*NETGROUP_BASE*  *base*

The base DN to use when performing *netgroup* LDAP queries. Typically this
is of the formou=netgroup,dc=example,dc=com for the domain example.com.
This feature bypasses the system’s netgroup configuration and queries LDAP
directly for netgroup information. If this value is not defined, sudo will
use the system’s authentication system.

*NETGROUP_SEARCH_FILTER* *ldap_filter*

An LDAP filter which is used to restrict the set of records returned when
performing a *netgrup* LDAP query. Typically, this is of the form
attribute=value or (&(attribute=value)(attribute2=value2)).



Diff of ldap.c:

diff -r da17d5a611ce plugins/sudoers/ldap.c
--- a/plugins/sudoers/ldap.c  Thu Nov 20 13:34:17 2014 -0700
+++ b/plugins/sudoers/ldap.c  Fri Nov 21 15:17:03 2014 -0600
@@ -148,6 +148,9 @@
/* Default search filter. */
#define DEFAULT_SEARCH_FILTER      "(objectClass=sudoRole)"

+/* Default netgroup search filter. */
+#define DEFAULT_NETGROUP_SEARCH_FILTER   "(objectClass=nisNetgroup)"
+
/* The TIMEFILTER_LENGTH is the length of the filter when timed entries
    are used. The length is computed as follows:
        81       for the filter itself
@@ -228,7 +231,9 @@
     char *bindpw;
     char *rootbinddn;
     struct ldap_config_str_list base;
+    struct ldap_config_str_list netgroup_base;
     char *search_filter;
+    char *netgroup_search_filter;
     char *ssl;
     char *tls_cacertfile;
     char *tls_cacertdir;
@@ -301,6 +306,8 @@
     { "sudoers_base", CONF_LIST_STR, -1, &ldap_conf.base },
     { "sudoers_timed", CONF_BOOL, -1, &ldap_conf.timed },
     { "sudoers_search_filter", CONF_STR, -1, &ldap_conf.search_filter },
+    { "netgroup_base", CONF_LIST_STR, -1, &ldap_conf.netgroup_base },
+    { "netgroup_search_filter", CONF_STR, -1,
&ldap_conf.netgroup_search_filter },
#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S
     { "use_sasl", CONF_BOOL, -1, &ldap_conf.use_sasl },
     { "sasl_auth_id", CONF_STR, -1, &ldap_conf.sasl_auth_id },
@@ -1222,16 +1229,185 @@
}

 /*
+ * Create struct to hold netgroup names
+ */
+struct netgroups{
+     char *netgroup;
+     char *base;
+     bool checked;
+};
+/*
+ * Lookup netgroups that the users is a member of, returns the size of the
netgroup array
+ */
+static bool
+sudo_netgroup_lookup(struct sudo_nss *nss, struct netgroups
**netgrp,struct passwd *pw, int *netgrp_count)
+{
+           char *filt;
+           struct sudo_ldap_handle *handle = nss->handle;
+           int i,rc;
+           int results_size,netgrp_size,results_count=0,buf_netgrp_count=0;
+           struct netgroups *buf;
+           struct ldap_config_str *netgroup_base;
+           struct ldap_result *lres;
+           struct timeval tv, *tvp = NULL;
+           LDAPMessage *entry, *result;
+           LDAP *ld = handle->ld;
+           debug_decl(sudo_netgroup_lookup, SUDOERS_DEBUG_LDAP,
sudoers_debug_instance);
+
+           //Recalculate the array size
+           for( i = 0;i<*netgrp_count;i++){
+
netgrp_size+=strlen((*netgrp)[i].netgroup)+strlen((*netgrp)[i].base)+sizeof(bool);
+           }
+
+           STAILQ_FOREACH(netgroup_base, &ldap_conf.netgroup_base,
entries){
+                       if (ldap_conf.timeout > 0) {
+                             tv.tv_sec = ldap_conf.timeout;
+                             tv.tv_usec = 0;
+                             tvp = &tv;
+                       }
+
+                       //Build Search String
+                       if(!*netgrp_count){
+                             DPRINTF1("searching from netgroup_base
'%s'",netgroup_base->val);
+                             int
filt_len=2*strlen(pw->pw_name)+2*strlen(user_shost)+64+strlen(ldap_conf.netgroup_search_filter);
+                             filt = sudo_emalloc(filt_len);
+                             (void) strlcpy(filt, "(&",filt_len);
+                             (void)
strlcat(filt,ldap_conf.netgroup_search_filter,filt_len);
+                             (void)
strlcat(filt,"(|(nisNetgroupTriple=\\(,", filt_len);
+                             (void) strlcat(filt,pw->pw_name,filt_len);
+                             (void)
strlcat(filt,",*\\))(nisNetgroupTriple=\\(",filt_len);
+                             (void) strlcat(filt,user_shost,filt_len);
+                             (void) strlcat(filt,",",filt_len);
+                             (void) strlcat(filt,pw->pw_name,filt_len);
+                             (void) strlcat(filt,",*\\))))",filt_len);
+                       }
+                       else{
+                             DPRINTF1("Checking for nested netgroups from
netgroup_base '%s'",netgroup_base->val);
+                             int netgrp_filt_count=0;
+                             int
filt_len=7+strlen(ldap_conf.netgroup_search_filter);
+                             //Build size indexes and check for any
untested netgroups
+                             for( i =
0;i<*netgrp_count+buf_netgrp_count;i++){
+                                   if(!(*netgrp)[i].checked  &&
((*netgrp)[i].base == netgroup_base->val)){
+                                         netgrp_filt_count++;
+
filt_len+=strlen((*netgrp)[i].netgroup);
+                                   }
+                             }
+                             filt_len+=20*netgrp_filt_count;
+                             //break loop if there is nothing to search for
+                             if(!netgrp_filt_count){
+                        continue;
+                             }
+                             filt = sudo_emalloc(filt_len);
+                             (void) strlcpy(filt,"(&",filt_len);
+                             (void)
strlcat(filt,ldap_conf.netgroup_search_filter,filt_len);
+                             (void) strlcat(filt,"(|",filt_len);
+                             for( i =
0;i<*netgrp_count+buf_netgrp_count;i++){
+                                   if(!(*netgrp)[i].checked  &&
((*netgrp)[i].base == netgroup_base->val)){
+                                         (void) strlcat(filt,
"(memberNisNetgroup=", filt_len);
+                                         (void) sudo_ldap_value_cat(filt,
(*netgrp)[i].netgroup, filt_len);
+                                         (void) strlcat(filt, ")",
filt_len);
+                                         (*netgrp)[i].checked=true;
+                                   }
+                             }
+                             (void) strlcat(filt, "))", filt_len);
+                       }
+                       DPRINTF1("ldap netgroup search filter: '%s'", filt);
+                       result = NULL;
+                       results_count=0;
+                       results_size=0;
+                       rc = ldap_search_ext_s(ld, netgroup_base->val,
LDAP_SCOPE_SUBTREE, filt,
+                                   NULL,0, NULL, NULL, NULL, 0, &result);
+                       if (rc != LDAP_SUCCESS) {
+                             DPRINTF1("nothing found for '%s'",filt);
+                             continue;
+                       }
+                       else{
+                             bool check_existing;
+                             LDAP_FOREACH(entry, ld, result) {
+                                   check_existing=0;
+                                   struct berval **bv;
+                                   bv = ldap_get_values_len(ld, entry,
"cn");
+                                   //Should only return 1 value
+                                   if (bv != NULL) {
+                                         //Don't add a netgroup twice
+                                         for( i = 0;i<*netgrp_count;i++){
+                                if((*netgrp)[i].netgroup == (*bv)->bv_val
&& (*netgrp)[i].base == netgroup_base->val){
+                                                     check_existing=1;
+                                                     break;
+                                               }
+                             }
+                                         //Build sizing indexes
+                                         if(!check_existing){
+
results_size+=strlen((*bv)->bv_val)+strlen(netgroup_base->val)+sizeof(bool);
+                                               results_count++;
+                                         }
+                                   }
+                             }
+                             //Check if we found any new netgroups
+                             if(!results_count){
+                                   DPRINTF1("No new results found for
%s",netgroup_base->val);
+                                   continue;
+                             }
+                             results_count=0;
+                             buf=sudo_emalloc(results_size);
+                             LDAP_FOREACH(entry, ld, result) {
+                                   struct berval **bv,**p;
+                                   bv = ldap_get_values_len(ld, entry,
"cn");
+                                   if (bv != NULL) {
+                                         if(!check_existing){
+
buf[results_count].netgroup=(*bv)->bv_val;
+
buf[results_count].base=netgroup_base->val;
+
buf[results_count].checked=0;
+                                               results_count++;
+                                         }
+                                   }
+                             }
+                       }
+                       if(!(*netgrp_count+buf_netgrp_count)){
+                             *netgrp=sudo_emalloc(results_size);
+                       }
+                       else{
+
*netgrp=sudo_erealloc(*netgrp,results_size+netgrp_size);
+                       }
+                       for(i=0; i<results_count ;i++){
+
(*netgrp)[i+*netgrp_count+buf_netgrp_count].netgroup=buf[i].netgroup;
+
(*netgrp)[i+*netgrp_count+buf_netgrp_count].base=buf[i].base;
+
(*netgrp)[i+*netgrp_count+buf_netgrp_count].checked=buf[i].checked;
+                             DPRINTF1("Add negroup %s to list for
%s",buf[i].netgroup,netgroup_base->val);
+                 }
+                       sudo_efree(buf);
+                       sudo_efree(filt);
+                       sudo_efree(result);
+                       buf_netgrp_count+=results_count;
+                       netgrp_size+=results_size;
+           }
+     *netgrp_count+=buf_netgrp_count;
+     //Check if we can end loop, return false by default to prevent
infinite loop
+     bool END_SEARCH=1;
+     for( i = 0;i<*netgrp_count;i++){
+        if(!(*netgrp)[i].checked){
+                 END_SEARCH=0;
+                 break;
+        }
+    }
+     if (!END_SEARCH){
+           debug_return_bool(1);
+     }
+     debug_return_bool(0);
+};
+
+/*
  * Builds up a filter to check against LDAP.
  */
static char *
-sudo_ldap_build_pass1(struct passwd *pw)
+sudo_ldap_build_pass1(struct sudo_nss *nss, struct passwd *pw)
{
     struct group *grp;
+     struct netgroups *netgrp;
     char *buf, timebuffer[TIMEFILTER_LENGTH + 1], gidbuf[MAX_UID_T_LEN +
1];
     struct group_list *grlist;
     size_t sz = 0;
-    int i;
+    int i,netgroup_count=0;
     debug_decl(sudo_ldap_build_pass1, SUDOERS_DEBUG_LDAP,
sudoers_debug_instance)

     /* If there is a filter, allocate space for the global AND. */
@@ -1262,6 +1438,17 @@
         sz += 13 + MAX_UID_T_LEN;
     }
     }
+     if (!STAILQ_EMPTY(&ldap_conf.netgroup_base)){
+     bool keep_looking=true;
+     netgrp=NULL;
+     while(keep_looking){
+           DPRINTF1("Looking up netgroups");
+
keep_looking=sudo_netgroup_lookup(nss,&netgrp,pw,&netgroup_count);
+           }
+           for( i = 0;i<netgroup_count;i++){
+           sz += 14 + strlen( netgrp[i].netgroup );
+           }
+     }

     /* If timed, add space for time limits. */
     if (ldap_conf.timed)
@@ -1321,6 +1508,15 @@
     if (grp != NULL)
     sudo_gr_delref(grp);

+     /* Add Netgroups */
+     if(netgroup_count != 0){
+           for(i=0;i<netgroup_count;i++){
+           (void) strlcat(buf, "(sudoUser=+", sz);
+           (void) sudo_ldap_value_cat(buf, netgrp[i].netgroup, sz);
+           (void) strlcat(buf, ")", sz);
+           }
+     }
+
     /* Add ALL to list and end the global OR */
     if (strlcat(buf, "(sudoUser=ALL)", sz) >= sz) {
     sudo_warnx(U_("sudo_ldap_build_pass1 allocation mismatch"));
@@ -1569,8 +1765,10 @@
     ldap_conf.rootuse_sasl = -1;
     ldap_conf.deref = -1;
     ldap_conf.search_filter = sudo_estrdup(DEFAULT_SEARCH_FILTER);
+    ldap_conf.netgroup_search_filter =
sudo_estrdup(DEFAULT_NETGROUP_SEARCH_FILTER);
     STAILQ_INIT(&ldap_conf.uri);
     STAILQ_INIT(&ldap_conf.base);
+    STAILQ_INIT(&ldap_conf.netgroup_base);

     if ((fp = fopen(path_ldap_conf, "r")) == NULL)
     debug_return_bool(false);
@@ -1627,6 +1825,17 @@
     if (ldap_conf.search_filter) {
     DPRINTF1("search_filter    %s", ldap_conf.search_filter);
     }
+     if (!STAILQ_EMPTY(&ldap_conf.netgroup_base)) {
+     struct ldap_config_str *netgroup_base;
+     STAILQ_FOREACH(netgroup_base, &ldap_conf.netgroup_base, entries) {
+           DPRINTF1("sudoers_netgroup_base     %s", netgroup_base->val);
+     }
+     } else {
+     DPRINTF1("sudoers_netgroup_base     %s", "(NONE: Will use nsswitch)");
+     }
+     if (ldap_conf.netgroup_search_filter) {
+        DPRINTF1("netgroup_search_filter    %s",
ldap_conf.netgroup_search_filter);
+    }
     DPRINTF1("binddn           %s",
     ldap_conf.binddn ? ldap_conf.binddn : "(anonymous)");
     DPRINTF1("bindpw           %s",
@@ -2943,7 +3152,7 @@
      */
     lres = sudo_ldap_result_alloc();
     for (pass = 0; pass < 2; pass++) {
-     filt = pass ? sudo_ldap_build_pass2() : sudo_ldap_build_pass1(pw);
+     filt = pass ? sudo_ldap_build_pass2() : sudo_ldap_build_pass1(nss,pw);
     if (filt != NULL) {
         DPRINTF1("ldap search '%s'", filt);
         STAILQ_FOREACH(base, &ldap_conf.base, entries) {
@@ -2978,6 +3187,9 @@
         }
         sudo_efree(filt);
     }
+     if (!STAILQ_EMPTY(&ldap_conf.netgroup_base)){
+           break;
+     }
     }

     /* Sort the entries by the sudoOrder attribute. */





Thanks,


More information about the sudo-workers mailing list