1- import subprocess
2- import json
31import sys
42import argparse
53import os
64import stat
5+ import pwd
6+ import grp
77
88
99# Expected groups for each user
159159}
160160
161161
162- # This program depends on osquery being installed on the system
163- # Function to run osquery
164- def run_osquery (query ):
165- process = subprocess .Popen (
166- ["osqueryi" , "--json" , query ], stdout = subprocess .PIPE , stderr = subprocess .PIPE
167- )
168- output , error = process .communicate ()
169- return output .decode ("utf-8" )
170-
171-
172- def parse_json (json_str ):
162+ def get_user_groups (username ):
163+ """Get all groups that a user belongs to using Python's pwd and grp modules."""
173164 try :
174- return json .loads (json_str )
175- except json .JSONDecodeError as e :
176- print ("Error decoding JSON:" , e )
165+ user_info = pwd .getpwnam (username )
166+ user_uid = user_info .pw_uid
167+ user_gid = user_info .pw_gid
168+
169+ # Get all groups
170+ groups = []
171+ for group in grp .getgrall ():
172+ # Check if user is in the group (either as primary group or in member list)
173+ if user_gid == group .gr_gid or username in group .gr_mem :
174+ groups .append ({"username" : username , "groupname" : group .gr_name })
175+
176+ # Sort by groupname to match expected behavior
177+ groups .sort (key = lambda x : x ["groupname" ])
178+ return groups
179+ except KeyError :
180+ print (f"User '{ username } ' not found" )
177181 sys .exit (1 )
178182
179183
@@ -195,43 +199,60 @@ def compare_results(username, query_result):
195199
196200
197201def check_nixbld_users ():
198- query = """
199- SELECT u.username, g.groupname
200- FROM users u
201- JOIN user_groups ug ON u.uid = ug.uid
202- JOIN groups g ON ug.gid = g.gid
203- WHERE u.username LIKE 'nixbld%';
204- """
205- query_result = run_osquery (query )
206- parsed_result = parse_json (query_result )
207-
208- for user in parsed_result :
209- if user ["groupname" ] != "nixbld" :
210- print (
211- f"User '{ user ['username' ]} ' is in group '{ user ['groupname' ]} ' instead of 'nixbld'."
212- )
213- sys .exit (1 )
202+ """Check that all nixbld users are only in the nixbld group."""
203+ # Get all users that match the pattern nixbld*
204+ nixbld_users = []
205+ for user in pwd .getpwall ():
206+ if user .pw_name .startswith ("nixbld" ):
207+ nixbld_users .append (user .pw_name )
208+
209+ if not nixbld_users :
210+ print ("No nixbld users found" )
211+ return
212+
213+ # Check each nixbld user's groups
214+ for username in nixbld_users :
215+ groups = get_user_groups (username )
216+ for user_group in groups :
217+ if user_group ["groupname" ] != "nixbld" :
218+ print (
219+ f"User '{ username } ' is in group '{ user_group ['groupname' ]} ' instead of 'nixbld'."
220+ )
221+ sys .exit (1 )
214222
215223 print ("All nixbld users are in the 'nixbld' group." )
216224
217225
218226def check_postgresql_mount ():
219- # processes table has the nix .postgres-wrapped path as the
220- # binary path, rather than /usr/lib/postgresql/bin/postgres which
221- # is a symlink to /var/lib/postgresql/.nix-profile/bin/postgres, a script
222- # that ultimately calls /nix/store/...-postgresql-and-plugins-15.8/bin/.postgres-wrapped
223- query = """
224- SELECT pid
225- FROM processes
226- WHERE path LIKE '%.postgres-wrapped%'
227- AND cmdline LIKE '%-D /etc/postgresql%';
228- """
229- query_result = run_osquery (query )
230- parsed_result = parse_json (query_result )
231-
232- pid = parsed_result [0 ].get ("pid" )
233-
234- # get the mounts for the process
227+ """Check that postgresql.service mounts /etc as read-only."""
228+ # Find the postgres process by reading /proc
229+ # We're looking for a process with .postgres-wrapped in the path
230+ # and -D /etc/postgresql in the command line
231+ pid = None
232+
233+ for proc_dir in os .listdir ("/proc" ):
234+ if not proc_dir .isdigit ():
235+ continue
236+
237+ try :
238+ # Read the command line
239+ with open (f"/proc/{ proc_dir } /cmdline" , "r" ) as f :
240+ cmdline = f .read ()
241+ # Check if this is a postgres process with the right data directory
242+ if ".postgres-wrapped" in cmdline and "-D /etc/postgresql" in cmdline :
243+ pid = proc_dir
244+ break
245+ except (FileNotFoundError , PermissionError ):
246+ # Process might have disappeared or we don't have permission
247+ continue
248+
249+ if pid is None :
250+ print (
251+ "Could not find postgres process with .postgres-wrapped and -D /etc/postgresql"
252+ )
253+ sys .exit (1 )
254+
255+ # Get the mounts for the process
235256 with open (f"/proc/{ pid } /mounts" , "r" ) as o :
236257 lines = [line for line in o if "/etc" in line and "ro," in line ]
237258 if len (lines ) == 0 :
@@ -265,9 +286,6 @@ def check_directory_permissions():
265286 actual_mode = oct (stat .S_IMODE (stat_info .st_mode ))[2 :] # Remove '0o' prefix
266287
267288 # Get owner and group names
268- import pwd
269- import grp
270-
271289 actual_owner = pwd .getpwuid (stat_info .st_uid ).pw_name
272290 actual_group = grp .getgrgid (stat_info .st_gid ).gr_name
273291
@@ -369,12 +387,10 @@ def main():
369387 if not qemu_artifact :
370388 usernames .append ("ec2-instance-connect" )
371389
372- # Iterate over usernames, run the query , and compare results
390+ # Iterate over usernames, get their groups , and compare results
373391 for username in usernames :
374- query = f"SELECT u.username, g.groupname FROM users u JOIN user_groups ug ON u.uid = ug.uid JOIN groups g ON ug.gid = g.gid WHERE u.username = '{ username } ' ORDER BY g.groupname;"
375- query_result = run_osquery (query )
376- parsed_result = parse_json (query_result )
377- compare_results (username , parsed_result )
392+ user_groups = get_user_groups (username )
393+ compare_results (username , user_groups )
378394
379395 # Check if all nixbld users are in the nixbld group
380396 check_nixbld_users ()
0 commit comments