SAGE - Sage feature

Managing Network Security with cfengine, Part 3

burgess_mark

by Mark Burgess
<[email protected]>

Mark is associate professor at Oslo College and is the author of cfengine and winner of the best paper award at LISA 1998.


Also, see Part 1 (August issue) and Part 2 (October issue) of this article.

You can read more about cfengine and obtain it from <http://www.iu.hioslo.no/cfengine>. (Please note that the following Web sites are not associated with cfengine, and readers are advised to steer clear of them: www.cfengine.com, www.cfengine.org, www.cfengine.net)

WWW Security

The security of the Web is a slightly paradoxical business. On the one hand, we make a system for distributing files to anyone without the need for passwords, and on the other hand we are interested in limiting who gets what information and who can change what. If you want Web privacy you have to exclude the possibility of running untrusted CGI scripts—CGI programs you did not write yourself—since CGI programs can circumvent any server's security. This is due to a fundamental weakness in the way that a WWW server works: It makes user-CGI scripts incompatible with the idea of private WWW areas.

The problem with CGI is that in order for the httpd daemon to be able to read information to publish it, that information must be readable by the UID with which httpd runs (e.g., the "www" special user [you should not run with UID "nobody" since that can be mixed up with NFS mappings]). But CGI programs automatically run with this www UID also. Since it is not possible to restrict the actions of CGI programs that you did not write yourself, any CGI program has automatically normal file permission access to any file the server can see. A CGI program could choose to open a restricted file, circumventing the security of the daemon. In short, privacy requires a separate UID (a separate daemon and port number) or a separate server host altogether.

Provided you acknowledge this weakness, you can still use cfengine to administer the permissions and access files on, say, two WWW servers from your central location. Let us imagine having a public WWW server and a private WWW server and assume that they have a common user/UID database. We begin by defining a user ID and group ID for the public and private services. These need to have different IDs in order to prevent the CGI trick mentioned above.

editfiles:

wwwpublic::

 { $(publicdocroot)/.htaccess

 AutoCreate
 EmptyEntireFilePlease
 AppendLine "order deny,allow"
 AppendLine "deny from all"
 AppendLine "allow from all"
 }

wwwprivate::

{ $(privatedocroot)/.htaccess

AutoCreate
EmptyEntireFilePlease
AppendLine "order deny,allow"
AppendLine "deny from all"
AppendLine "allow .mydomain.country"
}

Your documents should be owned by a user and group which are not the same as the UID/GID the daemon runs with; otherwise CGI programs and server-side embellishments could write and destroy those files. You will also want to ensure that the files are readable by the www daemon, so a files command can be used to this end. You might want a group of people to have access to the files to modify their contents.

files:

wwwprivate::

 $(privatedocroot) mode=664 owner=priv-data group=priv-data
           act=f ixall

wwwpublic::

 $(publicdocroot) mode=664 owner=public-data group=public-data
           act=f ixall

Pitfalls

Cfengine's ability to run your network depends, naturally, on the fact that it gets run. Normally you will run cfengine every hour or so as a cron task, but if you use cfengine itself to update cfengine's configuration from a trusted host, then a syntax error can bring this model to a quick halt. If cfengine cannot parse its configuration file, it will not be able to update, so one error here would be a disaster. The solution is to use a separate, simple script that updates the configuration only in case of accidents.

For example, you can get cfengine to install itself in the cron file like this:

control:

 cfbin = ( /usr/local/sbin )

editfiles:

 { /var/spool/cron/crontabs/root

 AppendIfNoSuchLine "0,30 * * * * $(cfbin)/cfwrap $(cfbin)/cfnormal"
 AppendIfNoSuchLine "15 * * * * $(cfbin)/cfwrap $(cfbin)/cfupdate"
 }

Cfwrap is a wrapper script included with cfengine that mails the output of cfengine to someone more useful than root (the owner of the cron job). Cfnormal is a small script that sets environment variables to point to your cfengine input files and runs cfengine:

#!/bin/sh
# cfnormal

CFINPUTS /etc/cfengine/inputs ; export CFINPUTS
/etc/cfengine/bin/cfengine

Cfupdate, on the other hand, runs a special file whose job it is to copy cfengine and its configuration to a known local filesystem such as /etc:

#!/bin/sh
# cfnormal

CFINPUTS /etc/cfengine/inputs ; export CFINPUTS
/etc/cfengine/bin/cfengine -f cf.update

This is an extra backup which makes sure that cfengine will always be able to update its configuration, even if you make a mistake in the configuration and it is unable to parse the input.

#
# Script only distributes the configuration
#

control:

 actionsequence = ( copy )
 domain = ( iu.hioslo.no )

copy:

 /local/share/cfengine dest=/etc/cfengine
  recurse=inf
  mode=a+rx
  type=binary
  exclude=*.lst
  server=trustedhost

 /local/sbin/cfengine dest=/etc/cfengine/bin/cfengine
  mode=755
  type=checksum
  server=trustedhost

