|< Day Day Up >|
Commonsense Preventive Measures
We've reached a good time to point out some commonsense preventive measures to provide basic security for your machine. They don't guarantee the safety of your machine, of course, but they provide good basic guidelines for you. These commonsense activities apply not only to Mac OS X, but also to any other operating system.
The commonsense preventive security measures that you should always keep in mind are
Limiting Access to Administrative Accounts: sudo and /etc/sudoers
Finally, an important commonsense measure you can take is to limit access to the abilities of the root account. The configuration with which Apple ships Mac OS X includes an admin group, any members of which can execute any command they want with root privileges. If you have multiple people with user accounts that you want to be able to perform various administrative functions, giving them all complete access to run any command as root is a serious security threat.
Your best option is to use the capabilities of the sudo command in the fashion it was originally intended: to provide limited access to specific privileged functions to specific users. Covering the complete syntax and configuration options for sudo is a topic best suited for an entire chapter, but we'll cover enough to get you started here.
sudo, and which users it will allow to execute which commands, is controlled by the file /etc/sudoers. This file is composed of a number of lines in Extended Backus-Naur Form (EBNF), which is a formalized way of writing definitions when the definitions are of the form "An A is a B or a C; Cs can be Ds or Es and Fs; and Es are types of fruit." That is, descriptions where left-side terms are equated to right-side definitions, and definitions may be other terms, ordered lists of terms, or terminal nodes. Terminal nodes are final, nonsubdividable entries. BNF and EBNF (the differences are beyond the scope of this book) are used, either directly or indirectly, in a number of computer configuration methods. BNF form definitions lend themselves to being used either as the direct content of a configuration file where the configuration must be a set of hierarchical definitions (such as who's allowed to run what commands), or the syntax of the configuration may be explained in terms of its BNF definition. (And, in fact, many computer languages, programs that can be thought of as very large configuration specifications, can be usefully defined in BNF.)
As an example of how BNF works, a simple first attempt at creating a BNF description for the English language might look something like this:
sentence : ([subject] [verb] [object]), ([subject] [verb]) subject : [phrase] object : (a [phrase]) phrase : [noun], ([adjective] [noun]) verb : hit, threw, chased noun : tommy, ball adjective : red, big
If we consider things in parentheses to be ordered lists of terms, things separated by commas to indicate choices between multiple things, things in square braces to be terms, and things without square braces to be terminal nodes, we have a BNF definition that can be used to build potential English-language sentences. Using this BNF to construct valid sentences, we can come up with things such as
"tommy hit a ball"
"tommy hit a big ball"
"big tommy hit a red ball"
Although English makes a good example for explaining simple BNF grammars, it's much too complex a language to completely codify in BNF form. Because of this, the BNF grammar I've constructed also would allow sentences such as "ball threw" and "red ball chased a big tommy." Fortunately, the language required for most things such as constructing configuration files is not nearly as complex as English. For /etc/sudoers, the language required is simply a way of specifying users, the commands they are allowed to run, and the user IDs under which the commands are run when these users invoke sudo. In the case of sudo, the configuration file consists of individual configuration lines specifying aspects of the configuration. There can be as many configuration lines as you need to completely specify the users and permissions desired. /etc/sudoers contains two types of configuration lines: alias lines and user-specification lines.
The alias lines are used to build a hierarchical specification of users, user IDs, or commands. The user-spec lines are then used to combine these into useful definitions as to who is allowed to do what and how. The alias lines conform to LINEs created by the following EBNF here I'm using commas to separate choices and square braces to indicate nonterminal terms. Parentheses again indicate an ordered grouping of terms, and angle braces denote textual explanations of a valid value. Single quotes surround characters that appear verbatim in a value, where those verbatim characters might be misunderstood as part of the EBNF itself. Exclamation point characters are used to negate a value.
LINE : [User_Line], [Runas_Line], [Cmnd_Line] User_Line : (User_Alias [NAME] = [User_List]) Runas_Line : (Runas_Alias [NAME] = [Runas_List]) Host_Line : (Host_Alias [NAME] = [Host_List]) Cmnd_Line : (Cmnd_Alias [NAME] = [Cmnd_List]) NAME : <A NAME is a string containing upper-case letters, numbers, and the underscore characters '_'. A NAME must start with an uppercase letter.> User_List : [User], ([User]',' [User_List]) User : [UserType], ![UserType] UserType : <username>, %<groupname>, <NAME where NAME is a defined User_Alias>
A User_List is made up of one or more usernames, system groups (prefixed with %), and other User_Alias aliases. Each item can be prefixed with an ! to negate the sense of the definition.
Runas_List : [RunasUser], ([RunasUser]',' [Runas_List]) RunasUser : [RunasType], ![RunasType] RunasType : <username>, #<userid>, %<group>, <NAME where NAME is a defined Runas_Alias>
A Runas_List is similar to a User_List except that it can also contain numeric user IDs (prefixed with #) and instead of User_Aliases, it can contain Runas_Aliases.
Host_List : [Host], ([Host]',' [Host_List]) Host : [HostType], ![HostType] HostType : <hostname>, <ipaddress>, <network range>, <NAME where NAME is a Host_Alias>
A Host_List is made up of one or more hostnames, IP addresses, network numbers, and other Host_Alias aliases. The Host_Alias directive is of only minor use if you're not building a cooperating group of machines. By allowing host specifications, the same /etc/sudoers file can be used on all machines of a cluster, yet have customized and specific actions on each. This allows much more convenient maintenance than if you needed to create and configure the file on each machine individually.
Cmnd_List : [Cmnd], ([Cmnd]',' [Cmnd_List]) Cmnd : [CmndType], ![CmndType] CmndType : [CommandName], <directory path>, <NAME where NAME is a defined Cmnd_Alias> CommandName : <command path>, (<commandpath> <args>), (<commandpath> '""')
A Cmnd_List is a list of one or more CommandNames, directories, and other Cmnd_Alias aliases. A CommandName is a fully qualified file path that might include shell-style wildcards. A simple filename enables the user to run the command with any arguments she wants. However, you can also specify command-line arguments (including wildcards). Alternately, you can specify "" to indicate that the command can be run only without command-line arguments. A directory path is a fully qualified path to a directory ending in a /. When you specify a directory in a Cmnd_List, the user can run any file within that directory (but not in any subdirectories therein). If a Cmnd has associated command-line arguments, the arguments given by the user on the command line must exactly match those in the Cmnd (or match the wildcards, if there are any). Note that the following characters must be escaped with a \if they're used in command arguments: ",", ":", "=", and "\."
After you've defined the various elements you need using the alias line syntax, you use these to define who may do what, using ULINEs in the following user-specification syntax:
ULINE : ([User_list] [WhereWhat]) WhereWhat : ([Host_List] = [Cmnd_Spec_List]), ([Host_List] = [Cmnd_Spec_List] : [WhereWhat]) Cmnd_Spec_List : [Cmnd_Spec], ([Cmnd_Spec]',' [Cmnd_Spec_List]) Cmnd_Spec : [Runas_Spec] [Passwd_Spec] [Cmnd] Runas_Spec : '(' [Runas_List] ')', '' Passwd_Spec : 'PASSWD:', 'NOPASSWD:', ''
A user-specification line consists of a User_List (constructed by the EBNF rules shown previously), for which this particular rule is true. This is followed by a Host_List defining the set of hosts on which the commands specified may be run. To this Host_List is assigned a Cmnd_Spec_List defining the commands that this user is allowed to run on the hosts that match the Host_List. There may be additional [Host_List] = [Cmnd_Spec_List] entries on the same line, separated from each other by colons. Each Cmnd_Spec_List is composed of previously defined Cmnd definitions (which, by convoluted logic, you'll note may be Cmnd_Aliases to lists of Cmnds). For each of these, the Cmnd may optionally be preceded by a Runas_List enclosed in parentheses or by the flags PASSWD: or NOPASSWD:. Inclusion of a Runas_List overrides the default root user ID as the commands run through sudo are executed. Inclusion of NOPASSWD: or PASSWD: allows overriding (or making explicit) the requirement for the user to enter a password to run commands in the Cmnd_Spec as the alternative user ID. Password checking can be turned on for some commands and off for others in the same ULINE by prefixing the respective group's Cmnd_Specs with PASSWD: and NOPASSWD: as appropriate.
All this might seem a bit complex, especially when you start looking at a definition such as that for a Cmnd, where a Cmnd may be the name of a defined Cmnd_Alias, which may be a Cmnd_List, which may be a Cmnd. But it's actually rather simple when you start following concrete examples through the EBNF grammar.
First, let's look at the /etc/sudoers file that comes with Mac OS X by default:
# sudoers file. # # This file MUST be edited with the 'visudo' command as root. # # See the sudoers man page for the details on how to write a sudoers file. # # Host alias specification # User alias specification # Cmnd alias specification # Defaults specification # User privilege specification root ALL=(ALL) ALL %admin ALL=(ALL) ALL
Not much to it, so it couldn't be too difficult to interpret, could it? Two lines of user specification. They both make rather copious use of the built-in ALL alias, which functions as "All valid values in this position." Parsing these lines is particularly easy. For the first ULINE, the User_List portion is simply root, so it's a line that specifies some things that root's allowed to do through sudo. The Host_List portion is ALL, so root's allowed to do the things listed anywhere. The optional Runas_Spec is ALL as well, so root's allowed to do (some as yet unspecified) things, as anyone through sudo. Finally, the Cmnd_Spec_List portion is again ALL, indicating that root is allowed to do anything, as anyone, anywhere, using the sudo command. This isn't particularly surprising. If root wanted to do something under an alternative user ID, even if it couldn't be done by specifying an alternative user ID to sudo, root can always su to anyone.
The second ULINE, which is quite similar to the first, is the one that's a bit disturbing for your day-to-day system security. Its User_List portion is specified by group name instead of by user ID, and it enables anyone in the group admin to have exactly the same complete access to the system as root. If you're the only user on your machine, this isn't a concern. But if your machine is a multiuser machine, having everyone who needs any administrative access getting all administrative access is a bad idea and a recipe for an eventual security disaster.
Instead of this rather poor configuration, consider something that limits sub-administrator access to specific commands. Even with limited access, there might be ways for a person with malicious intent to find a way to do harm, but there's no sense in giving everyone the combination to the bank vault if what they need to do their job is only a key to the utility closet. Your needs will be as individual as your system, but you should be able to build a much more competent administrative hierarchy for sudo by studying the following example and adapting it to your needs.
Let's consider an /etc/sudoers file that contains the following definitions (lines that start with # are comments):
# User alias specification User_Alias FULLTIMERS = john, will, joan User_Alias PARTTIMERS = adam, robyn, jack User_Alias WEBMASTERS = sandy, rich User_Alias PROGRAMMERS = dave, bob User_Alias SECRETARIES = karen, nancy, dan
Here, the file defines five named groups of users: FULLTIMERS, PARTTIMERS, WEBMASTERS, PROGRAMMERS, and SECRETARIES. You might imagine that these separate full-time administrative staff from part-time staff, and webmaster-type administrators (who probably aren't experienced Unix administrators, but who need some root-like functions to do things such as stop and start the web server) from programmers and secretaries. Programmers might have some reason to have limited special privileges, such as installing software, and our secretaries can do some general maintenance without needing an administrator around.
# Runas alias specification Runas_Alias OP = root Runas_Alias DB = mysql Runas_Alias SW = software
Three aliases are constructed to specific users to allow the commands to be run with their user IDs:
# Host alias specification Host_Alias SPARC = soyokaze, rosalyn, ryoko, rodan :\ SGI = waashu, oni, halo :\ ALPHA = godzilla :\ LINUX = mother, venice, hedora Host_Alias CUNETS = 188.8.131.52 Host_Alias SERVERS = ftp, mail, www Host_Alias CDROM = waashu, ryoko, soyokaze
The first Host_Alias line uses the : separator to define several aliases simultaneously for a number of different machines by name. The next line (remember that \in the file itself causes the next physical line to be read as a continuation of this one) specifies a network range. They are all machines in the 140.254.12. C-class network; there's an additional netmask parameter available if you're building truly complex configurations see the sudoers man page for more information. Next, an alias for some server machines is constructed, and finally an alias for some machines that have CD-ROMs. Note that membership in one Host_Alias doesn't preclude membership in others. All the machines that have CD-ROMs are also members of their respective hardware-type alias groups.
# Cmnd alias specification Cmnd_Alias DUMPS = /usr/bin/mt, /usr/sbin/dump, /usr/sbin/rdump,\ /usr/sbin/restore, /usr/sbin/rrestore Cmnd_Alias KILL = /usr/bin/kill Cmnd_Alias INSTALL = /usr/bin/make install Cmnd_Alias PRINTING = /usr/sbin/lpc, /usr/bin/lprm Cmnd_Alias SHUTDOWN = /usr/sbin/shutdown Cmnd_Alias HALT = /usr/sbin/halt, /usr/sbin/fasthalt Cmnd_Alias REBOOT = /usr/sbin/reboot, /usr/sbin/fastboot Cmnd_Alias SHELLS = /usr/bin/sh, /usr/bin/csh, /usr/bin/ksh, \ /usr/local/bin/tcsh, /usr/bin/rsh, \ /usr/local/bin/zsh Cmnd_Alias SU = /usr/bin/su
Now we have some Cmnd_Alias lines configured with a few useful groups of commands. The DUMPS Cmnd_Alias includes commands you'd need to run to do dump/restore-type backups. PRINTING includes control programs for lpr/lpd access to CUPS. SHELLS includes those applications that can also function as user login shells. The others are fairly self-explanatory.
Making use of all these definitions is now rather easy: Simply construct ULINE enTRies that tie them together to explain who can do what and where it can be done. Perhaps we want to allow our full-time staff complete access to any machine as root, whereas our part-time staff should be able to run various commands as root, but we don't want them to be able to start up a root shell:
FULLTIMERS ALL = (OP) ALL PARTTIMERS ALL = (OP) ALL, !SU, !SHELLS
We have an additional administrative user (scott) who's only responsible for backup and restore functions on the server machines:
scott SERVERS = (OP) DUMPS
We want the secretaries to be able to kill off printer jobs that are causing problems. We'd also like them to be able to use the CD-ROM burner to write a user's files to CD-R without needing them to be able to su to that user ID to access the files, so the secretaries need hdiutil and ditto:
SECRETARIES ALL = (OP) PRINTING, /usr/bin/hdiutil, \ /usr/bin/ditto
We also have a few people who fiddle with and compile software, who we trust enough to install it as under the auspices of our software user, but to whom we don't want to give full access to the software user's account:
PROGRAMMERS ALL = (SW) INSTALL
On the Linux boxes, we'd like scott to be able to do shutdown and reboot type administrative tasks:
scott LINUX = (OP) SHUTDOWN, HALT, REBOOT
User dave needs to have access to kill hung processes on his desktop machine:
dave 192.168.1.18 = (OP) KILL
The webmasters need to have permission to run any command on the web server (a machine named www), with a generic webmaster user ID (which won't necessarily be able to do much). They also need to be able to restart the web server as root because it's run by the system startup scripts and the parent server is owned by root:
WEBMASTERS www = (webmaster) ALL, \ (OP) /System/Library/StartupItems/Apache/Apache start,\ (OP) /System/Library/StartupItems/Apache/Apache stop, \ (OP) /System/Library/StartupItems/Apache/Apache restart
Because we've been having a bit of trouble with the power in the room where the SGI workstations live, we're going to let any user run sync on them as root. This way, if any user happens to be around when the power goes out, she might be able to sync the drives before the uninterruptible power supplies die. We're not even going to require a password for this because it would be hard to hurt the machines by syncing the drives:
ALL SGI = (OP) NOPASSWD: /bin/sync
This is nowhere near a complete iteration of what can be configured using sudo and /etc/sudoers, or even a complete itemization of what can be built using the simple alias lines constructed in the first part of the example. It should, however, be enough to demonstrate how you can compartmentalize your administrative needs into separate tasks and users who can perform them. This compartmentalization, combined with a judicious use of reassigning directory and application ownerships, will enable you to vastly increase your machine's day-to-day security because it removes much of the need and/or temptation to run as the root user. If you believe your cluster configuration would benefit from greater richness in what can be permitted and limited via sudo, please see the sudoers man page the discussion here only brushes the surface.
|< Day Day Up >|