This is cfengine-Tutorial.info, produced by makeinfo version 4.0 from
cfengine-Tutorial.texinfo.

INFO-DIR-SECTION System Utilities
START-INFO-DIR-ENTRY
* cfengine Tutorial: (cfengine-Tutorial.info).
                        Cfengine is a language based tool specifically
                        designed for configuring and maintaining BSD
                        and System-5-like operating systems attached
                        to a TCP/IP network.
END-INFO-DIR-ENTRY


File: cfengine-Tutorial.info,  Node: NFS filesystem resources,  Next: Unique filesystem mountpoints,  Prev: NFS resources,  Up: NFS resources

NFS filesystem resources
------------------------

   Using the Network File System (NFS) in a large workstation
environment requires a bit of planning.  The idea of NFS is to share
files on one host with other hosts.  In most cases, filesystems to be
shared across the network fall into two categories: _binary_
filesystems (those which contain compiled software) and _user_ or _home_
filesystems (which contain users' login areas).

   The most simple minded way to share resources would be to mount every
resource (each available NFS filesystem) onto every host.  To avoid
collisions, each filesystem would have to have a unique name.  This is
one possibility, but not a very intelligent one.  As experienced users
will realize, cross-mounting too many NFS filesystems is a recipe for
all kinds of trouble.

   Cfengine offers a simple model which can help you pick out only the
resources you need from the list of NFS filesystems.  It will then mount
them automatically and edit the appropriate filesystem tables.  It does
this by defining classes of hosts.  For instance -- you really don't
need to mount a binary filesystem for an `ultrix' system onto an `HPUX'
system.  There would be no point -- binary resources are _architecture_
or _hard-class dependent_.  But home directories are architecture
independent.

   Cfengine lets you to define a list of allowed servers for various
hosts so that only filesystems from the servers will be considered for
mounting!


File: cfengine-Tutorial.info,  Node: Unique filesystem mountpoints,  Next: How does it work?,  Prev: NFS filesystem resources,  Up: NFS resources

Unique filesystem mountpoints
-----------------------------

   The first step towards treating NFS filesystems as network resources
is to invent a naming scheme so that every filesystem has a unique name
on which it can be mounted.  If we don't sort this out now, we could
find two or more hosts with a filesystem called `/usr/local', both of
which we might like to mount since they contain different software.

   A simple but extremely useful naming scheme is the following.  (1)
If you don't like this scheme you can invent your own, but the
remainder of the text will encourage you to use this one.  If you
follow this scheme, exactly as described here, you will never have any
problems with mount points.  We shall describe the scheme in detail
below. Here are some points to digest:

   * When mounting a remote filesystem on your local system, the local
     and remote directories should always have exactly the same name.

   * The name of every filesystem mountpoint should be unique and tell
     you something meaningful about where it is located and what its
     function is.

   * You can always make links from special unique names to more general
     names like `/usr/local'. If you this involves compiled software and
     you do this on one host, you should do it on others which are of
     the same type.

   * It doesn't matter whether software compiles in the path names of
     special directories into software as long as you follow the points
     above.


Each filesystem is given a directory name composed of three parts:


     /site/host/contents

The first directory (which only exists to create a suitable mountpoint)
is the name of your local site.  If you are a physics department at a
university (with a separate setup) you could call this `physics'.  It
could be your company name or whatever.  The second piece is the name of
the host to which the disk space is physically attached.  The final
piece is the name of the filesystem.  Here are some typical examples:

     /physics/einstein/local    # /usr/local for einstein@physics
     /physics/newton/u1         # user partition 1 for newton@physics

On the machines which are home to the `local' partition, it is better to
make a link to `/usr/local' than call the filesystem `/usr/local'
directly.  This is because it makes the procedure of organizing the
entire network much clearer.

   It is worth noting that, when you ask cfengine to mount such a
resource, it will automatically make the mount directory and can easily
be asked to make a link to `/usr/local', so this small amount of extra
work is really no work at all.

   The whole naming convention is compactly summarized by defining a
mount point variable, `mountpattern'.  With the present scheme, this can
be defined as

     mountpattern = ( /$(site)/$(host) )

so that it evaluates to the name of the host executing the file
regardless of who that may be.  This variable is used together with the
`homepattern' pattern variable, which is used to distinguish between
home directories and binary resources. (See `homepattern' in the
reference section).  You can think of this as being part of the naming
convention.  In this text, we use the convention `u1 u2 u3...' for home
disks.  You could equally well use `home1 home2...' etc.  As long as
the name is unique, it doesn't matter.

   The full list of named resources should now be listed in the
`mountables' list, which is simply a list of all the resources
available for mounting on the network.

   ---------- Footnotes ----------

   (1) This unique naming scheme was suggested to me originally by Knut
Borge at USIT of the University of Oslo.


File: cfengine-Tutorial.info,  Node: How does it work?,  Next: Special variables,  Prev: Unique filesystem mountpoints,  Up: NFS resources

How does it work?
-----------------

   Once you have defined your unique names, how does cfengine know what
to mount? The idea is now to define a list of servers for each class of
hosts.

   Suppose we make a `binserver' declaration:


     binservers:
     
       mygroup.sun4::
     
          einstein
          newton