The purpose of this roundabout method is that should network connections go down, cfengine will have everything it needs to do its job on a local file system, just like an immune system. Even if you insert a typo into the main cfengine configuration file (run by cfnormal), updates will get distributed around the network. So, while you will be able to shoot yourself in the foot, you will not be able to shoot yourself in the head. Of course cfengine will not be able to copy file updates from a server if the network is unavailable, but it will still do its job of checking and watching over the system in every other respect. In this way, it is impossible to perform a complete denial-of-service attack on cfengine. This can be contrasted with systems that use network protocols in every operation.

If you use cfengine on hosts that have mounted NFS file systems, it is a bad idea to give hosts setuid permissions on those NFS file systems. This can lead to accidents. (This is precisely why the root -> nobody mapping exists.) Normally cfengine detects NFS filesystem boundaries and does not descend into such file systems during recursive operations, but if you make file systems setuid root this can fail.

Always remember that processes that are started by a script or by cfengine inherit the environment variables that the parent script has, including timezone, path, and umask. If you are unwary, you might end up resetting the system clock or permission mask for certain services. Be careful.

Miscellaneous Security of Cfengine Itself

control:

 SecureInput = ( on )

If this is set cfengine will not read any files that are not owned by the UID running the program, or that are writeable by groups or others.

Privacy (Encryption)

Encryption (privacy) is not often a big deal in system administration. With the exception of the distribution of passwords and secret keys themselves, there is little or no reason to maintain any level of privacy when transferring system files (binaries, for instance). If you find yourself using a tool like cfengine to transmit company secrets from one place to another you should probably book yourself into the nearest asylum for a checkup. Cfengine is not about super-secure communication, but it can be used to perform the simple job of file distribution through an encrypted link (e.g., as an NIS replacement or other password distributor). Cfengine uses the triple DES implementation in Eric Young's SSLeay distribution (or equivalent) to provide "good enough" privacy during remote copying.

The most important issue in system security is authentication. Without the ability to guarantee the identity of a user or of trusted information it is impossible to speak of security at all. Although services like pidentd can go some way to confirming the identity of a user, the only nonspoofable way of confirming identity is to use a shared secret—a password. A password works by demanding that two parties who want to trust each other must both know a piece of information that untrusted parties do not. Without matching secrets it is impossible to prove someone's identity.

To copy a file over an encrypted link, you write:

copy:

 source dest=destination secure=true server=trusted

Bear in mind that the server must be a trusted host. Privacy won't help you if the data you are collecting is faulty. In order to use the DES algorithm there must be a secret key known by both hosts. You can use the program cfkey to generate a new key file. This file must then be distributed. In programs like ssh a method of key exchange is used. The problem of how secret keys are distributed is subtle. The process must be bootstrapped, preferably under secure conditions.

Under secure communications, cfengine conceals the names and contents of files. Initially, private keys are used to transmit a session key that is combined with part of the private key to randomize it, and also to avoid replay attack. Provided that the key files are private, this has the added side effect of authenticating both hosts for each other.

On the server side, you can choose whether root on a client host should have root privileges to read protected files on the server. In the cfd.conf file you make a list, rather like for NFS:

admit:

 /filetree *.domain.country root=myhost,yourhost

 /etc/shadow *.domain.country secure=true

In the example on the second line, you can also restrict access to certain files to secure lines, that is, demand that clients use a private connection to collect the file in order to prevent wiretapping.

Adaptive Locks

Cfengine treats all of its operations as transactions that are locked. Locking prevents contention from competing processes and also places reasonable limits on the execution of the program. The fact that operations are locked means that several cfengine programs can coexist without problems. Two locking parameters control the way in which operations can procure locks. The IfElapsed parameter tells operations that they can only be performed if a certain period of time has elapsed since the last time the action was performed. This is anti-spamming protection. The ExpireAfter parameter tells cfengine that no action should last more than a given length of time. This is protection against hanging subprocesses.

Spoofing

Spoofing refers to attempts to masquerade as another host when sending network transmissions. The cfd program that can be used to transfer files or activate cfengine remotely attempts to unmask such attempts by performing double reverse lookups in the name service. This verifies by a trusted server that the socket address and the host name are really what they claim to be. If you have the TCP wrappers package on your system (libwrap), then cfd will use this as additional protection. Also, if you have

CheckIdent = ( on )

cfd will demand verification of connections by attempting to connect to a pidentd server on the calling host. Secret keys also provide protection against spoofing by providing a confirmation of trustworthiness based on a shared secret.

Race Conditions in File Copying

When copying files from a source, it is possible that something might go wrong and leave a corrupt file in place. For example, the disk might become full while copying a file. This could lead to problems. Cfengine deals with this by always copying to a new file on the destination file system (prefix .cfnew) and then renaming it into place only if the transfer was successful. This ensures that there is space on the file system and that nothing went wrong with the network connection or the disk during copying.

size= in copy

As a further check on copying, cfengine allows you to define acceptable limits on the size of files. After all, sometimes errors might occur quite independently of anything you are doing with cfengine. Perhaps the master password file got emptied somehow or got replaced by a binary through some silly mistake. By making an estimate of the expected size of the file and adding it to the copy command, you can avoid installing a corrupt file and making a localized problem into a global one.

useshell= and owner= in Shell Commands

