Managing Network Security With Cfengine

Computer security is about protecting the data and availability of an association of hosts. Briefly, the key words are authentication, privacy, integrity and trust. To understand computer security we have to understand the interrelationships between all of the hosts and services on our networks as well as the ways in which those hosts can be accessed. Tools which allow this kind of management are complex and usually expensive. A simple free software program which is in widespread use and whose functionality is at least equal to commerical packages is cfengine. Cfengine runs on nearly every Unix-like system and on NT with the cygwin tools installed.

For a computer to be secure it must be *physically secure* --- if we can get our hands on a host then we are never more than a screwdriver away from all of its assets---but assuming that hosts are physically secure, we then wish to deal with the issues of software security which is a much more difficult topic. Software security is about access control and software reliability. No single tool can make computer systems secure. Major blunders have been made out of the belief that a single product (e.g. a `firewall') would solve the security problem. For instance, a few years ago a cracker deleted all the user directories from a dialup login server belonging to a major Norwegian telecommunications company, from the comfort of his web browser. This was possible, even through a firewall, because the web server on the host concerned was incorrectly configured. The bottom line is that there is no such thing as a secure operating system, firewall or none. What is required is a persistent mixture of vigilence and adaptability.

For many, security is perceived as being synonymous with network privacy or network intrusion. Privacy is one aspect of security, but it is not the network which is our special enemy. Many breaches of security happen from within. There is little difference between the dangers of remote access from the network or direct access from a console: privacy is about access control, no matter where the potential intruder might be. If we focus exclusively on network connectivity we ignore a possible threat from internal employees (e.g. the janitor who is a computer expert and has an axe to grind, or the mischievous son of the director who was left waiting to play mom's office, or perhaps the unthinkable: a disgruntled employee who feels as though his/her talents go unappreciated). Software security is a vast subject, because modern computer systems are complex. It is only exacerbated by the connectivity of the internet which allows millions of people to have a go at breaking into networked systems. What this points to is the fact that a secure environment requires a tight control of access control on every host individually, not merely at specific points such as firewalls.

This article is not a comprehensive guide to security. Rather it is an attempt to illustrate how cfengine can be used to help you automate a level of host integrity on all the hosts of your network. Cfengine is a network configuration tool with two facets. It is a language used to build an `expert system'. An expert system describes the way you would like your hosts and network to look and behave. CFengine is also a software robot which compares the model you have described with what the world really looks like and then sets to work correcting any deviations from that picture. In many ways it is like an immune system, neutralizing and repairing damaged parts. Unlike many shell-script packages for sysadmin, cfengine is a C program which means light on system resources. Also it works on a principle of `convergence'. Convergence means that each time you run cfengine the system should get closer to the model which you have described until eventually when the system is the model, cfengine becomes quiescent, just like an immune system. In the words of one user your hosts `never get worse'. This assumes of course that the model you have is what you really want. Using cfengine, model building becomes synonymous with formulating and formalizing a system policy.

What makes cfengine a security tool is that security policy is a part of system policy: you cannot have one without the other. You will never have security unless you are in control of your network. Cfengine monitors and indeed repairs hosts with simple easily controllable actions. From an automation perspective, security is no different from the general day to day business of system maintenance, you just need to pay more attention to the details. We cannot speak of `have security' and `have not security'. There is always security, it is simply a matter of degree: weak or strong; effective or ineffective.

A word of warning

Before starting it is only proper to state the obvious. You should never trust anyone's advice about configuration or security without running it past your own grey matter first. The examples provided here are just that: examples. They might apply to you as written and they might need to be modified. You should never accept and use an example without thinking carefully and critically first! Also, in any book of recipies or guide to successful living you know that there are simplified answers to complex questions and you should treat them as such. There is no substitute for real understanding.

Automation

Even in the smallest local area network you will want to build a scheme for automating host configuration and maintenance, because networks have a way of growing from one host into many quite quickly. It is therefore important to build a model which scales. A major reason for using cfengine is precisely for scalability. Whether you have one host or a hundred makes little difference. Cfengine is instructed from a central location, but its operation is completely and evenly spread across the network. Each host is responsible for obtaining a copy of the network model from a trusted source and is then responsible for configuring itself without intervention from outside. Unlike some models, cfengine does not have to rely on network communication or remote object models.

We also need integration, or the ability to manage the interrelationships between hosts. It is no good having complete control of one important host and thinking that you are secure. If an intruder can get into any host, he or she is almost certain to get into the ones that matter, especially if you are not looking at all of them. Using cfengine is a good way of forcing yourself to formulate a configuration/security policy and then stick to it. Why cfengine? There are three reasons: i) it forces a discipline of preparation which focuses you on the problems at the right level of detail, ii) it provides you with `secure' scalable automation and a common interface to all your hosts, and iii) it scales to any number of hosts without additional burdens. We'll need to qualify some of these points below.

The first step in security management is to figure out a security policy. That way, you know what *you* mean by security and if that security is breached, you will know what to do. In many cases you can formulate a large part of your security policy as cfengine code. That makes it formal, accurate and it means that it will get done by the robot without requiring any more work on your part.

As an immune system, cfengine will even work fine in a partially connected environment it makes each host responsible for its own state. It is not reliant on network connectivty for remote method invocations or CORBA-style object requests as is, say, Tivoli. All it needs is an authentic copy of the network configuration document stored locally on each host. If this is the case, a detached host will not be left unprotected, at worst it might lag behind in its version of the network configuration.

Trust

There are many implicit trust relationships in computer systems. It is crucial to understand them. If you do not understand where you are placing your trust, your trust can be exploited by attackers who have thought more carefully than you have.

For example, any NFS server of users' home-directories trusts the root user on the hosts which mount those directories. Some bad accidents are prevented by mapping root to the user nobody on remote systems, but this is not security, only convenience. The root user can always use `su' to becomes any user in its password file and access/change any data within those filesystems. The .rlogin and hosts.equiv files on Unix machines grant root (or other user) privileges to other hosts without the need for authentication.

