Analysis: Primary Group Name of LINUX Users

I’m really struggling with this Analysis.

On Linux machines, I need a Property that contains a list of all members of each groups.
This is simple enough, right? Just “lines of file “/etc/group”” and be done with it, right?

Problem is, /etc/group lists all members of all groups except those who are in the group via their primary/default group–which is seen in /etc/passwd … but only by Group ID number (element 4 in the colon-delimited list), not name.

What I want to do is pull each line in /etc/group and add element 1 (user name) of each line in /etc/passwd where element 4 of /etc/passwd (the default/primary Group ID) matches element 3 of /etc/group (the Group ID number).

What I’ve got:
I can pull Element 1 (username) from /etc/passwd with the following Relevance:

(preceding texts of firsts “:” of it) of (lines of file “/etc/passwd” as string)

And I can pull Element 4 (the default/primary Group ID) from /etc/passwd with the following Relevance:

preceding texts of firsts “:” of (following texts of firsts “:” of (following texts of firsts “:” of (following texts of firsts “:” of (lines of file “/etc/passwd” as string))))

And I can pull Element 3 (the Group ID) from /etc/group with the following:

preceding texts of firsts “:” of (following texts of firsts “:” of (following texts of firsts “:” of (lines of file “/etc/group” as string)))

And of course, the group name is just like the username:

(preceding texts of firsts “:” of it) of (lines of file “/etc/group” as string)

But how to get either the username into the groups output, or the group name into the users output is stumping me.

What I wouldn’t give for a handful of register values I could put results into, and for proper string tokenization and elements…

This is an interesting problem…I should be able to dig in in a bit, but wanted to comment so I’m tagged on replies.

As far as register variables…years ago I submitted an RFE to add “this” and “that” in addition to “it”, but it never got anywhere :slight_smile:

1 Like

Here’s my first stab at it

(tuple string items 0 of item 0 of it, tuple string items 2 of item 0 of it, concatenation ", " of unique values of (tuple string item 3 of item 0 of it | "" ; concatenation ", " of tuple string items 0 of items 1 of (tuple string item 2 of item 0 of it, elements of item 1 of it) whose (item 0 of it = tuple string item 3 of item 1 of it) of it) whose (it as trimmed string != "")) of (elements of item 0 of it, item 1 of it) of (set of (concatenation ", " of substrings separated by ":" of it) of lines of file "/etc/group", set of (concatenation ", " of substrings separated by ":" of it) of lines of file "/etc/passwd")

In expanded relevance, this may be more clearly shown as

(
  tuple string items 0 of item 0 of it /* group name from group file */
, tuple string items 2 of item 0 of it /* gid number from group file */
, concatenation ", " of unique values of ( /* join users from group and passwd file*/
     tuple string item 3 of item 0 of it | "" /* username entries from group file or blank*/
     ; concatenation ", " of  /* join usernames with comma */
          tuple string items 0 of items 1 of ( /* username entry from passwd file */
          /* For each group, unwind the passwd file and find matching passwd entries */
          tuple string item 2 of item 0 of it /* gid entry from group */
          , elements of item 1 of it /* full passwd line */
          ) whose (
               /* group gid matches user gid */
           item 0 of it = tuple string item 3 of item 1 of it
           ) of it 
  ) whose (it as trimmed string != "") /* exclude any empty strings */
) of (
  elements of item 0 of it /* unwind the group strings */
  , item 1 of it /* leave the passwd list as a string set */
  ) of (
  /* read group and passwd files into string sets to reduce number of file reads */
  /* convert each to tuple string format instead of colon-delimited format */
  set of (concatenation ", " of substrings separated by ":" of it) of lines of file "/etc/group"
, set of (concatenation ", " of substrings separated by ":" of it) of lines of file "/etc/passwd"
)

This converts each of the files into a ‘string set’, and changes the field delimiters from “:” to ", ". That comma-space combination is special; it means we can now treat each field as a ‘tuple string item’ instead of having to repeatedly look at "preceding text of first “:” of following text of first “:” of following text of first “:”

Reading each file into a string set helps with efficiency. We will have to compare every line of the group file to every line of the passwd file; if we had the file reads “inside the loop”, we’d re-read the passwd file again for every line of the group file. By building the string sets first and doing all the comparisons against the elements of each set, we only have to read each file once.

1 Like

Outstanding! Thank you!

So the resulting list is of format:

I really appreciate the comments and explanations. This result is one for me to study, and will make our security audits much easier.

Thanks again!

Happy to help, I love a relevance challenge…bear in mind, though, this only gets the direct entries from /etc/passwd and /etc/group. If you’re using an AD integration, LDAP, NIS+, or anything else that’s not directly in the files…it gets more complicated.

Understood on the centrally-managed userlists like LDAP/Infoblox/etc, though they solve the problem themselves by being only a handful of boxes instead of hundreds.

My next challenge is just to see if I can figure out myself how to add the member list from the group itself, but right now I can slap this and the /etc/groups output into 2 Properties and earn the eternal adoration of my ISSO. :smiley:

I totally missed that my relevance is lacking the entries from the ‘group’ file (because multiple users in a group are also separated by ", " that’s breaking my query based on ‘tuple string items’.

…be right back.

Give this version a try…

(item 0 of it & ":" & item 1 of it & ":" & item 2 of it) of /* Format the results to be colon-delimited */

(
  tuple string items 0 of item 0 of it /* group name */
  , tuple string items 2 of item 0 of it /* group gid */
  , concatenation "," of unique values of (it as trimmed string) whose (it != "") /* join group members by commas, keeping only unique and non-empty ones */
   of (
       substrings separated by "," of (tuple string item 3 of item 0 of it | "") /* split of the members from group file, if they exist */
       ; tuple string items 0 of items 1 of ( /* the matching user name from the passwd entry */
           tuple string item 2 of item 0 of it /* gid number from group entry */
           , elements of item 1 of it /* passwd entries */
           ) whose (item 0 of it = tuple string item 3 of item 1 of it) of it) /* user's gid matches group's gid */

) of (elements of item 0 of it, item 1 of it) of 
(
 set of (tuple string of substrings separated by ":" of it) of lines of file "/etc/group",
 set of (tuple string of substrings separated by ":" of it) of lines of file "/etc/passwd"
)

I changed how I constructed the tuple strings slightly…by using 'tuple string of ’ instead of ‘concatenation ", " of’, this properly escapes the case where the group entry has embedded commas.
I also slightly changed the comparison and between passwd and group entries and moved where I check for empty results to (I hope) be slightly more clear; and then in the end, I combine the fields with colons instead of commas to distinguish between the groupname, gidnumber, and multiple user entries.

I never would have guessed it, thank you!!
You’ve greatly improved my understanding of BigFix–and saved our ISSO from certain ulcers! :wink:

1 Like