There are dangers in starting scripts from programs that run with root privileges. Normally, shell commands are started by executing them with the help of a /bin/sh -c command. The trouble with this is that it leaves you open to a variety of attacks. One example is fooling the shell into starting foreign programs by manipulating the IFS variable to treat "/" as a separator. You can ask cfengine to start programs directly, without involving an intermediary shell, by setting the useshell variable to false. The disadvantage is that you will not be able to use shell directives such as | and > in your commands. The owner=uid directive executes shell commands as a special user, allowing you to safely run scripts without root privilege.

Firewalls

Cfengine is a useful tool for implementing, monitoring, and maintaining firewalls. You can control what programs are supposed to be on the firewall and what programs are not. You can control file permissions, processes, and a dozen other things that make up the configuration of a bastion host. By referencing important programs against a read-only medium you can not only monitor host integrity but always be certain that you are never more than a cfengine execution away from correctness.

Summary

Cfengine is not a tool—it is an environment for managing host configuration and integrity. Its big advantage over many other configuration schemes is that you can have everything in one file (or set of files). The global file is common to every host, yet it can be as general or as specific as you want it to be. You can use it as a front end for cron, and you can use its advanced features to make your hosts converge to a desired, correct state.

Anonymous FTP example

Configuring a service like anonymous FTP requires a certain amount of vigilance. It is a good idea to automate it and let cfengine make sure that things don't go astray. Note that we constantly ensure that the ls program used by the anonymous FTP server is a trusted program by checking it with an md5 signature of a trusted version of the program. If for some reason it were replaced with a Trojan horse, cfengine would notice the incorrect checksum (md5) and move the bad program to ls.cf-saved and immediately replace it with the correct version without waiting for the administrator to act. The inform and syslog options ask for an explicit warning to be made about this copy. Here is a complete anonymous FTP setup and maintenance program for Solaris hosts.

control:

  actionsequence = ( directories copy editfiles files )

  # Define variables

  ftp = ( /usr/local/ftp )
  uid = ( 99 ) # ftp user
  gid = ( 99 ) # ftp group

directories:

 solaris::

  $(ftp)/pub mode=644 owner=root group=other
  $(ftp)/etc mode=111 owner=root group=other
  $(ftp)/dev mode=555 owner=root group=other
  $(ftp)/usr mode=555 owner=root group=other
  $(ftp)/usr/lib mode=555 owner=root group=other

files:

 solaris::

  $(ftp)/etc/passwd mode=644 o=root action=fixplain
  $(ftp)/etc/shadow mode=400 o=root action=fixplain
  $(ftp)/pub mode=644 owner=ftp action=fixall recurse=inf

copy:

 solaris::

  # Make sure ls is a trusted program by copying
  # a secure location...

 /bin/ls dest=$(ftp)/usr/bin/ls
  mode=111
  owner=root
  type=checksum
  inform=true
  syslog=true

 /etc/netconfig dest=$(ftp)/etc/netconfig mode=444 o=root

 /devices/pseudo/mm@0:zero dest=$(ftp)/dev/zero mode=666 o=root
 /devices/pseudo/clone@0:tcp dest=$(ftp)/dev/tcp mode=444 o=root
 /devices/pseudo/clone@0:udp dest=$(ftp)/dev/udp mode=666 o=root
 /devices/pseudo/tl@0:ticotsord dest=$(ftp)/dev/ticotsord mode=666 o=root

 /usr/lib  dest=$(ftp)/usr/lib recurse=2
  mode=444
  owner=root
  backup=false
  include=ld.so*
  include=libc.so*
  include=libdl.so*
  include=libmp.so*
  include=libnsl.so*
  include=libsocket.so*
  include=nss_compat.so*
  include=nss_dns.so*
  include=nss_files.so*
  include=nss_nis.so*
  include=nss_nisplus.so*
  include=nss_xfn.so*
  include=straddr.so*

 /usr/share/lib/zoneinfo dest=$(ftp)/usr/share/lib/zoneinfo
  mode=444 recurse=2 o=root type=binary

editfiles:

 solaris::

  #
  # Make sure that umask is right for ftpd
  # or files can be left 666 after upload!
  #

 { /etc/rc2.d/S72inetsvc

PrependIfNoSuchLine "umask 022"

}

{ $(ftp)/etc/passwd

AutoCreate
EmptyEntireFilePlease
AppendIfNoSuchLine "ftp:x:$(uid):$(gid):Anonymous FTP:$(ftp):/bin/sync"
}

{ $(ftp)/etc/group

AutoCreate

EmptyEntireFilePlease

AppendIfNoSuchLine "ftp::$(gid):"

}

{ $(ftp)/etc/shadow

AutoCreate
EmptyEntireFilePlease
AppendIfNoSuchLine "ftp:NP:6445::::::"
}

# Finally...useful for chown

{ /etc/passwd

AppendIfNoSuchLine "ftp:x:$(uid):$(gid):Anonymous FTP:$(ftp):/bin/sync"


?Need help? Use our Contacts page.
Last changed: 13 Dec. 1999 jr
Issue index
;login: index
SAGE home