Restricting Machine Access with the rma "shell"
Greg A. Wade
Many sites enforce restrictions as to which machines
can be accessed by
account holders in an NIS domain. These access policies
are motivated by
a wide range of concerns, some political and others
technical, with the
goal of protecting the CPU resources of one workgroup
from utilization
by others in the organization. Administrators may also
wish to restrict
unauthorized users from logging into systems that perform
critical roles
in the networked environment. Typically, these types
of access
restrictions are implemented by utilizing netgroups.
Using NIS Netgroups to Restrict Access
Netgroups can be used essentially to make only portions
of the NIS
password map visible to the client. On the NIS server,
netgroups
containing user lists are added to the /etc/netgroup
file, and the NIS
netgroup map is populated with this information. No
additional
configuration or modification of the server machine
is required. Figure 1
shows a segment of a netgroup file specifying two
netgroups - staff
with members tim and jill, and sales with brad, randy,
and mark.
The password files of the client machines are then modified.
These files
will typically contain +:*:0:0::: as the final entry.
A user id of +
indicates that the entire NIS passwd map should be appended
to the local
passwd file. The disabled password for the "user"
+ is used to maintain
security on some older systems, and the user and group
ids of 0 are
included so the line has the format of a valid entry.
Inclusion of the
entire NIS map is not desired, so the +:*:0:0::: entry
is removed and
replaced with entries of the form +@netgroup, in which
netgroup is the
name of a netgroup containing user accounts with access
to the machine.
Figure 2a shows a portion of a passwd file from an NIS
client that only
allows logins from NIS users in the sales netgroup.
Similarly, you can exclude netgroups of users from the
password file by
retaining the +:*:0:0::: and including -@netgroup entries.
Figure 2b
shows a segment from a passwd file allowing logins from
all NIS users
except those in the staff netgroup. When excluding netgroups
from the
passwd file, ensure that the -@netgroup entries precede
the +:*:0:0:::
entry. This requirement is imposed by the routines for
reading the
passwd file. These routines stop their search of the
file as soon as a
match is found. As a result, -@netgroup entries placed
after the
+:*:0:0::: would never be processed.
One additional step is required on clients running Solaris
2.x. You must
enable the +/- syntax for policy control in the password
file by
modifying the passwd line in /etc/nsswitch.conf. If
the client is
accessing an NIS server, then the line passwd: compat
should be placed
in the file. Clients accessing NIS+ servers should contain
two lines:
passwd: compat, and passwd_compat: nisplus.
Why rma?
Although netgroups are effective at prohibiting unauthorized
users from
logging into workstations, they are not appropriate
for restricting
logins to many types of servers. NIS servers, http servers
serving
user's WWW pages, mail hubs performing local delivery,
and print servers
performing per user accounting are all examples of servers
that must be
aware of all the users in the domain (i.e., they must
have access to a
complete password file). To overcome this limitation
of NIS netgroups,
the rma shell wrapper was created.
The original version of rma was created to restrict
25 undergraduate
students at Southern Illinois University at Carbondale
to the use of six
Sun workstations located in a small lab operated by
the Department of
Computer Science. During the past four years rma has
evolved and is now
used to restrict undergraduate students from utilizing
computing
facilities reserved for graduate students and faculty.
rma is also used
to prevent users who are not members of systems staff
from logging into
the department's servers. See Listing 1 for the current
version of the
rma code.
rma stands for restricted machine access. This restriction
is
accomplished by moving the shells, typically found in
/bin and /usr/bin,
to a new location - the directory specified by the SHELL_DIR
define in
the program. /usr/bin/shells is usually a good choice.
For security
reasons, the statically linked copy of the Bourne shell
usually found in
/sbin should remain untouched, because it is customarily
root's login
shell. Then, the original shell names are linked to
the rma binary,
which should be copied to /usr/bin. This same procedure
is used to
install most wrapper programs.
Enforcing Access Restrictions
Enforcement of access restrictions is based on the system's
hostname and
the user's primary group id. These policies are specified
by creating a
file containing access lists, and the name and location
of this file are
determined by the definition of ACCESS_LIST. Each machine
running rma
must have an ACCESS_LIST that is owned by root with
file permissions
644. The format of this file closely resembles a standard
UNIX group
file, which allows rma to utilize the fgetgrent() function
to process
the file. This feature greatly reduces the amount of
code required for
rma. However this similarity with the group file also
brings one
disadvantage: there is no method for adding comments
to the ACCESS_LIST.
rma looks in the equivalent of the password field for
a + or a -
character to determine the type of access list given
for a group. A +
indicates an allow list (i.e., a list of machines that
group members may
use), whereas a - denotes a deny list, (i.e., a list
of machines group
members may not access). Figure 3 shows an ACCESS_LIST
that 1) allows
wheel and daemon group members to access any machine
by denying access
to no machines; 2) permits the staff group login access
only to the
machines named isis, bast, and horus; 3) restricts the
sales group from
logging into isis; and 4) prohibits users with a primary
group of guest
from logging into any machine by not granting access
to any machines.
As in the previous example, the wheel and daemon groups,
along with any
other system-level groups, should be given access to
all machines in a
domain. Otherwise, root access may possibly be disabled,
along with that
of other administrative users. Although giving each
machine an
ACCESS_LIST that describes the entire domain may seem
redundant, this
method does ease administration. When changes are required,
a new access
file can be distributed to all machines with a utility
such as rdist.
This method of updating the access list helps to preserve
the NIS
paradigm of centralized management, which rma does not
directly support.
After a user has successfully logged in, rma determines
the system's
hostname through the gethostname() system call and determines
the user's
primary group id through a call to getgid(). Next, the
CanLogin()
function is called to check this information against
the access control
file. If the user is permitted access, CanLogin() returns
1, and rma's
Login() function is called to launch a shell for the
user, otherwise
KickOut() is called to terminate the user's current
session.
Launching the Shell
The Login() function performs two primary tasks. First,
it determines
which shell to invoke and, second, whether to invoke
it as a login shell
or subshell. rma extracts the program name of the shell
to be executed
from the first argument, argv[0], passed in at run-time.
Because the
user's shell is linked to rma, this process produces
the name of the
user's real shell. This name is then combined with SHELL_PATH
to
generate the fully qualified name of the shell to execute.
rma also
determines whether the shell is to be run as the login
shell by
examining argv[0]. If the first character of argv[0],
*argv[0], is a -,
then rma has been started by the login process as a
login shell, and in
return must start the user's shell as a login shell.
Alternatively, if
*argv[0] is not -, then rma was started interactively
(e.g., to execute
a script, or by the rsh daemon to execute remote commands),
and it
should start the user's shell as a subshell.
When Login() starts a shell, it passes any command line
parameters to
the shell it is executing. This allows rsh's, as well
as any software
packages using the system() or popen() functions, to
function properly.
Advanced Features
KickOut() is called when a user is denied access. By
defining MAIL_TO as
a valid email address, KickOut() will use /usr/ucb/Mail
to send email
when an attempted access violation occurs. The body
of this message is
empty, but the subject, "ACCESS VIOLATION!!,"
and the message headers
state who attempted access, when the attempt was made,
and on which
machine. Some investigation with the last command will
even tell where
the login attempt originated from, because the user
was allowed to
complete the login process before they were "kicked
off" the machine.
In addition to sending email, rma can run scripts before
a user's login
shell is started and after it terminates. These scripts
are defined by
LOGIN_SCRIPT and LOGOUT_SCRIPT, respectively. To run
these scripts, rma
first calls the function LoginScript(), which forks
a child process to
execute the LOGIN_SCRIPT, and waits for this child to
terminate. Once
this child process terminates, rma forks a second child,
which executes
the user's login shell, and again waits for that process
to terminate.
Upon termination of the second child process, the original
rma process
calls LogoutScript() to execute the LOGOUT_SCRIPT.
These scripts have a wide variety of uses. For example,
the LOGIN_SCRIPT
can be used to display system messages to users, and
the LOGOUT_SCRIPT
can be used to clean temporary and scratch directories
upon logout.
These scripts are not called when rma starts a subshell.
Thus, the
opening and closing of windows on a workstation, for
example, would not
cause the login or logout scripts to be executed.
Limitations and Concerns
rma does a good job restricting console logins, telnets,
rlogins, and
rsh's. Unfortunately, rma provides no facilities for
restricting ftp
access. The ftp daemon never attempts to execute the
user's login shell.
Contrary to ftp, su does start a shell. It executes
the shell of the
user being su'ed to. As a result, su'ing to an account
that is
prohibited from accessing a given machine generates
an access violation.
Careful readers of the rma code will notice a section
of code to deal
with an oddity of the SunOS su program. su on SunOS
does not reset
argv[0] to the name of the shell being executed, so
a special check is
performed in the Login() function. If su was the calling
program, the
name of the shell is extracted from the SHELL environment
variable.
Conclusion
rma may look like a security tool, however, it is only
intended to
enforce access policies, because a clever user may be
able to bypass
rma's function. This problem has not arisen in any of
the variants of
rma I use. These include versions running on workstations
and servers
running SunOS 4.1.x and Solaris 2.x. The version of
rma discussed here
has been tested under SunOS 4.1.4 and Solaris 2.5. Although
I have not
tested them all, rma should compile on almost any UNIX
variant having an
ANSI C compiler with very minimal modifications.
About the Author
Greg Wade has a masters degree in computer science
from Southern
Illinois University at Carbondale (SIUC). He is currently
employed by
the Department of Computer Science at SIUC as the assistant
lab
director, and with the Universities Water Information
Network (UWIN) as
a computer information specialist. Mr. Wade also does
independent
consulting, and conducts research in information systems
and spatial
decision support systems.
|