August 16, 2005

Limiting Linux logins

Author: Brian K. Jones

I'm sometimes surprised at the complex solutions administrators come up with to keep out users whenever that course becomes necessary when a simple, built-in solution to their problem already exists. In this article, we'll look at a few different mechanisms used to keep out intruders either temporarily, or on a more permanent basis.

Most obviously, Linux offers single user mode, in which only the system administrator can log on. You can get to single user mode in a number of ways. Typing init 1 at a shell prompt as root will do it nicely, and do it immediately. As per the shutdown man page, if you don't explicitly tell the command to halt or reboot (using the -h or -r flags, respectively), it'll put you into single user mode. This is a little more friendly if you have users on the system, since you can use shutdown to send a friendly warning message and give users a grace period to clean up and log off before you go into single user mode.

The other way to get into single user mode is at boot time, either by having a GRUB entry for single user mode, or by editing the GRUB "kernel" line at boot time. When you see the GRUB menu at boot time, just press "e", arrow down to the kernel line, press "e" again, go to the end of the line, and type "single." Press Enter, and then "b" for "boot," and you're on your way.

Usually, if I want everyone off the box I'm working on, it's for maintenance or troubleshooting downtime, where I need to boot to full multi-user mode (e.g. to check that all of the services come up properly), but I don't want users to be logging in because I might reboot again, or things might not go as planned. In this case, I log in as root and simply type touch /etc/nologin. All of the services have already started at this point, but none of them will accept a login if the file /etc/nologin is present. I can do my work and make outgoing connections and test services that don't involve a login. Another useful feature is that the /etc/nologin file disappears upon reboot, so you can't lock yourself out of the box (well, not using /etc/nologin, anyway).

Typically, users in a department are informed ahead of time about downtime, so I don't feel the need to send a message to users during scheduled downtime, but the nologin man page says that it will also use an /etc/nologin.txt file to give users a message informing them that the machine or service is currently unavailable, or whatever message you choose to put in the file.

More permanent lockouts

Linux systems offer so many ways to restrict logins these days that it's hard to keep up with all of them. The Linux PAM system alone provides a dozen different modules used to restrict logins based on group memberships, time of day, and other criteria. Let's look at a couple of ways to restrict access to a Linux machine: tcpwrappers, and the pam_access module.

The tcpwrappers system is capable of applying security checks to anything that is linked against (usually) /usr/lib/libwrap.so. On a standard Red Hat installation, this includes a great many services, including portmap, sshd, httpd, xinetd, and snmpd. You configure the tcpwrappers settings using the files /etc/hosts.allow and /etc/hosts.deny. In environments that still use the "r" commands
(rlogin, rsh, etc), you also find the /etc/hosts.equiv file in use. I will cover only the first two files, since they cover 99% of the services on the machine. Armed with knowledge of those, you can tackle hosts.equiv without a problem.

Here's the most important bit to remember about the hosts.allow/hosts.deny files:

  1. hosts.allow is read first, followed by hosts.deny.
  2. If access is granted to a service by hosts.allow, access is granted. The
    hosts.deny file is ignored.
  3. If access is not granted by hosts.allow, but is not denied by hosts.deny, access is granted.
  4. If access is not granted by hosts.allow, and it is denied by
    hosts.deny, access is denied to that service.

Let's look at an example. I'll use sshd as my example service, since most people have it installed, and it's widely used. Here's a sample hosts.allow/deny pairing:

#hosts.allow
sshd:ALL

#hosts.deny
ALL:ALL@ALL

This is a pretty locked-down configuration. Access will be granted to anyone via the sshd service, but in the hosts.deny file, I have a "cleanup rule," so that if access is not explicitly granted in
hosts.allow, everything is denied. Of course, remember that "everything" here means "everything that's linked against libwrap."

Here's a more complex example that exploits more of the advanced syntax you
can use with tcpwrappers:

 #hosts.allow
ALL:ALL@@goodhosts
sshd: .linuxlaboratory.org 64.141.
portmap: LOCAL

#hosts.deny
snmpd:ALL EXCEPT monitor.linuxlaboratory.org PARANOID
ALL: PARANOID

The syntax here is servicelist:hostlist[: shell
command]
. Shell commands will be left as an exercise to the user. Armed with the syntax, we see that hosts.allow will grant access to ALL services, to ALL users coming from any host in the "goodhosts" netgroup. If I wanted to give access to all users on a specific host, I would write ALL:ALL@hostname. To denote a netgroup, you precede the netgroup name with an "@". Put these together and you get two "@" signs next to each other in the same definition. This is a little confusing if you've never seen it before, but it does works.

The sshd service in this example is available to any user on any host whose fully qualified domain name ends with "linuxlaboratory.org." Don't forget the dot in front! Conversely, to allow access from a certain IP block, you must end with a dot. Finally, the LOCAL keyword allows access to the portmap facility to any host whose name does not contain a dot.

In the hosts.deny file here, we deny access to the snmpd service to every machine except for our monitoring host, and the addition of the PARANOID keyword will deny access even to that host if its name doesn't properly resolve to its IP address. Aside from that, the only other thing we do is deny access to any host for any service if the client's name doesn't properly resolve. There is no ALL:ALL statement here, which means that if I'm running xinetd, for example, it is not explicitly allowed or denied in either file; as long as the client's name resolves properly (as per the PARANOID keyword), access will be granted.

This is just the tip of the iceberg in terms of the power and flexibility that tcpwrappers can afford you. I urge you to read the hosts_access man page to discover all that's in store for you in the tcpwrappers package.

The pam_access module

Once you have a clue regarding hosts.allow and hosts.deny syntax, you also have learned by proxy about pam_access. The pam_access module consults a configuration file that looks a little like the hosts.allow file on those versions of Unix that use only the hosts.allow file for tcpwrappers configuration. By default, the file pam_access consults /etc/security/access.conf and looks for lines beginning with "+" or "-", which basically map to "allow" and "deny" lines, respectively. From there, a line like the following example shouldn't look all that foreign:

-:ALL EXCEPT root:ttyS*

The above line disallows logins over a serial link (devices matching ttyS*) to everyone except root. Within the PAM framework lies a host of modules that can be used together to restrict access to your system. The pam_access module is a very simple one. Check out the PAM documentation that comes with your distribution, which usually includes the README files for each module installed on the system, as well as sample configurations of each module.

Conclusion

Many more options are available to Linux administrators for restricting access. Besides the systems covered here, check into more advanced usage of iptables, SELinux, and the /etc/securetty file, among other things, to find the right solution before inventing your own hack to get where you need to go.

Click Here!