/etc/group make up the core of user and group management on a Linux/*nix system. On systems with many users (think of company or department servers) they are probably complemented by a directory service. But on standalone servers these three files are all there is, so their content is very important.
Testing these files is useful for a number of reasons:
- to know if a certain user or group exists
- to make sure a certain user can login (i.e. is enabled)
- to verify that a group has certain member users
These reasons are valuable also in their negative form. In security, it is as important to know that a user is in a group, as to know that a user is NOT in group.
Looking at these files manually is tedious and error-prone. So we’re going to use Tstconfig to automate this task.
Create a file called
users.tstconfig and start the test with the following content:
# Configuration file file /etc/passwd # The syntax to use syntax etc_passwd
/etc/passwd is made of lines, one per user. Each line is made of seven fields:
- user id
- primary group id
- user info
- home directory
- login shell
For this tutorial, I’m interested in the username and the login shell. The other fields can be ignored. Going back to the
users.tstconfig file we can express this as follows:
columns 0 6
Counting from zero, ‘0’ is the username, ‘6’ is the login shell. Now we can write a test for user ‘myuser’:
property myuser assert_eq /bin/bash
We could repeat this operation for each user but it would be time-consuming. Instead, we could reverse the order of the columns and treat the shell as the property name, and the user as the property value.
columns 6 0 # '/usr/sbin/nologin' means the user cannot login property /usr/sbin/nologin assert_contains daemon bin sys games man lp mail news uucp proxy www-data backup list irc gnats nobody sshd # /bin/false serves a similar puprose property /bin/false assert_contains syslog messagebus mysql colord
With just four lines, we have tested that about 20 key system users cannot login to a shell.
users.tstconfig and run the following command:
$ tstconfig users.tstconfig Tstconfig 0.2 Reading definition file: users.tstconfig SUMMARY REPORT: PASS Assertions tested: 3 Assertions passed: 3 Assertions failed: 0 Errors: 0
The report shows that all tests passed.
For security reasons,
/etc/shadow can only be read by root. To read it in our tests, we have to use a different approach. In
users.tstconfig add these lines:
# Read /etc/shadow as sudo command sudo cat /etc/shadow syntax etc_passwd
/etc/shadow is organised in lines and fields, similarly to
/etc/passwd. There are eight fields:
- encrypted password
- last password change
- minimum number of days before change allowed
- maximum number of days before expiry
- number of warning days before expiry
- number of days after expiry before account is disabled
- fixed date at which account is disabled
Of all these fields, we’re going to use the first two:
# Consider username and password only columns 0 1
Testing the password is not useful in itself, as passwords are encrypted. However there is a rule whereby if a password starts with symbols ‘!’ or ‘*’ then the username is considered disabled.
To test if the root account is disabled we could write:
property root assert_eq !
users.tstconfig and launch the tests:
$ tstconfig users.tstconfig Tstconfig 0.2 Reading definition file: users.tstconfig ASSERTION FAILED Command: sudo cat /etc/shadow Property: root Value: $6$ehdtSs2J$kGzf8ZDmOMJlZHgDjxbTJpvXXDyW2wy34zCuBn3FypStNkCOjWoRnIc5pY1BGqnoihk7XmCs4lfDCYoS78QR3. Assertion: assert_eq ! SUMMARY REPORT: FAIL Assertions tested: 4 Assertions passed: 3 Assertions failed: 1 Errors: 0
The report failed and shows that, on the system where the test was performed, account
root is not disabled. Disabling
root and re-running the test will make all tests pass.
/etc/group file is made of lines organised in four fields:
- the group name
- a password (not really used)
- the gid or group id
- the member users
To test it, add the following lines to
file /etc/group syntax etc_group
Here the useful fields are the group name (0) and the members (3):
columns 0 3
Meaningful tests are to check the members of privileged groups:
# Check that nobody is in the root group property root assert_empty # The adm group grants access, e.g., to certain logs property adm assert_eq syslog,myuser # The sudoers property sudo assert_eq myuser # Access to web server files property www-data assert_eq myuser
You may wonder why the
root group is tested to be empty, while we know that the
root user does belong to the
root group. The reason lies in the difference between primary and secondary group membership. Primary group membership is defined in
/etc/passwd, while secondary group membership is defined in
/etc/group. Going back to the example, the
root user has
root as the primary group, which justifies the possibility for the group to have no members as far as
/etc/group is concerned.
Testing a user’s groups
In the previous section we asked the question ‘What users are members of group X?’. It would make sense to ask another question: ‘What groups does user Y belong to?’. This would mean querying
/etc/group by user instead of by group. Unfortunately this is not possible given the syntax of
/etc/group and the way Tstconfig works.
Looking for a workaround, I discovered a simple command called
groups which does just that. Let’s give it a try:
$ groups myuser myuser : myuser adm cdrom sudo dip www-data plugdev lpadmin sambashare
The output is a key-values map that Tstconfig can handle easy. Edit
users.tstconfig and add these few lines:
# Test a single user's group membership command groups myuser parse_mode keyvalue key_separator : # expect a single line, with the given user as the key property myuser assert_contains adm www-data assert_not_contains root
The usual contains/not_contains/eq assertions are possible.
On a Linux system there are many configuration files that are essentially tables. Tstconfig has the following commands to ‘query’ their contents:
- file: to read a configuration file
- command: to execute a Linux command and parse its output
- syntax/parse_mode/key_separator/…: to specify the format for parsing
- columns: to focus only on certain columns and ignore the others
- property: to select a certain row
- assert_*: to check the values of a row
I’ve shown how with these simple commands you can perform a variety of tests and enforce security and good configuration on your systems.