[PATCH] Modify samba-tool group commands to view summary of group memberships

Tim Beale timbeale at catalyst.net.nz
Wed Oct 24 22:48:20 UTC 2018


Currently we don't have a great idea of what group memberships look like
in a typical AD network, e.g. the max number of group members. Obviously
this will vary from network to network. But for a large database, the
number of members in a group could potentially have a big impact on
whether a particular bug occurs or not, e.g. 1,000 members vs 10,000
members...

This patch modifies the 'samba-tool group' commands to display more
information about group membership. Specifically:
* Extend 'samba-tool group list --verbose' to include how many members
are in each group.
* Add a new 'samba-tool group stats' to display summary information
about the group memberships for the domain.

Example output from the commands is attached.

CI link: https://gitlab.com/catalyst-samba/samba/pipelines/34087257

Review appreciated. Thanks.

-------------- next part --------------
From c06e41180cce80f7e3e3ad631d81dd3cdc57808a Mon Sep 17 00:00:00 2001
From: Tim Beale <timbeale at catalyst.net.nz>
Date: Thu, 18 Oct 2018 16:59:24 +1300
Subject: [PATCH 1/2] netcmd: Include num-members in 'samba-tool group list
 --verbose'

This adds an easy way for users to see (via samba-tool) how many members
are in various groups, without querying the members for each individual
group.

For example, you could pipe this output to grep to check for groups with
zero or one members (i.e. historic groups that may no longer make
sense).

Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
---
 python/samba/netcmd/group.py | 26 +++++++++++++++-----------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/python/samba/netcmd/group.py b/python/samba/netcmd/group.py
index 5158357..51967ff 100644
--- a/python/samba/netcmd/group.py
+++ b/python/samba/netcmd/group.py
@@ -324,37 +324,41 @@ class cmd_group_list(Command):
 
         samdb = SamDB(url=H, session_info=system_session(),
                       credentials=creds, lp=lp)
+        attrs=["samaccountname"]
 
+        if verbose:
+            attrs += ["grouptype", "member"]
         domain_dn = samdb.domain_dn()
         res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
                            expression=("(objectClass=group)"),
-                           attrs=["samaccountname", "grouptype"])
+                           attrs=attrs)
         if (len(res) == 0):
             return
 
         if verbose:
-            self.outf.write("Group Name                                  Group Type      Group Scope\n")
-            self.outf.write("-----------------------------------------------------------------------------\n")
+            self.outf.write("Group Name                                  Group Type      Group Scope  Members\n")
+            self.outf.write("--------------------------------------------------------------------------------\n")
 
             for msg in res:
                 self.outf.write("%-44s" % msg.get("samaccountname", idx=0))
                 hgtype = hex(int("%s" % msg["grouptype"]) & 0x00000000FFFFFFFF)
                 if (hgtype == hex(int(security_group.get("Builtin")))):
-                    self.outf.write("Security         Builtin\n")
+                    self.outf.write("Security         Builtin  ")
                 elif (hgtype == hex(int(security_group.get("Domain")))):
-                    self.outf.write("Security         Domain\n")
+                    self.outf.write("Security         Domain   ")
                 elif (hgtype == hex(int(security_group.get("Global")))):
-                    self.outf.write("Security         Global\n")
+                    self.outf.write("Security         Global   ")
                 elif (hgtype == hex(int(security_group.get("Universal")))):
-                    self.outf.write("Security         Universal\n")
+                    self.outf.write("Security         Universal")
                 elif (hgtype == hex(int(distribution_group.get("Global")))):
-                    self.outf.write("Distribution     Global\n")
+                    self.outf.write("Distribution     Global   ")
                 elif (hgtype == hex(int(distribution_group.get("Domain")))):
-                    self.outf.write("Distribution     Domain\n")
+                    self.outf.write("Distribution     Domain   ")
                 elif (hgtype == hex(int(distribution_group.get("Universal")))):
-                    self.outf.write("Distribution     Universal\n")
+                    self.outf.write("Distribution     Universal")
                 else:
-                    self.outf.write("\n")
+                    self.outf.write("                          ")
+                self.outf.write("   %u\n" % len(msg.get("member", default=[])))
         else:
             for msg in res:
                 self.outf.write("%s\n" % msg.get("samaccountname", idx=0))