If you are collecting software from remote servers, you should make sure that they come from a machine that you trust, particularly if they are files which could lead to privileged access to your system. Even checksums are no good unless they also are trustworthy. For example, it would be an extremely foolish idea to copy a binary program such as /bin/ps from a host you know nothing about. This program runs with root privileges. If someone were to replace that version of ps with a Trojan horse command, you would have effectively opened your system to attack. Most users trust anonymous FTP servers where they collect free software. In any remote copy you are setting up an implicit trust relationship. First of all you trust integrity of the host you are collecting files from. Secondly you trust that they have the same username database with regard to access control. The root user on the collecting host has the same rights to read files as the root user on the server. The same applies to any matched user name.

Why trust cfengine?

Cfengine has a very simple trust model. It trusts the integrity of its input file and any data which is explicitly chooses to download. Cfengine places the responsibility on root@localhost not on any outsiders. *You* can make cfengine destroy your system, just as you can destroy it yourself, but no one else can, so as long as you are careful with the input file you are trusting essentially no-one. We shall qualify this below for remote file copying.

Cfengine assumes that its input file is secure. Apart from that input file, no part of cfengine accepts or uses any configuration information from outside sources. The most one could do from an authenticated network connection is to ask cfengine to carry out (or not) certain parts of its model, thus in the worst case scanario an outside attacker could spoof cfengine into configuring the host correctly. In short, no one except root@localhost can force cfengine to do anything (unless root access to your system has already been compromised by another route). This means that there is a single point of failure. The input file does not even have to be private as long as it is authentic. No one except you can tell cfengine what to do.

There is a catch though. Cfengine can be used to perform remote file transfer. In remote file transfer one is also forced to trust the integrity of the data received, just as in any remote copy scheme. Although cfengine works hard to authenticate the identity of the host, once the host's identity is verified it cannot verify the accuracy of unknown data it has been asked to receive. Also, as with all remote file transfers, cfengine could be tricked by a DNS spoofing into connecting to an imposter host, so use the IP addresses of hosts, not their names if you don't trust your DNS service. In short, these faults are implicit in remote copying. They do not have to do with cfengine itself. This has nothing to do with encryption as users sometimes believe: encrypted connections do not change these trust relationships---they improve the privacy of the data being transmitted not their accuracy or trustworthiness.