This would tell cfengine that it should mount all binary resources from
hosts `einstein' or `newton' onto any host of type `sun4' in the group
`mygroup'.  Every filesystem which is listed in `mountables' and is not
a home directory will be mounted.

   Home directories and binary resources are kept separate
automatically by cfengine, because a home directory is one whose
contents-name matches the `homepattern' pattern variable.  *Note Unique
filesystem mountpoints::.

   A `homeserver' declaration:


     homeservers:
     
       mygroup::
     
          einstein
          newton
          schwinger
          feynman

would correspondingly mean mount all the home directory resources on the
hosts in the list on all hosts in the group `mygroup'.  Clearly it is
unnecessary to distinguish between the architecture platform types of
the actual servers for user directories.

   In each case, cfengine will mount filesystems, make the appropriate
directories for the mount point and edit the filesystem table.


File: cfengine-Tutorial.info,  Node: Special variables,  Next: Mount example,  Prev: How does it work?,  Up: NFS resources

Special variables
-----------------

   Once you have mounted a resource on a unique directory, you have
access to all of the relevant filesystems on your network -- but you
really wanted the `local' filesystem to be mounted on `/usr/local'.  All
you need do now is to make a link:


     links:
     
       any::
     
           /usr/local  -> /$(site)/$(binserver)/local