-- 
2.7.4


From 7f2e63fd479e1062277a1c20f0a6ece9ab638a87 Mon Sep 17 00:00:00 2001
From: Tim Beale <timbeale at catalyst.net.nz>
Date: Thu, 18 Oct 2018 17:08:32 +1300
Subject: [PATCH 2/2] netcmd: Add 'samba-tool group stats' command

With large domains it's hard to get an idea of how many groups there
are, and how many users are in each group, on average. However, this
could have a big impact on whether a problem can be reproduced or not.

This patch dumps out some summary information so that you can get a
quick idea of how big the groups are.

Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
---
 docs-xml/manpages/samba-tool.8.xml     |  5 ++
 python/samba/netcmd/group.py           | 98 ++++++++++++++++++++++++++++++++++
 python/samba/tests/samba_tool/group.py | 18 +++++++
 3 files changed, 121 insertions(+)

diff --git a/docs-xml/manpages/samba-tool.8.xml b/docs-xml/manpages/samba-tool.8.xml
index 2c043b9..01f5313 100644
--- a/docs-xml/manpages/samba-tool.8.xml
+++ b/docs-xml/manpages/samba-tool.8.xml
@@ -644,6 +644,11 @@
 	<para>Show group object and it's attributes.</para>
 </refsect3>
 
+<refsect3>
+	<title>group stats [options]</title>
+	<para>Show statistics for overall groups and group memberships.</para>
+</refsect3>
+
 <refsect2>
 	<title>ldapcmp <replaceable>URL1</replaceable> <replaceable>URL2</replaceable> <replaceable>domain|configuration|schema|dnsdomain|dnsforest</replaceable> [options] </title>
 	<para>Compare two LDAP databases.</para>
diff --git a/python/samba/netcmd/group.py b/python/samba/netcmd/group.py
index 51967ff..3546425 100644
--- a/python/samba/netcmd/group.py
+++ b/python/samba/netcmd/group.py
@@ -35,6 +35,7 @@ from samba.dsdb import (
     GTYPE_DISTRIBUTION_GLOBAL_GROUP,
     GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
 )
+from collections import defaultdict
 
 security_group = dict({"Builtin": GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
                        "Domain": GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
@@ -588,6 +589,102 @@ Example3 shows how to display a users objectGUID and member attributes.
             self.outf.write(user_ldif)
 
 
+class cmd_group_stats(Command):
+    """Summary statistics about group memberships."""
+
+    synopsis = "%prog [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+               metavar="URL", dest="H"),
+    ]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+    }
+
+    def num_in_range(self, range_min, range_max, group_freqs):
+        total_count = 0
+        for members, count in group_freqs.items():
+            if range_min <= members and members <= range_max:
+                total_count += count
+
+        return total_count
+
+    def run(self, sambaopts=None, credopts=None, versionopts=None, H=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp, fallback_machine=True)
+
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        domain_dn = samdb.domain_dn()
+        res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE,
+                           expression=("(objectClass=group)"),
+                           attrs=["samaccountname", "member"])
+
+        # first count up how many members each group has
+        group_assignments = {}
+        total_memberships = 0
+
+        for msg in res:
+            name = str(msg.get("samaccountname"))
+            memberships = len(msg.get("member", default=[]))
+            group_assignments[name] = memberships
+            total_memberships += memberships
+
+        self.outf.write("Group membership statistics*\n")
+        self.outf.write("-------------------------------------------------\n")
+        self.outf.write("Total groups: {0}\n".format(res.count))
+        self.outf.write("Total memberships: {0}\n".format(total_memberships))
+        average = float(total_memberships / res.count)
+        self.outf.write("Average members per group: %.2f\n" % average)
+        group_names = list(group_assignments.keys())
+        group_members = list(group_assignments.values())
+        # note that some builtin groups have no members, so this doesn't tell us much
+        idx = group_members.index(min(group_members))
+        self.outf.write("Min members: {0} ({1})\n".format(group_members[idx],
+                                                          group_names[idx]))
+        idx = group_members.index(max(group_members))
+        max_members = group_members[idx]
+        self.outf.write("Max members: {0} ({1})\n\n".format(max_members,
+                                                            group_names[idx]))
+
+        # convert this to the frequency of group membership, i.e. how many
+        # groups have 5 members, how many have 6 members, etc
+        group_freqs = defaultdict(int)
+        for group, count in group_assignments.items():
+            group_freqs[count] += 1
+
+        # now squash this down even further, so that we just display the number
+        # of groups that fall into one of the following membership bands
+        bands = [(0, 1), (2, 4), (5, 9), (10, 14), (15, 19), (20, 24), (25, 29),
+                 (30, 39), (40, 49), (50, 59), (60, 69), (70, 79), (80, 89),
+                 (90, 99), (100, 149), (150, 199), (200, 249), (250, 299),
+                 (300, 399), (400, 499), (500, 999), (1000, 1999),
+                 (2000, 2999), (3000, 3999), (4000, 4999), (5000, 9999),
+                 (10000, max_members)]
+
+        self.outf.write("Members        Number of Groups\n")
+        self.outf.write("-------------------------------------------------\n")
+
+        for band in bands:
+            band_start = band[0]
+            band_end = band[1]
+            if band_start > max_members:
+                break
+
+            num_groups = self.num_in_range(band_start, band_end, group_freqs)
+
+            if num_groups != 0:
+                band_str = "{0}-{1}".format(band_start, band_end)
+                self.outf.write("%13s  %u\n" % (band_str, num_groups))
+
+        self.outf.write("\n* Note this does not include nested group memberships\n")
+
+
 class cmd_group(SuperCommand):
     """Group management."""
 
