[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