The meaning of this is that, on any host, the directory `/usr/local'
should be a link to the `nearest' binary server's `local' resource.
The `$(binserver)' variable can in principle expand to any binary
server in the list.  In practice, cfengine goes through the list in
order and picks the first filesystem resource which matches.

   Could this lead to a collision? Suppose we are on the host `einstein'
and we execute the above command.  The host `einstein' has a filesystem
`/physics/einstein/local' on its local disk -- it is in fact the binary
server for the network, so it certainly doesn't need to mount any NFS
filesystems.  But this is no problem because cfengine automatically
treats `$(host)' as the highest priority binary server for any host.
That means that if you have a local filesystem, it will always have
priority.

   In contrast, if the host `schwinger' ran the command above, it would
find no local filesystem called `/physics/schwinger/local', so it would
go along the list of defined binary servers, find `einstein' and try
again.  It will succeed in finding `einstein' _provided all the binary
servers were mounted before the link command is executed_.  This means
that you should structure the `actionsequence' so that all filesystems
are mounted before any links are made.

   With a little practice, the cfengine model can lead to an enormous
simplification of the issue of NFS-mountable resources.

   NOTE: cfengine does not try to export filesystems, only mount already
exported filesystems.  If you want to automate this procedure also, you
can use the `editfiles' facility to add entries to `/etc/exports' (see
`editfiles' in the Reference manual).  In practice this is very
difficult to do and perhaps not desirable.


File: cfengine-Tutorial.info,  Node: Mount example,  Prev: Special variables,  Up: NFS resources

Example programs for mounting resources
---------------------------------------

   Let's write a very simple configuration for a network with only one
server called hal, where all the hosts are of the same operating system
type.  In such an example we can avoid using classes altogether.


     control:
     
       site   = ( univ )
       domain = ( univ.edu )
     
       actionsequence =
          (
          mountall
          mountinfo
          addmounts
          mountall
          links
          )
     
     binservers:
     
        hal
     
     homeservers:
     
        hal
     
     mailserver:
     
        hal:/var/spool/mail
     
     mountables:
     
        hal:/univ/home1
        hal:/univ/home2
        hal:/univ/local
     
     links:
     
        /usr/local -> /univ/local

   In this example, we have only one type of host so the configuration
is the same for each of them: no class references are required.  If we
look through the action sequence we see that the program first mounts
all the filesystems which are already defined on each host.  It does
this to be sure that everything which is already set up to be mounted
is mounted.  Let's assume that there are no problems with this.

   The next thing that happens is that `mountinfo' builds a list of the
filesystems which each host has successfully mounted.  Then by calling
`addmounts' we ask cfengine to check whether the host is missing any
filesystems.  What happens is that cfengine first looks to see what
servers are defined for each host.  In this case all hosts on the
network have only one server: hal.  Hal is defined as a server for both
binary data and `home' data -- i.e.  users' home directories.  The list
`mountables' tells cfengine what filesystems are available over the
network for the server hal.  There are three filesystems which can be
mounted, called `/univ/home1', `/univ/home2' and `/univ/local'.
Cfengine checks to see whether each of these filesystems is mounted
and, if not, it builds the necessary directories, edits the necessary
files and mounts the filesystems.

   Finally we come to `links' in the action sequence.  This tells
cfengine to look at the defined links.  There is one link defined: a
link from `/usr/local' to the mounted filesystem `/univ/local'.
Cfengine checks and tries to make the link if necessary.  If all goes
well, each host on the network should now have at least three
filesystems mounted and a link from `/usr/local' to `/univ/local'.

   Here is another simple example program for checking and automatically
mounting an NFS based `/usr/local' and all home directories onto all
hosts on a small network.  Here we have several servers and must
therefore use some classes.

     #
     #  Mounts
     #
     
     
     control:
     
        site      = ( mysite )
        domain    = ( mysite.country )
        sysadm    = ( mark )
        netmask   = ( 255.255.255.0 )
     
        actionsequence =
           (
           mountall
           mountinfo
           addmounts
           mountall
           links
           )
     
        mountpattern = ( /$(site)/$(host) )
        homepattern   = ( u? )                # u1 u2 u3 etc..
     
     groups:
     
        MyGroup =
           (
           host1
           host2
           binserver1
           binserver2
           )
     
     ######################################################################
     
     homeservers:
     
        MyGroup:: host1
     
     
     binservers:
     
        MyGroup.sun4::   server1
        MyGroup.ultrix:: server2
     
     mailserver:
     
        host1:/usr/spool/mail
     
     mountables:
     
        host1:/mysite/host1/u1
        host1:/mysite/host1/u2
        server1:/mysite/server1/local
        server2:/mysite/server2/local
     
     
     ##########################################################################
     
     links:
     
           /usr/local  -> /${site}/${binserver}/local

   Let's suppose we run this program on host2 which is an ultrix
machine.  This host belongs to the class `mygroup' and the hard-class
`ultrix'.  This tells us that its homeserver is host1, its binary
server is server2 and its mailserver is host1.  Moreover, since the
homepattern matches any filesystem ending in u-something, it recognizes
the two home directories in the mountables list -- and therefore the
two binary directories also.

   The action sequence starts by mounting all of the filesystems
currently in the filesystem table `/etc/fstab'.  It then scans the list
of mounted filesystems to find out what is actually mounted.  Since the
homeserver is host1, we know that our host has to mount all
home-filesystems from this server, so it checks for
`host1:/mysite/host1/u1' and `host1:/mysite/host1/u2'.  If they are not
present they are added to `/etc/fstab'(1).  Next, we know that the
binary server is server1, so we should check for
`server1:/mysite/server1/local'.  The mail server is also checked for
and added if necessary.  Cfengine then tries to mount all filesystems
once again, so that the new filesystems should be added.

   Note that, in the process of adding the filesystems to `/etc/fstab',
cfengine creates the directories up to and including the point at which
the filesystems should be mounted.  If something prevents this -- if we
try to mount on top of a plain file for instance -- then this will
result in an error.

   Finally, we reach the link section and we try to expand the
variables.  `$(site)' expands to `mysite'.  `$(binserver)' expands
first to the hostname (host2), but `/mysite/host2/local' does not
exist, so it then goes to the binserver list, which substitutes server1
for the value of `$(binserver)'.  Since `/mysite/server1/local' does
exist and is now mounted, cfengine makes a link to this directory from
`/usr/local'.  The script is then completed.

   If the script is run again, everything should now be in place so
nothing happens.  If for some reason it failed the first time, it will
fail again.  At any rate it will either do the job once and for all or
signal an error which must be corrected by human intervention(2).

   ---------- Footnotes ----------

   (1) Note: if the filesystem was in the fstab but not actually
mounted a warning is issued telling you that the filesystem was
probably not exported correctly on host1.

   (2) One possibility is that an NFS filesystem cannot be mounted
because the host serving the filesystem is out of service.  If this is
the case then a subsequent re-run when the server resumes normal
service will succeed.


File: cfengine-Tutorial.info,  Node: Using the automounter,  Next: Editing files,  Prev: NFS resources,  Up: Global configurations

Using the automounter
=====================

   The automounter is a daemon based service which replaces static
mounting of NFS filesystems with a dynamical model. When the
automounter is running, filesystems are mounted only when a user tries
to access a file which resides on one of those filesystem. After a
given period (usually five minutes) any filesystem which has not been
accessed is unmounted. The advantage of this scenario is that hanging
servers do not affect the behaviour of hosts which mount their
filesystems, unless a specific file is being accessed.  In both cases,
filesystems must be exported in order to be mountable.

   It is not the purpose of this section to explain the use of the
automounter in detail, only to offer hints as to how cfengine can be
used to simplify and rationalize automount configuration for the already
initiated.  Let us begin by comparing the behaviour of the automounter
with the cfengine model for mounted filesystems.

   The automounter is designed to be used together with a global
configuration file, distributed by NIS (the network information
service). As such, all hosts read the same configuration file. This
makes it appear as though all hosts end up mounting every filesystem in
the automount configuration database, but this is not so in practice
because filesystems are only mounted if required. Thus a system which
does not require a filesystem will not attempt to mount it.  Moreover,
the existence of a global configuration file does not affect which
hosts have the right to mount certain filesystems (which is specified
by exports or share on the relevant server), thus a request to mount a
non-exported filesystem will result in an access denial. The automounter
is configured locally on each host in files named `/etc/auto_master',
`auto_direct' etc.

   In the cfengine static mounting scheme, you define a list of binary
and home servers. The filesystem table is modified on the basis of
these decisions, and filesystems are only added if cfengine deems it
appropriate to mount them on a given host. The idea here is to minimize
the number of filesystems mounted to those which are known to be
required.  Again the issue of access permissions must be arranged
separately. These filesystems are placed directly in `/etc/fstab', or
the equivalent for your system.

   From cfengine, you can use the automounter instead of the static
mount model by

   * omitting `addmounts', `mountinfo', `mountall' from the
     actionsequence, in the control part of your cfengine program,

   * using `editfiles' to edit the relevant configuration files such as
     `/etc/auto_master', or `auto_direct' etc,

   * using the `AutomountDirectResources' command in editfiles to dump
     the list of cfengine class-based list of mountables into a file of
     your choice in the correct format for autmount's direct maps,

   * using `processes' to restart the automounter (send the hangup
     signal `hup'), or perhaps stop and restart the daemon by sending
     the `term' signal (you should never send the `kill' signal).

   * using the multiple link facilities to link in indirect mounted
     filesystems as required, and `files' or `tidy' to clean up stale
     links afterwards,

   * perhaps using `copy' to distribute basic automount configuration
     files to multiple systems.


   The automounter was created to solve certain problems which cfengine
now solves (in the author's opinion) better. For example, the use of the
`hosts' map in the automounter mounts filesystems like `/usr/local' on
different (uniquely named) mountpoints for each host in order to avoid
name space collisions. Using cfengine and a unique naming scheme, you
can achieve the same thing more cleanly, without all of the gratuitous
linking and unlinking which the automounter performs by itself.
Moreover, the idea of a unique name-space is better practice and more
in keeping with new global filesystem ideas such as AFS and DFS.  The
only advantage of the automounter is that one avoids the annoying error
messages from hung servers about "NFS server not responding".  In that
respect, it seems sensible to use only direct mounts and a unique name
space.

   Some systems advocate grouping all users' login (home) directories