@@ -600,3 +697,4 @@ class cmd_group(SuperCommand):
     subcommands["listmembers"] = cmd_group_list_members()
     subcommands["move"] = cmd_group_move()
     subcommands["show"] = cmd_group_show()
+    subcommands["stats"] = cmd_group_stats()
diff --git a/python/samba/tests/samba_tool/group.py b/python/samba/tests/samba_tool/group.py
index 7a5fd96..bb701e9 100644
--- a/python/samba/tests/samba_tool/group.py
+++ b/python/samba/tests/samba_tool/group.py
@@ -208,3 +208,21 @@ class GroupCmdTestCase(SambaToolCmdTest):
             return grouplist[0]
         else:
             return None
+
+    def test_stats(self):
+        (result, out, err) = self.runsubcmd("group", "stats",
+                                            "-H", "ldap://%s" % os.environ["DC_SERVER"],
+                                            "-U%s%%%s" % (os.environ["DC_USERNAME"],
+                                                          os.environ["DC_PASSWORD"]))
+        self.assertCmdSuccess(result, out, err, "Error running stats")
+
+        # sanity-check the command reports 'total groups' correctly
+        search_filter = "(objectClass=group)"
+        grouplist = self.samdb.search(base=self.samdb.domain_dn(),
+                                      scope=ldb.SCOPE_SUBTREE,
+                                      expression=search_filter,
+                                      attrs=[])
+
+        total_groups = len(grouplist)
+        self.assertTrue("Total groups: {0}".format(total_groups) in out,
+                        "Total groups not reported correctly")
-- 
2.7.4

-------------- next part --------------
bin/samba-tool group stats -H ldap://$SERVER -U$USERNAME%$PASSWORD

Group membership statistics*
-------------------------------------------------
Total groups: 187
Total memberships: 10024
Average members per group: 53.00
Min members: 0 (Certificate Service DCOM Access)
Max members: 979 (STGG-0-1)

Members        Number of Groups
-------------------------------------------------
          0-1  33
          2-4  5
          5-9  15
        10-14  26
        15-19  28
        20-24  12
        25-29  12
        30-39  9
        40-49  8
        50-59  4
        60-69  6
        70-79  2
        80-89  3
        90-99  2
      100-149  8
      150-199  4
      200-249  1
      250-299  2
      300-399  1
      400-499  2
      500-999  4

* Note this does not include nested group memberships

bin/samba-tool group list -H ldap://$SERVER -U$USERNAME%$PASSWORD --verbose