The point of cfengine is normally to have only one global configuration for every host. This needs to be distributed somehow which means that hosts must collect this file from a remote server. This in turn means that you must trust the host which has the master copy of the cfengine configuration file.

Configuration

The beginning of security is correct host configuration. Even if you have a firewall shielding you from outside intrusion, an incorrectly configured host is a security risk. Host configuration is what cfengine is about, so we could easily write a book on this. Rather than reiterating the extensive documentation, let's just consider a few examples which address actual problems and get down to business without further ado.

A cfengine configuration file is composed of objects with the following syntax (see the cfengine documentation):

  rule-type:

    classes-of-host-this-applies-to::

          Actual rule 1
          Actual rule 2 ...

The rule-types include checking file permissions, editing textfiles, disabling (renaming and removing permissions to) files, controlled execution of scripts and a variety of other things relating to host configuration. Some of the `control' rules are simply flags which switch on complex (read `smart') behaviour. Every cfengine program needs an actionsequence which tells it the order in which bulk configuration operations should be evaluated. e.g.

control:

  actionsequence = ( netconfig copy processes editfiles )

You should look at the cfengine manual to get started with your configuration.

Let us step through some basic idioms which can repeated in different contexts. As representative examples we shall take solaris and GNU/Linux as example operating systems. This is not to single them out as being particularly secure or insecure, it is merely due to their widespread use and for definiteness.

Disabling and replacing software

One of the simplest things which we are asked to do constantly is to disable dangerous programs as bugs are discovered. CERT security warnings frequently warn about programs with flaws which can compromise a system. In cfengine, disabling a file means renaming it to *.cf-disabled and setting its permission to 400.
 disable:

   #
   # CERT security patches
   #

   solaris:: 

     /usr/openwin/bin/kcms_calibrate
     /usr/openwin/bin/kcms_configure
     /usr/bin/admintool
     /etc/rc2.d/S99dtlogin
     /usr/lib/expreserve

   linux::

      /sbin/dip-3.3.7n
      /etc/sudoers
      /usr/bin/sudoers

Although this is a trivial matter, the fact that it is automated means that cfengine is checking for this all the time. As long as a host is up and running (connected to the network or not) cfengine will be ensuring the named file is not present.

Another issue is to replace standard vendor programs with drop-in replacements. For example, most admins would like to replace their vendor sendmail with the latest update from Eric Allman's site. One way to do this is to compile the new sendmail into a special directory, separate from vendor files and then to symbolically link the new program into place.


 links:

   solaris||linux::

    /usr/lib/sendmail      ->!  /usr/local/lib/mail/bin/sendmail-8.9.3
    /usr/sbin/sendmail     ->!  /usr/local/lib/mail/bin/sendmail-8.9.3
    /etc/mail/sendmail.cf  ->!  /usr/local/lib/mail/etc/sendmail.cf

The exclamation marks mean (by analogy with the csh) that existing file objects should be replaced by links to the named files. Again the integrity of these links is tested every time cfengine runs. If the object /usr/lib/sendmail is not a link to the named file, the old file is moved and a link is made. If the link is okay, nothing happens. After putting the new sendmail in place, you will need to make sure that the restricted shell configuration is in order.
   #
   # Sendmail, restricted shell needs these links
   #

   solaris::

     # Most of these will only be run on the MailHost
     # but flist (procmail) is run during sending...

     /usr/adm/sm.bin/vacation -> /usr/ucb/vacation
     /usr/adm/sm.bin/flist   ->  /home/listmgr/.bin/flist

   linux::

     /usr/adm/sm.bin/vacation -> /usr/bin/vacation

Link management is a particularly useful feature of cfengine. By putting links (actually all system modifications) into the cfengine configuration and never doing anything by hand, you build up a system which is robust to reinstallation. If you lose your host, you just have to run cfengine once or twice to reconstruct it.