under a common directory called `/home' or `users'.  The automounter
goes through all manner of contortions to achieve this task. If you use
a unique naming scheme like the one advocated here, this is a trivial
task. You simply arrange to mount or automount all user directories,
such as

        /SITE/HOST/home1
        /SITE/HOST/home2
        ...

and then link them as follows:


        /home +> /SITE/HOST/home1
        /home +> /SITE/HOST/home2
        ...

   Finally, you should be aware that the automounter does not like to
be mixed with static mount and unmount operations. Automounted
filesystems take priority over statically mounted filesystems, but the
automounter can be confused by manually mounting or unmounting
filesystems while it is running.


File: cfengine-Tutorial.info,  Node: Editing files,  Next: Disabling and the file repository,  Prev: Using the automounter,  Up: Global configurations

Editing Files
=============

   A very convenient characteristic of BSD/System 5 systems is that they
are configured primarily by human-readable textfiles. This makes it easy
for humans to configure the system and it also simplifies the automation
of the procedure.  Most configuration files are line-based text files, a
fact which explains the popularity of, for example, the Perl programming
language.  Cfengine does not attempt to compete with Perl or its peers.
Its internal editing functions operate at a higher level which are
designed for transparency rather than flexibility.  Fortunately most
editing operations involve appending a few lines to a file, commenting
out certain lines or deleting lines.

   For example, some administrators consider the finger service to be a
threat to security and want to disable it. This could be done as
follows.


     editfiles:
     
           { /etc/inetd.conf
     
           HashCommentLinesContaining "finger"
           }

   Commands containing the word `Comment' are used to `comment out'
certain lines from a text-file--i.e. render a line impotent without
actually deleting it. Three types of comment were supported originally:
shell style (hash) `#', `%' as used in TeX and on AIX systems, and
C++-style `//'.

   A more flexible way of commenting is also possible, using directives
which first define strings which signify the start of a comment and the
end of a comment. A single command can then be used to render a comment.
The default values of the comment-start string is `# ' and the default
comment-end string is the empty string.  For instance, to define C
style comments you could write:


       { FILE
     
       SetCommentStart "/* "
       SetCommentEnd   " */"
     
       # Comment out all lines containing printf!
     
       CommentLinesMatching ".*printf.*"
       }

   Other applications for these editing commands include monitoring and
controlling root-access to hosts by editing files such as `.rhosts' and
setting up standard environment variables in global shell resource
files-- for example, to set the timezone. You can use the editing
feature to update and distribute the message of the day file, or to
configure sendmail, (see FAQS and Tips in the Reference manual).

   An extremely powerful feature of cfengine is the ability to edit a
similar file belonging to every user in the system. For example, as a
system administrator, you sometimes need to ensure that users have a
sensible login environment. Changes in the system might require all
users to define a new environment variable, for instance. This is
achieved with the `home' pseudo-wildcard. If one writes


       { home/.cshrc
     
       AppendIfNoSuchLine "# Sys admin/cfengine: put next line here"
       AppendIfNoSuchLine "setenv PRINTER newprinter"
       }

then the users' files are checked one-by-one for the given lines of
text, and edited if necessary.

   Files are loaded into cfengine and edited in memory. They are only