Group Name                                  Group Type      Group Scope  Members
--------------------------------------------------------------------------------
Remote Desktop Users                        Security         Builtin     0
STGG-0-139                                  Security         Global      2
STGG-0-150                                  Security         Global      11
STGG-0-133                                  Security         Global      8
STGG-0-42                                   Security         Global      54
STGG-0-62                                   Security         Global      29
STGG-0-121                                  Security         Global      13
STGG-0-59                                   Security         Global      27
STGG-0-8                                    Security         Global      299
STGG-0-67                                   Security         Global      29
STGG-0-71                                   Security         Global      19
STGG-0-9                                    Security         Global      251
Read-only Domain Controllers                Security         Global      0
STGG-0-12                                   Security         Global      190
STGG-0-14                                   Security         Global      170
STGG-0-140                                  Security         Global      8
Replicator                                  Security         Builtin     0
STGG-0-128                                  Security         Global      8
STGG-0-136                                  Security         Global      6
STGG-0-22                                   Security         Global      98
STGG-0-45                                   Security         Global      46
STGG-0-44                                   Security         Global      36
STGG-0-110                                  Security         Global      20
STGG-0-47                                   Security         Global      33
STGG-0-15                                   Security         Global      144
STGG-0-11                                   Security         Global      190
STGG-0-114                                  Security         Global      6
Domain Controllers                          Security         Global      0
STGG-0-77                                   Security         Global      19
STGG-0-97                                   Security         Global      16
STGG-0-2                                    Security         Global      802
STGG-0-57                                   Security         Global      28
STGG-0-111                                  Security         Global      13
Terminal Server License Servers             Security         Builtin     0
IIS_IUSRS                                   Security         Builtin     1
STGG-0-120                                  Security         Global      15
Schema Admins                               Security         Universal   1
Network Configuration Operators             Security         Builtin     0
STGG-0-99                                   Security         Global      18
Enterprise Read-only Domain Controllers     Security         Universal   0
STGG-0-127                                  Security         Global      10
STGG-0-74                                   Security         Global      16
STGG-0-135                                  Security         Global      10
STGG-0-76                                   Security         Global      22
STGG-0-124                                  Security         Global      13
STGG-0-91                                   Security         Global      20
STGG-0-126                                  Security         Global      17
STGG-0-51                                   Security         Global      38
STGG-0-53                                   Security         Global      30
STGG-0-88                                   Security         Global      16
STGG-0-116                                  Security         Global      11
STGG-0-86                                   Security         Global      15
Event Log Readers                           Security         Builtin     0
STGG-0-123                                  Security         Global      15
Domain Users                                Security         Global      0
Allowed RODC Password Replication Group     Security         Domain      1
STGG-0-19                                   Security         Global      118
STGG-0-101                                  Security         Global      15
STGG-0-106                                  Security         Global      13
STGG-0-104                                  Security         Global      16
Users                                       Security         Builtin     3
STGG-0-147                                  Security         Global      9
STGG-0-41                                   Security         Global      41
STGG-0-105                                  Security         Global      10
STGG-0-65                                   Security         Global      25
STGG-0-73                                   Security         Global      23
STGG-0-58                                   Security         Global      44
STGG-0-31                                   Security         Global      81
STGG-0-146                                  Security         Global      13
STGG-0-148                                  Security         Global      11
STGG-0-4                                    Security         Global      554
STGG-0-69                                   Security         Global      23
Performance Monitor Users                   Security         Builtin     0
STGG-0-80                                   Security         Global      18
Administrators                              Security         Builtin     3
STGG-0-23                                   Security         Global      80
STGG-0-130                                  Security         Global      11
STGG-0-50                                   Security         Global      37
STGG-0-85                                   Security         Global      14
STGG-0-96                                   Security         Global      13
STGG-0-78                                   Security         Global      15
STGG-0-122                                  Security         Global      16
STGG-0-137                                  Security         Global      15
STGG-0-52                                   Security         Global      20
Performance Log Users                       Security         Builtin     0
STGG-0-70                                   Security         Global      20
STGG-0-18                                   Security         Global      124
STGG-0-25                                   Security         Global      95
STGG-0-142                                  Security         Global      9
Group Policy Creator Owners                 Security         Global      1
STGG-0-13                                   Security         Global      181
STGG-0-95                                   Security         Global      9
STGG-0-49                                   Security         Global      38
STGG-0-89                                   Security         Global      17
STGG-0-90                                   Security         Global      25
STGG-0-83                                   Security         Global      32
STGG-0-16                                   Security         Global      133
STGG-0-149                                  Security         Global      5
Domain Guests                               Security         Global      0
STGG-0-3                                    Security         Global      669
STGG-0-94                                   Security         Global      15
STGG-0-68                                   Security         Global      26
STGG-0-61                                   Security         Global      29
STGG-0-81                                   Security         Global      21
STGG-0-6                                    Security         Global      412
STGG-0-112                                  Security         Global      11
STGG-0-132                                  Security         Global      4
STGG-0-93                                   Security         Global      16
STGG-0-119                                  Security         Global      9
Enterprise Admins                           Security         Universal   1
STGG-0-145                                  Security         Global      7
STGG-0-113                                  Security         Global      22
STGG-0-35                                   Security         Global      64
STGG-0-26                                   Security         Global      64
STGG-0-84                                   Security         Global      15
STGG-0-21                                   Security         Global      104
STGG-0-48                                   Security         Global      47
STGG-0-66                                   Security         Global      26
STGG-0-102                                  Security         Global      16
STGG-0-39                                   Security         Global      43
Domain Admins                               Security         Global      1
STGG-0-1                                    Security         Global      979
STGG-0-38                                   Security         Global      56
Certificate Service DCOM Access             Security         Builtin     0
STGG-0-63                                   Security         Global      24
Denied RODC Password Replication Group      Security         Domain      8
Backup Operators                            Security         Builtin     0
STGG-0-36                                   Security         Global      58
STGG-0-33                                   Security         Global      69
DnsAdmins                                   Security         Domain      0
Account Operators                           Security         Builtin     0
STGG-0-131                                  Security         Global      10
STGG-0-55                                   Security         Global      36
STGG-0-32                                   Security         Global      67
STGG-0-141                                  Security         Global      8
STGG-0-109                                  Security         Global      18
Incoming Forest Trust Builders              Security         Builtin     0
Guests                                      Security         Builtin     2
STGG-0-46                                   Security         Global      44
STGG-0-144                                  Security         Global      13
Server Operators                            Security         Builtin     0
STGG-0-37                                   Security         Global      52
STGG-0-40                                   Security         Global      63
STGG-0-92                                   Security         Global      15
Windows Authorization Access Group          Security         Builtin     1
STGG-0-118                                  Security         Global      14
STGG-0-129                                  Security         Global      12
STGG-0-79                                   Security         Global      19
STGG-0-64                                   Security         Global      26
STGG-0-17                                   Security         Global      120
STGG-0-27                                   Security         Global      87
STGG-0-125                                  Security         Global      9
STGG-0-87                                   Security         Global      18
STGG-0-54                                   Security         Global      27
Cert Publishers                             Security         Domain      0
STGG-0-5                                    Security         Global      437
STGG-0-134                                  Security         Global      9
STGG-0-117                                  Security         Global      12
STGG-0-98                                   Security         Global      13
DnsUpdateProxy                              Security         Global      0
STGG-0-28                                   Security         Global      65
STGG-0-115                                  Security         Global      15
STGG-0-10                                   Security         Global      232
STGG-0-143                                  Security         Global      12
STGG-0-82                                   Security         Global      19
Domain Computers                            Security         Global      0
STGG-0-72                                   Security         Global      29
Pre-Windows 2000 Compatible Access          Security         Builtin     1
STGG-0-56                                   Security         Global      36
STGG-0-60                                   Security         Global      21
STGG-0-34                                   Security         Global      46
STGG-0-103                                  Security         Global      13
STGG-0-24                                   Security         Global      109
RAS and IAS Servers                         Security         Domain      0
STGG-0-100                                  Security         Global      13
STGG-0-43                                   Security         Global      43
STGG-0-107                                  Security         Global      14
STGG-0-20                                   Security         Global      102
Distributed COM Users                       Security         Builtin     0
STGG-0-138                                  Security         Global      11
STGG-0-108                                  Security         Global      21
STGG-0-30                                   Security         Global      76
STGG-0-29                                   Security         Global      75
STGG-0-7                                    Security         Global      333
Cryptographic Operators                     Security         Builtin     0
Print Operators                             Security         Builtin     0
STGG-0-75                                   Security         Global      16


More information about the samba-technical mailing list