Of course, the fundamental tenet of security is to be able to restrict privilege to resources. We therefore need to check the permissions on files. For instance, a recent CERT advisory warned of problems with some free unix mount commands which were setuid root. If we suppose there is a group of hosts called `securehosts' which we don't need to worry about, then we could remove the setuid bits on all other hosts as follows:


 files:

   !securehosts.linux::

      /bin/mount     mode=555 owner=root action=fixall
      /bin/umount    mode=555 owner=root action=fixall

   securehosts.linux::

      /bin/mount      m=6555 o=root action=fixall
      /bin/umount     m=6555 o=root action=fixall

One area where cfengine excels over other tools is in its ascii file editing abilities. Editing textfiles in a non-destructive way is such an important operation that having used it you will wonder how you every managed without it! Here are some simple but real examples of how file editing can be used.

 editfiles:

   # sun4, who are they kidding?

   { /etc/hosts.equiv

   HashCommentLinesContaining "+"
   }

   #
   # CERT security patch for vold vulnerability
   #

   sunos_5_4::

      { /etc/rmmount.conf

      HashCommentLinesContaining "action cdrom"
      HashCommentLinesContaining "action floppy"
      }

TCP wrapper configuration can be managed easily by maintaining a pair of master files on a trusted host. Files of the form

 # /etc/hosts.allow (exceptions)
 #
 # Public services

 sendmail: ALL
 in.ftpd:  ALL
 sshd:     ALL

 # Private services

 in.fingerd:  .mydomain.country LOCAL
 in.cfingerd: .mydomain.country LOCAL
 cfd:         .mydomain.country LOCAL
 sshdfwd-X11: .mydomain.country LOCAL

 # Portmapper has to use IP series

 portmap: 128.39.89. 128.39.74. 128.39.75.
and

 # /etc/hosts.deny (default)

 ALL: ALL

may be distributed to each host by cfengine
copy:

 /masterfiles/hosts.deny dest=/etc/hosts.deny 
                         mode=644
                         server=trusted
 /masterfiles/hosts.allow dest=/etc/hosts.allow 
                          mode=644 
                          server=trusted
and installed as follows
 editfiles:

      { /etc/inet/inetd.conf

      # Make sure we're using tcp wrappers

      ReplaceAll "/usr/sbin/in.ftpd"    With "/local/sbin/tcpd"
      ReplaceAll "/usr/sbin/in.telnetd" With "/local/sbin/tcpd"
      ReplaceAll "/usr/sbin/in.rshd"    With "/local/sbin/tcpd"
      ReplaceAll "/usr/sbin/in.rlogind" With "/local/sbin/tcpd"

 processes:

      "inetd" signal=hup

The services which we do not need should be removed altogether. There's no sense in tempting fate:

editfiles:

      { /etc/inetd.conf

      # Eliminate unwanted services

      HashCommentLinesContaining "rwall"
      HashCommentLinesContaining "/usr/sbin/in.fingerd"
      HashCommentLinesContaining "comsat"
      HashCommentLinesContaining "exec"
      HashCommentLinesContaining "talk"
      HashCommentLinesContaining "echo"
      HashCommentLinesContaining "discard"
      HashCommentLinesContaining "charge"
      HashCommentLinesContaining "quotas"
      HashCommentLinesContaining "users"
      HashCommentLinesContaining "spray"
      HashCommentLinesContaining "sadmin"
      HashCommentLinesContaining "rstat"
      HashCommentLinesContaining "kcms"
      HashCommentLinesContaining "comsat"
      HashCommentLinesContaining "xaudio"
      HashCommentLinesContaining "uucp"
      }

Process monitoring

When it comes to process management we are usually interested in three things: i) making sure certain processes are running, ii) making sure some processes are NOT running and iii) sending HUP signals to force configuration updates. To HUP a daemon and make sure that it is running, we write
processes:

 linux::
  
  "inetd"  signal=hup restart "/usr/sbin/inetd"   useshell=false
  "xntp"              restart "/local/sbin/xntpd" useshell=false
 
The useshell option tells cfengine that it should not use a shell to start the program. The idea here is to protect against IFS attacks. Unfortunately some programs require a shell in order to be started, but most do not. This is an extra precaution. When the cron daemon crashes, restarting it can be a problem since it does not close its filed descriptors properly when forking. The dumb-option helps here:

  "cron" matches=>1 restart "/etc/init.d/cron start"  useshell=dumb

To kill processes which should not be running, we write:

 processes:

   solaris::

   #
   # Don't want CDE stuff or SNMP peepholes...
   #

    "ttdbserverd" signal=kill
    "snmpd"       signal=kill
    "mibiisa"     signal=kill

A couple of years ago, a broken cracked account was revealed at Oslo College by the following test in the cfengine configuration:
processes:

   # Ping attack ?

   "ping"  signal=kill inform=true

There are few legimate reasons to run the ping command more than a few times. The chances of cfengine detecting single pings is quite small. But coordinated ping attacks are another story. When it was revealed that a user had twenty ping processes attempting to send large ping packets to hosts in the United States it was obvious the the account had been compromised. Fortunately for the recipient, the ping command was incorrectly phrased and would probably not have been noticed.
processes:

     "sshd"        
                  restart "/local/sbin/sshd"
                  useshell=false    

     "snmp"       signal=kill
     "mibiisa"    signal=kill

     "named"      matches=>1
                  restart "/local/bind/bin/named"
                  useshell=false

     # Do the network community a service and run this

     "identd"   restart "/local/sbin/identd" inform=true

Process management also includes the garbage collection which we shall return to briefly.

Monitoring files

Almost all security programs available are for the monitoring of file integrity. Cfengine also incorporates tools for monitoring files. Here are some of the elements in the faily complex files command:

 files:

     classes::

        /file-object
                          mode=mode
                          owner=uid-list
                          group=gid-list
                          action=fixall/warnall..
                          ignore=pattern
                          include=pattern
                          exclude=pattern
                          checksum=md5
                          syslog=true/on/false/off

In additions to these, there are extra flags for BSD filesystems and ways of managing file ACLs for systems like NT. Here are some examples of basic checks on file permissions:

 classes:

  # Define a class of hosts based on a test...

  have_shadow = ( `/bin/test -f /etc/shadow` )

  NFSservers = ( server1 server2 )

 files:

   any::

      /etc/passwd mode=0644 o=root  g=other  action=fixplain

   have_shadow::

      /etc/shadow mode=0400 o=root  g=other  action=fixplain

   # Takes a while so do this at midnight and only on servers

   NFSservers.Hr00::

      /usr/local 
            mode=-0002   Check no files are writable!
            recurse=inf 
            owner=root,bin 
            group=0,1,2,3,4,5,6,7,staff
            action=fixall

In the last example we parse through a whole file system (recurse=inf) and as a result we get a number of checks for free. Any previously unknown setuid programs are reported as well as any suspicious filenames (see below).

The setuid log

Cfengine is always on the lookout for files which are setuid or setgid root. It doesn't go actively looking for them uninvited, but whenever you get cfengine to check a file or directory with the files feature, it will make a note of setuid programs it finds there. These are recorded in the file cfengine.host.log which is stored under /etc/cfengine or /var/log/cfengine. When new setuid programs are discovered, a warning is printed, but only if you are root. If you ever want a complete list, delete the log file and cfengine will think that all of the setuid programs it finds are new. The log file is not readable by normal users.

Suspicious filenames

Whenever cfengine opens a directory and scans through files and directories (recursively) (files, tidy, copy), it is also on the lookout for for suspicious filenames, i.e. files like ".. ." containing only space and/or dots. Such files are seldom created by sensible sources, but are often used by crackers to try to hide dangerous programs. Cfengine warns about such files. Although not necessarily a security issue, cfengine will also warn about filenames which contain non-printable characters if desired, and directories which are made to look like plain files by giving them filename extensions.

control:

   #
   # Security checks
   #

   NonAlphaNumFiles = ( on )
   FileExtensions = ( o a c gif jpg html ) # etc
   SuspiciousNames = ( .mo lrk3 lkr3 )

The file extension list may be used to detect concealed directories during these searches, if users create directories which look like common files this will be warned about. Additional suspicious filenames can be checked for automatically as a matter if course. This is commented further below. The mail spool directory is a common place for users to try to hide dowloaded files. These options inform about files which do not have the name of a user or are not owned by a valid user:

 control:

   WarnNonOwnerMail = ( true )
   WarnNonUserMail = ( true )  # Warn about mail which is not owned by a user

Corresponding commands exist to delete these files without further ado. This can be a useful way of cleaning up after users whose accounts have been removed.

Checksums and Tripwire functionality

Cfengine can be used to check for changes in files which only something as exacting as an MD5 checksum/digest can detect. If you specify a checksum database and activate checksum verification,

control:

  ChecksumDatabase = ( /etc/cfengine/cache.db )

  ChecksumUpdates = ( false )

files:

    /filename checksum=md5 ....
    /dirname  checksum=md5 recurse=inf....

    # If the database isn't secure, nothing is secure...

    /etc/cfengine/cache.db  mode=600 owner=root action=fixall

then cfengine will build a database of file checksums and warn you when files' checksums change. This makes cfengine act like Tripwire (currently only with MD5 checksums). It can be used to show up Trojan horse versions of programs. It should be used sparingly though since database management and MD5 checksum computation are resource intensive operations and this could add significant time to a cfengine run. The ChecksumUpdates variable (normally false) can be set to true to update the checksum database when programs change for valid reasons. Warnings are all every fine and well, but the spirit of cfengine is not to bother us with warnings, it is to fix things automatically. Warning is a useful supplement, but in security breaches it is better to fix the problem, rather than leaving the host in a dangerous state. If you are worried about the integrity of the system then don't just warn about checksum mismatches here, make an md5 copy comparison against a read-only medium which has correct, trusted version of the file on it. That way if a binary is compromised you will not only warn about it but also repair the damage immediately! The control variable ChecksumUpdates may be switched to on in order to force cfengine to update its checksum database after warning of a change.

FileExtensions

This list may be used to define a number of extensions which are regarded as being plain files by the system. As part of the general security checking cfengine will warn about any directories which have names using these extensions. They may be used to conceal directories.

  FileExtensions = ( c o gif jpg html )

NonAlphaNumFiles

If enabled, this option causes cfengine to detect and disable files which have purely non-alphanumeric filenames, i.e. files which might be accidental or deliberately concealed. The files are then marked with a suffix .cf-nonalpha and are rendered visible.

  NonAlphaNumFiles = ( on )

These files can then be tidied (deleted) or disabled by searching for the suffix pattern. Note that alphanumeric means ascii codes less than 32 and greater than 126.

Defensive garbage collection

We tend to be worried about the fact that crackers will destroy our systems and make them unusable, but many operating systems are programmed to do this to themselves! There are few systems which can survive a full system disk and yet many logging agents go on filling up disks without ever checking to see how full they are getting. In short they choke themselves in a self-styled denial of service attack. Cfengine can help here by rotating logs frequently and by tidying temporary file directories:

disable:

  Tuesday.Hr00::

   #
   # Disabling these log files weekly prevents them from
   # growing so enormous that they fill the disk!
   #

   /local/iu/httpd/logs/access_log   rotate=2
   /local/iu/httpd/logs/agent_log    rotate=2
   /local/iu/httpd/logs/error_log    rotate=2
   /local/iu/httpd/logs/referer_log  rotate=2

  FTPserver.Sunday::

   /local/iu/logs/xferlog rotate=3

tidy:

    /tmp pattern=* age=1

Process garbage collection is just as important. There are lot's of reasons why process tables fill up with unterminated processes. One example is faulty X terminal software which does not kill its children at logout. Another is that programs like netscape and pine tend to go into loops from which they never return, gradually loading the system with an ever increasing glacial burden. Just killing old processes can cause your system to spring back from its ice age blues (hopefully without littering the system with too many dead mammoths or bronze age axe-bearers). If the host concerned has important duties then this lack of responsiveness can compromise key services. It also gives local users a way of carrying out denial of service attacks on the system.

If users always log out at the end of the day and log in again the day after then this is easy to address with cfengine. Here is some code to kill commonly hanging processes. Note that on BSD like systems process options "aux" are required to see the relevant processes:

processes:

  linux|freebsd|sun4::

      SetOptionString "aux"

  any::

  "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"  

      signal=kill 

      include=ftpd
      include=tcsh 
      include=xterm  
      include=netscape
      include=ftp
      include=pine
      include=perl
      include=irc
      include=java
      include=/bin/ls
      include=emacs
      include=passwd

This pattern works like this: as processes become more than a day old they name of the month appears in the date of the process start time. These are matched by the regular expression. The include lines then filter the list of the processes further picking out lines which include the specified strings. On some BSD-like systems the default ps option string is "-ax" and you might need to reset it to something which adds the start date in order to make this work. Another job for process management is to clean up processes which have hung, gone amok or which are left over from old logins. Here is a regular expression which detects non-root processes which have clocked up more than 100 hours of CPU time. This is a depressingly common phenomenon when a program goes into an infinite loop. It can starve other processes of resources in a very efficient denial of service attack.
 any::

  #
  # Kill processes which have run on for too long e.g. 999:99 cpu time
  # Careful a pattern to match 99:99 will kill everything!
  #

  "[0-9][0-9][0-9][0-9]:[0-9][0-9]" signal=term exclude=root
       "[0-9][0-9][0-9]:[0-9][0-9]" signal=term exclude=root     
Under NT this is not so simple, since the process table for the cygwin library applies only to processes which have been started by programs working under the Unix process emulation. Hopefully this short-coming can be worked around at some point in the future.

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 signture of a trusted version of the program. If for some reason it should be 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 adminstrator 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"
  }

  { /etc/group

  AppendIfNoSuchLine "ftp::$(gid):"
  }



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 limited 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, i.e. CGI programs which you did not write yourself since CGI programs can circumvent any server security. This is because of 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 this: 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 which you did not write yourself, any CGI program has automatically normal file permission access to any file which 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 administrate 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 ID's 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 documnts should be owned by a user and group which is *not* the same as the UID/GID the daemon runs with, otherwise CGI programs and server-side emebellishments 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 modifiy their contents.

 files:

 wwwprivate::

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

 wwwpublic::

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


Pitfalls

Cfengine's ability to run your network depends 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 distaster. The solution is to use a separate, simple script which only updates the configuration 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 which mails the output of cfengine to someone more useful than root (the owner of the cron job). cfnormal is then a small script which 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 file system, e.g. /etc:
#!/bin/sh
# cfnormal

CFINPUTS /etc/cfengine/inputs  ; export CFINPUTS
/etc/cfengine/bin/cfengine -f cf.update 
and cfupdate is a cfengine program which makes sure that the configuration file is up to date
#
# 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 other systems which use network protocols in every operation.

If you use cfengine on hosts which have mounted NFS filesystems, 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 filesystems during recursive operations, but if you make filesystems setuid root this can fail.

Always remember that processes which are started by a script or by cfengine inherit the environment variables which the parent script has, including the 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 which are not owned by the uid running the program, or which are writable 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 a NIS replacement or other password distributor). Cfengine uses the triple DES implemenation 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 non-spoofable way of confirming identity is to use a shared secret --- i.e. a password. A password works by demanding that two parties who want to trust one another must both know a piece of information which untrusted parties do not.

Following the second world war, the now famous pair, Julius and Ethel Rosenberg were convicted and executed for spying on the U.S. bomb project for the Soviet Union in 1953. At one point they improvised a clever password system: a cardboard Jell-O box was torn in two and one half given to a contact whom they later would need to identify. The complex edge shape and colour matching made a complex key quite impossible to forge. Our bodies use a similar method of receptor identification of molecules for immune responses as well as for smell (with some subtleties). 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 are 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 conditons.

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

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 second example, you can also restrict access to certain files to secure lines, i.e. 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 which are locked. Locking prevents contention from competing processes and it 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 sub-processes.

Spoofing

Spoofing refers to attempts to masquerade as another host when sending network transmissions. The cfd program which 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 who 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 conformation 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 during the operation 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 filesystem (prefix .cfnew) and then renaming it into place, only if the transfer was successful. This ensures that there is space on the filesystem 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 checking 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 shellcommands

There are dangers in starting scripts from programs which 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 one 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 supposed to be there. You can control file permissions, processes and a dozen other things which 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. In this article it has only been possible to scratch the surface of what cfengine can do. To fully understand the syntax of the examples here you should read the documentation for cfengine.

The big advantage of cfengine 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 and 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. Cfengine 1.5.0 is available for most kinds of Unix and for NT (with the cygwin-32 package). It is easily portable to other platforms.