saved again if modifications to the file are carried out, in which case
the old file is preserved by adding a suffix to the filename. When
files are edited, cfengine generates a warning for the administrator's
inspection so that the reason for the change can be investigated.

   The behaviour of cfengine should not be confused with that of _sed_
or _perl_.  Some functionality is reproduced for convenience, but the
specific functions have been chosen on the basis of (i) their
readability and (ii) the fact that they are
`frequently-required-functions'. A typical file editing session involves
the following points:

   * Load file into memory.

   * Is the size of the file within sensible user-definable limits?  If
     not, file could be binary, refuse to edit.

   * Check each editing command and count the number of edits made.

   * If number of edits is greater than zero, rename the old file and
     save the edited version in its place. Inform about the edit.

   * If no edits are made, do nothing, say nothing.

   Equivalent one-line sed operations involve editing the same file
perhaps many times to achieve the same results--without the safety
checks in addition.


File: cfengine-Tutorial.info,  Node: Disabling and the file repository,  Next: Running user scripts,  Prev: Editing files,  Up: Global configurations

Disabling and the file repository
=================================

   The existence of certain files can compromise the integrity of your
system and you may wish to ensure that they do not exist. For example,
some manufacturers sell their workstations with a `+' symbol in the
file `/etc/hosts.equiv'.  This means that anyone in your NIS domain has
password free access to the system!! Since this is probably not a good
idea, you will want to disable this file by renaming it, or simply
deleting it.


       disable:
     
          /etc/hosts.equiv

Other files compromise the system because they grow so large that they
fill an entire disk partition. This is typically true of log files such
as the system 5 files `/var/adm/wtmpx' and `/var/lp/logs/lpsched'.
Other files like /VAR/ADM/MESSAGES get "rotated" by the system so that
they do not grow so large as to fill the disk. You can make cfengine
rotate these files too, by writing


     disable:
     
         Sunday::
     
         /var/lp/logs/lpsched  rotate=3

Now, when cfengine is run, it renamed the file `lpsched' to a file
called `lpsched.1'. It also renames `lpsched.1' as `lpsched.2' and so
on, until a maximum of 3 files are kept. After passing 3, the files
`fall off the end' and are deleted permanently. This procedure prevents
any log files from growing too large. If you are not interested in
keeping back-logs, then you may write `rotate=empty' and cfengine will
simply empty the log file.

   When ever cfengine disables a file (`disable' or `links' with the
`!'  operator), or saves a new file on top of an old one (`copy' or
`editfiles'), it makes a backup of the original. Usually disabled files
are renamed by appending the string `.cfdisabled' the filename; copied
files are saved by appending the string `.cfsaved'.  It is possible to
switch off backup file generation in the copy feature by setting the
variable `backup=false', but a better way of managing disabled and
backed-up files is to use a directory in which you collect all such
files for the whole system. This directory is called the file
repository and is set in the control part of the program, as follows:


       control:
     
          repository = ( DIRECTORY-NAME )

   If this variable is defined, cfengine collects all backup and
disabled files (except for rotated files) in this directory, using a
unique pathname.  You can then inspect these files in the repository
and arrange to tidy the repository for old files which are no longer
interesting.


File: cfengine-Tutorial.info,  Node: Running user scripts,  Next: Compressing logs,  Prev: Disabling and the file repository,  Up: Global configurations

Running user scripts
====================

   Above all, the aim of cfengine is to present a simple interface to
system administrators. The actions which are built into the engine are
aimed at solving the most pressing problems, not at solving every
problem.  In many cases administrators will still need to write scripts
to carry out more specific tasks. These scripts can still be profitably
run from cfengine. Variables and macros defined in cfengine can be
passed to scripts so that scripts can make maximal advantage of the
class based decisions. Also note that, since the days of the week are
also classes in cfengine, it is straightforward to run weekly scripts
from the cfengine environment (assuming that the configuration program
is executed daily). An obvious use for this is to update databases,
like the fast-find database one day of the week, or to run quota checks
on disks.

     shellcommands:
     
        myhost.Sunday::
     
           "/usr/bin/find/updatedb"

Cfengine scripts can be passed variables using normal variable
substitution:

     control:
     
        cfbin     = ( /local/gnu/lib/cfengine/bin )
        backupdir = ( /iu/dax/backup )
     
     shellcommands:
     
       "$(cfbin)/cfbackup -p -f $(backupdir) -s /iu/nexus/u1"

   If you need to write a particularly complex script to expand
cfengine's capabilities, it might be useful to have full access to the
defined classes. You can do this in one of two ways:

   * Pass the variable `$(allclasses)' to the script. This contains a
     list of all classes in the form of a string


                CFALLCLASSES=CLASS1:CLASS2:...

     This variable always contains an up to date list of the defined
     classes.

   * Use the command line option `-u' or `--use-env'. When this is
     defined, cfengine defines an internal environment variable called
     `CFALLCLASSES' which contains the same list as above.
     Unfortunately, system 5 boxes don't seem to like having to update
     an environment variable continuously and tend to dump core, so
     this is not the default behaviour!



File: cfengine-Tutorial.info,  Node: Compressing logs,  Next: ACLs,  Prev: Running user scripts,  Up: Global configurations

Compressing old log files
=========================

   In the previous two sections we have looked at how to rotate old log
files and how to execute shell commands. If you keep a lot of old log
files around on your system, you might want to compress them so that
they don't take up so much space. You can do this with a shell command.
The example below looks for files matching a shell wildcard. Names of
the form `file.1', `file.2'...`file.10' will match this wildcard and
the compression program sees that they get compressed. The output is
dumped to avoid spurious messages.


     shellcommands:
     
       "$(gnu)/gzip /var/log/*.[0-9] /var/log/*.[0-9][0-9]  > /dev/null 2>&1"

   Cfengine will also recognize rotated files if they have been
compressed, with suffixes `.Z', `.gz', `.rbz' or `.rbz'.


File: cfengine-Tutorial.info,  Node: ACLs,  Prev: Compressing logs,  Up: Global configurations

Managing ACLs
=============

   Access control lists are extended file permissions. They allow you to
open or close a file to a named list of users (without having to create
a special group for those users).  They also allow you to open or close
a file for a named list of groups.  Several unix-like operating systems
have had access control lists for some time; but they do not seem to
have caught on.

   There is a number of reasons for this dawdling in the past.  The
tools for setting ACLs are generally interactive and awkward to use.
Because a named list of users would lead to excessive verbosity in an
`ls -l' listing, one does not normally see them. There is therefore the
danger that the hidden information would lead to undetected blunders in
opening files to the wrong users.  ACLs are also different on every
vendor's filesystems and they don't work over intersystem NFS.  In
spite of these reservations, ACLs are a great idea. Here at Oslo
College, it seems that users are continually asking how they can open a
file just for the one or two persons they wish to collaborate with.
They have grown used to Novell/PC networks which embraced the
technology from Apollo/NCS much earlier. Previously the Unix answer to
users has always been: go ask the system administrator to make a
special group for you.  Then do the `chmod' thing. And then they would
say: so what's so great about this Unix then?

   Addressing this lack of standardization has been the job of a POSIX
draft committee. Some vendors have made their implementations in the
image of this draft. Solaris 2.6 has a good implementation.  In spite
of this, even these systems have only awkard tools for manipulating
ACLs. Not the kind of thing you want to be around much, if you have
better things to do.  But the incompatibility argument applies only to
multiple vendor headbutting. Some institutions who share data on a
global basis opt for advanced solutions to network filesystems, such as
AFS and DFS.  Filesystems such as DCE's DFS make extensive use of file
ACLs, and they are not operating system specific.  Even so, DFS
provides only interactive tools for examining and setting file
permissions, and this is of little use to system administrators who
would rather relegate that sort of thing to a script.

   The need for this kind of thing is clear. Systems which make use of
ACLs for security can be brought to their knees by changing a few ACLs.
Take the Apollo/Domain OS as an example. All one needs to do to kill the
system is to change a few ACLs and forget what they were supposed to
be. Suddenly the system is crippled, nothing works. The only solution,
if you don't have a backup, is to remove all of the security. Unix has a
simpler security philosophy when it comes to the operating system files,
but ACLs would be a valuable addition to the security of our data.

   A cfengine bare-bones file-checking program looks like this:

     #
     # Free format cfengine program
     #
     
      control:
     
        ActionSequence - ( files )
     
      files:
     
        classes::
     
          /directory/file  mode=644
                           owner=mark,ds
                           group=users,adm
                           acl=zap
                           action=fixplain
     
       # ... more below

This program simply checks the permissions and ownership of the named
file. The regular file mode, owner and group are specified
straightforwardly.  The new feature here is the `acl' directive. It is
a deceptively simply looking animal, but it hides a wealth of
complexity.  The `zap' is, of course, not an access control list.
Rather, cfengine uses a system of aliases to refer to ACLs, so that the
clutter of the complex ACL definitions does not impair the clarity of a
file command.  An ACL alias is defined in a separate part of the
program which looks like this:

      # ...contd
     
     
      acl:
     
        { zap
     
        method:append
        fstype:solaris
        user:rmz:rwx
        user:len:r
        }

As you can see, an ACL is a compound object--a bundle of information
which specifies which users have which permissions.  Because ACLs are
_lists_ the alias objects must also know whether the items are to be
appended to an existing list or whether they are to replace an existing
list. Also, since the permission bits, general options and programming
interfaces are all different for each type of filesystem, we have to
tell cfengine what the filesystem type is.

   It is possible to associate several ACL aliases with a file.  When
cfengine checks a files with ACLs, it reads the existing ACL and
compares it to the new one. Files are only modified if they do not
conform to the specification  in the cfengine program. Let's look at a
complete example:

      files:
     
        $(HOME)/myfile acl=acl_alias1 action=fixall
     
      acl:
     
        { acl_alias1
     
        method:append
        fstype:solaris
        user:len:rwx
        }

ACLs are viewed in Solaris with the command `getfacl'.  Suppose that,
before running this program, our test-file had permissions

        user:*:rwx
        user:mark:rwx           #effective:r-x
        group:*:r-x              #effective:r-x
        mask:r-x
        other:r-x
        default_user:rw-
        default_group:r--
        default_mask:-w-
        default_other:rwx

After the cfengine run, the ACL would become:

        user:*:rwx
        user:mark:rwx           #effective:r-x
        user:len:rwx            #effective:r-x
        group:*:r-x              #effective:r-x
        mask:r-x
        other:r-x
        default_user:rw-
        default_group:r--
        default_mask:-w-
        default_other:rwx

Suppose we wanted to to remove 'w' bit for user `jacobs', or make sure
that it was never there.

     	{ acl_alias1
     
     	method:append
     	fstype:solaris
     	user:jacobs:-w
     	}

Note that the method used here is append. That means that, whatever
other access permissions we might have granted on this file, the user
`jacobs' (a known cracker) will have no write permissions on the file.
Had we used the method `overwrite' above, we would have eliminated all
other access permissions for every user and added the above.  If we
really wanted to burn `jacobs', we could remove all rights to the file
like this


       user:jacobs:noaccess

The keyword `noaccess' removes all bits. Note that this is not
necessarily the same as doing a `-rwx', since some filesystems, like
DFS, have more bits than this. Then, if we want to forgive and forget,
the ACLs may be removed for `jacobs' with the syntax

       user:jacobs:default

   In Solaris, files inherit default ACLs from the directory they lie
in; these are modified by the umask setting to generate their own
default mask.

   DFS ACLs look a little different. They are examined with the
`acl_edit' command or with

     dcecp -c acl show <filename>

In order to effect changes to the DFS, you have to perform a DCE login
to obtain authentication cookies. The user `cell_admin' is a special
user account for administrating a local DFS cell.  Suppose we have a
file with the following DCE ACL:

       mask_obj:r-x---
       user_obj:rwxcid
       user:cell_admin:r--c-- #effective:r-----
       group_obj:r-x--d       #effective:r-x---
       other_obj:r-x---

Now we want to add `wx' permissions for user `cell_admin', and add new
entries with `rx' permissons for group `acct-admin' and user `root'.
This is done with the following ACL alias:


        { acl_alias2
     
        method:append
        fstype:dfs
        user:/.../iu.hioslo.no/cell_admin:wx
        group:/.../iu.hioslo.no/acct-admin:rx
        user:/.../iu.hioslo.no/root:rx
        user:*:-x
        }

The local cell name `/.../iu.hioslo.no' is required here. Cfengine can
not presently change ACLs in other cells remotely, but if your cfengine
program covers all of the cell servers, then this is no limitation,
since you can still centralize all your ACLs in one place. It is just
that the execution and checking takes place at distributed locations.
This is the beauty of cfengine.  After running cfengine, with the above
program snippet, the ACL then becomes:

       mask_obj:r-x---
       user_obj:rwcid
       user:cell_admin:rwxc-- #effective:r-x---
       user:root:r-x---       #effective:r-x---
       group_obj:r-x--d       #effective:r-x---
       group:acct-admin:r-x---
       other_bj:r-x---

For the sake of simplicity we have only used standard Unix bits `rwx'
here, but more complicated examples may be found in DFS. For example,


      user:mark:+rwx,-cid

which sets the read, write, execute flags, but removes the control,
insert and delete flags. In the DFS, files inherit the inital object
ACL of their parent directory, while new directories inherit the
initial container object.

   The objects referred to in DFS as `user_obj', `group_obj' and so
forth refer to the owner of a file. i.e. they are equivalent to the
same commands acting on the user who owns the file concerned.  To make
the cfengine user-interface less cryptic and more in tune with the
POSIX form, we have dropped the `_obj' suffices. A user field of `*' is
a simple abbreviation for the owner of the file.

   A problem with any system of lists is that one can generate a
sequence which does one thing, and then undoes it and redoes something
else, all in the same contradictory list. To avoid this kind of
accidental interaction, cfengine insists that each user has only one ACE
(access control entry), i.e. that all the permissions for a given user
be in one entry.


File: cfengine-Tutorial.info,  Node: Using cfengine as a front-end for cron,  Next: Cfengine network services,  Prev: Global configurations,  Up: Top

Using cfengine as a front end for `cron'
****************************************

   One of cfengine's strengths is its use of classes to identify systems
from a single file or set of files. Many administrators think that it
would be nice if the cron daemon also worked in this way. One possible
way of setting up cron from a global configuration would be to use the
cfengine `editfiles' facility to edit each cron file separately. A much
better way is to use cfengine's time classes to work like a user
interface for cron.  This allows you to have a single, central cfengine
file which contains all the cron jobs on your system without losing any
of the fine control which cron affords you. All of the usual advantages
apply:
   * It is easier to keep track of what cron jobs are running on the
     system when you have everything in one place.

   * You can use all of your carefully crafted groups and user-defined
     classes to identify which host should run which programs.

   The central idea behind this scheme is to set up a regular cron job
on every system which executes cfengine at frequent intervals.  Each
time cfengine is started, it evaluates time classes and executes the
shell commands defined in its configuration file.  In this way we use
cfengine as a wrapper for the cron scripts, so that we can use
cfengine's classes to control jobs for mulitple hosts. Cfengine's time
classes are at least as powerful as `cron''s time specification
possibilities, so this does not restrict you in any way, *Note Building
flexible time classes::. The only price is the overhead of parsing the
cfengine configuration file.

   To be more concrete, imagine installing the following `crontab' file
onto every host on your network:

     #
     # Global Cron file
     #
     0,15,30,45 * * * * /usr/local/cfengine/inputs/run-cfengine

This file contains just a single cron job, namely a script which calls
cfengine.  Here we are assuming that you will not want to execute any
cron script more often than every fifteen minutes. If this is too
restrictive, the above can be changed. We refer to the time interval
between runs of the script `run-cfengine' as the `scheduling interval'
and discuss its implications in more detail below.

   The script `run-cfengine' would replace any `cfdaily' or `cfhourly'
scripts which you might have, and can as simple as this

     #!/bin/sh
     #
     # Script run-cfengine
     
     export CFINPUTS=/usr/local/cfengine/inputs
     
     /usr/local/gnu/bin/cfengine
     
     #
     # Should we pipe mail to a special user?
     #

or it could be more fancy. You could also use the `cfwrap' script, if
you have perl on all your systems, to pipe mail to the mail address
described in the cfengine file. (See also the variable `sysadm' in the
Reference manual).

     #
     # Global Cron file
     #
     0,15,30,45 * * * * PATH/cfwrap PATH/run-cfengine

You might not want to run your entire system configuration
`cfengine.conf' every time cron fires up cfengine. An alternative would
be to keep a separate fil for cron jobs called, say, `cf.cron'. You
would then replace the `run-cfengine' file by

     #!/bin/sh
     #
     # Script run-cfengine
     
     export CFINPUTS=/usr/local/cfengine/inputs
     
     /usr/local/gnu/bin/cfengine -f cf.cron
     
     #
     # Should we pipe mail to a special user?
     #

There is no particular advantage to doing this unless you are running
cfengine on some very slow hardware. A better way to approach the
problem is to think of the `cf.cron' file as a module which can be
imported into the main configuration file. This gives you the maximum
amount of flexibilty, since it allows you to decide exactly what you
want to happen any any given time from the central file.

* Menu:

* Structuring cfengine.conf::
* Splaying host times::
* Building flexible time classes::
* Scheduling interval::


File: cfengine-Tutorial.info,  Node: Structuring cfengine.conf,  Next: Splaying host times,  Prev: Using cfengine as a front-end for cron,  Up: Using cfengine as a front-end for cron

Structuring `cfengine.conf'
===========================

   The structure of `cfengine.conf' needs to reflect your policy for
running jobs on the system. You need to switch on relevant tasks and
switch off unwanted tasks depending on the time of day. This can be
done in three ways:

   * By placing individual actions under classes which restrict the
     times at which they are executed,


            ACTION:
          
                  Hr00.Min10_15||Hr12.Min45_55::
          
                     COMMAND

   * By choosing a different `actionsequence' depending on the time of
     day.


            control:
          
                  Hr00::   # Action-sequence for daily run at midnight
          
                     actionsequence = ( SEQUENCE )
          
                 !Hr00::   # Action-sequence otherwise
          
                      actionsequence = ( SEQUENCE )

   * By importing modules based on time classes.


            import:
          
                  Hr00:: cf.dailyjobs
          
                  any::  cf.hourlyjobs


The last of these is the most efficient of the three, since cfengine
does not even have to spend time parsing the files for actions which
you know you will not want.


File: cfengine-Tutorial.info,  Node: Splaying host times,  Next: Building flexible time classes,  Prev: Structuring cfengine.conf,  Up: Using cfengine as a front-end for cron

Splaying host times
===================

   The trouble with starting every cfengine at the same time using a
global cron file is that it might lead to contention or inefficiency.
For instance, if a hundred cfengines all suddenly wanted to copy a file
from a master source simultaneously this would lead to a big load on the
server. We can prevent this from happening by introducing a time delay
which is unique for each host and not longer than some given interval.
Cfengine uses a hashing algorithm to generate a number between zero and
a maximum value in minutes which you define, like this:


      control:
     
         SplayTime = ( 60 ) # minutes

If this number is non-zero, cfengine goes to sleep after parsing its
configuration file and reading the clock. Every machine will go to sleep
for a different length of time, which is no longer than the time you
specify in minutes. A hashing algorithm, based on the fully qualified
name of the host, is used to compute a unique time for hosts. The
shorter the interval, the more clustered the hosts will be. The longer
the interval, the lighter the load on your servers.  This `splaying' of
the run times will lighten the load on servers, even if they come from
domains not under your control but have a similar cron policy.

   Splaying can be switched off temporarily with the `-q' or
`--no-splay' options.

