HOWTO Disable Accounts on GNU/Linux Systems

How to disable GNU/Linux user accounts is a topic that gets asked of me once every few years. And then as I have slept since the last time I needed to know this I always must go read through all of the documentation all over again and make some test cases to verify how everything works. This just happened again. This time I am documenting this for my own purposes for the next time.

This does NOT APPLY to BSD or legacy Unix systems. FreeBSD, NetBSD, and OpenBSD are different with a different account password management system.

TL;DR:

To disable an account.

# passwd -l ACCOUNTNAME
# usermod -e 1 ACCOUNTNAME
# crontab -u ACCOUNTNAME -l > crontab.saved.ACCOUNTNAME
# crontab -u ACCOUNTNAME -r
# mv ~ACCOUNTNAME/.ssh/authorized_keys ~ACCOUNTNAME/.ssh/authorized_keys.disabled

To re-enable a disabled account.

# passwd -u ACCOUNTNAME
# usermod -e '' ACCOUNTNAME
# crontab -u ACCOUNTNAME - < crontab.saved.ACCOUNTNAME
# mv ~ACCOUNTNAME/.ssh/authorized_keys.disabled ~ACCOUNTNAME/.ssh/authorized_keys

Documentation References

The passwd command.

PASSWD(1)                        User Commands                       PASSWD(1)
NAME
   passwd - change user password
   ...
   -l, --lock
       Lock the password of the named account. This option disables a
       password by changing it to a value which matches no possible
       encrypted value (it adds a '!' at the beginning of the password).

       Note that this does not disable the account. The user may still be
       able to login using another authentication token (e.g. an SSH key).
       To disable the account, administrators should use usermod
       --expiredate 1 (this set the account's expire date to Jan 2, 1970).

       Users with a locked password are not allowed to change their
       password.

   -u, --unlock
       Unlock the password of the named account. This option re-enables a
       password by changing the password back to its previous value (to
       the value before using the -l option).

The usermod command.

USERMOD(8)                System Management Commands                USERMOD(8)
NAME
   usermod - modify a user account
   ...
   -e, --expiredate EXPIRE_DATE
       The date on which the user account will be disabled. The date is
       specified in the format YYYY-MM-DD.

       An empty EXPIRE_DATE argument will disable the expiration of the
       account.

       This option requires a /etc/shadow file. A /etc/shadow entry will
       be created if there were none.

The shadow file from a GNU/Linux system.

SHADOW(5)                File Formats and Conversions                SHADOW(5)
NAME
   shadow - shadowed password file
DESCRIPTION
   shadow is a file which contains the password information for the
   system's accounts and optional aging information.

   This file must not be readable by regular users if password security is
   to be maintained.

   Each line of this file contains 9 fields, separated by colons (":"), in
   the following order:
   ...
   account expiration date
       The date of expiration of the account, expressed as the number of
       days since Jan 1, 1970.

       Note that an account expiration differs from a password expiration.
       In case of an account expiration, the user shall not be allowed to
       login. In case of a password expiration, the user is not allowed to
       login using her password.

       An empty field means that the account will never expire.

       The value 0 should not be used as it is interpreted as either an
       account with no expiration, or as an expiration on Jan 1, 1970.

The crontab command.

CRONTAB(1)                  General Commands Manual                 CRONTAB(1)

NAME
   crontab - maintain crontab files for individual users (Vixie Cron)

SYNOPSIS
   crontab [ -u user ] file
   crontab [ -u user ] [ -i ] { -e | -l | -r }
   ...
   The first form of this command is used to install a new crontab from
   some named file or standard input if the pseudo-filename ``-'' is
   given.

   The -l option causes the current crontab to be displayed on standard
   output. See the note under DEBIAN SPECIFIC below.

   The -r option causes the current crontab to be removed.
   ...

Depth and Details

Let's dig into the details of the above and interpret and summarize.

Locking the Password File

The password is stored as a hash. When matching passwords the entry is hashed in the same way and then the hash string compared. Two characters that are not part of the hash output and therefore if present can never match are "*" and "!". If either of those characters are present in the hash string then it is impossible for the password to match.

This makes using "*" a very typical character to use in the hashed password field since the beginning. Admins will somethings refer to it this way. "I starred out the password." That means they either added a "*" or replaced the entire hash with a "*" to disable the password.

Programs used for automation also need to edit this file. And it is convenient to be able to make the edit reversible. Therefore the passwd program adds a "!" character to the start of the hash with -l and remove a leading "!" character with a -u option. This makes the program usage to automatically disable and to reversibly enable accounts possible.

# passwd -u test1
# passwd -l test1

Logged to the /var/log/auth.log file.

passwd[20415]: password for 'test1' changed by 'root'
passwd[20817]: password for 'test1' changed by 'root'

Expiring the Account Entry

The documentation for the shadow file on a GNU/Linux system says "The value 0 should not be used as it is interpreted as either an account with no expiration, or as an expiration on Jan 1, 1970." And therefore I should conclude that different systems do this differently. And BSD systems are examples that do this differently. On a FreeBSD system the man 5 passwd page says.

The expire field is the number of seconds from the epoch, UTC, until the
account expires.  This field may be left empty to turn off the account
aging feature.

Therefore on FreeBSD, NetBSD, and OpenBSD the field should definitely be 1 and not 0. Also there are many other differences and this document specifically only deals with GNU/Linux systems. Above we see that BSD stores this in seconds for one. I checked HP-UX for a different legacy system and on HP-UX it is stored as the number of weeks which is yet again different.

Testing with these following commands I find the following results.

# usermod -e 0 test1
# su - test1
Your account has expired; please contact your system administrator.
su: Authentication failure

$ ssh test1@localhost
test1@localhost's password:
Your account has expired; please contact your system administrator.
Connection closed by 127.0.0.1 port 22

# usermod -e 1 test1
# su - test1
Your account has expired; please contact your system administrator.
su: Authentication failure

$ ssh test1@localhost
test1@localhost's password:
Your account has expired; please contact your system administrator.
Connection closed by 127.0.0.1 port 22

Then in the log the following is logged to the /var/log/auth.log file.

usermod[854]: change user 'test1' expiration from 'never' to '1970-01-01'
usermod[886]: change user 'test1' expiration from '1970-01-01' to '1970-01-02'
su: pam_unix(su-l:account): account test1 has expired (account expired)
sshd[19005]: pam_unix(sshd:account): account test1 has expired (account expired)
sshd[19005]: Failed password for test1 from 127.0.0.1 port 51028 ssh2
sshd[19005]: fatal: Access denied for user test1 by PAM account configuration [preauth]
su: pam_unix(su-l:account): account test1 has expired (account expired)
sshd[20211]: pam_unix(sshd:account): account test1 has expired (account expired)
sshd[20211]: Failed password for test1 from 127.0.0.1 port 51050 ssh2
sshd[20211]: fatal: Access denied for user test1 by PAM account configuration [preauth]

Removing the Crontab File

If the user had an installed crontab file then cron will continue to attempt to run the contents of that cronjob file. If the account is disabled then this will be rejected. Therefore disabing an account (the usermod -e 1 part of the above) will also prevent any of that users cronjob tasks from running. However when cron attempts to run the task the result will be logged into

If the user has commands in their crontab file then the following gets logged to the /var/log/auth.log file every time it would have run.

CRON[21493]: pam_unix(cron:account): account test1 has expired (account expired)

Therefore to silence that log action it is best to remove the file. But obviously removing the file is not a reversible action. Therefore I suggest saving the file off prior to removing it. Then to reverse the action the saved file can be installed. This feels better to me than to comment out the entire file. Because the file might already be commented out and reversing such an edit is not completely trivial to do.

# crontab -u test1 -l > /tmp/test.crontab.out
# crontab -u test1 -r
# crontab -u test1 -l
no crontab for test1
# crontab -u test1 - < /tmp/test.crontab.out
# crontab -u test1 -l
# This is a comment in test1 crontab.
* * * * * echo hello > /var/tmp/hello.txt
# crontab -u test1 -r
# crontab -u test1 -l
no crontab for test1

Disabling the authorized_keys File

SSH logins are immediately disabled when the account is expired. Therefore there is no concern that a left-behind authorizedkeys file will allow access by itself. However if the account were to be enabled again by accident then the authorizedkeys file is once again active if it is present. Therefore it is best to disable it.

# mv ~ACCOUNTNAME/.ssh/authorized_keys ~ACCOUNTNAME/.ssh/authorized_keys.disabled

Once again I choose to make these changes reversible. However if it is known that this will never need to be reversed then removing the file is a good cleanup rather than leaving behind a public key file that can never be used again. In which case it would just be lint.

# rm -f ~ACCOUNTNAME/.ssh/authorized_keys

License

Copyright (C) 2021 Bob Proulx

Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty.