a public resource for all things web hosting, systems administration, and dedicated server management.

Editing configuration files with cfengine

Now we've got a fairly solid template for checking a service, we want to be able to configure it. This time Jamie Wilkinson takes a look at cfengine's editfiles section and describes a neat way to cope with migrating existing managed servers with some slightly different configurations to the state we want them in.

After having used cfengine at a previous job, one of the first tasks I set to at Anchor was automating our new server builds by tying together Red Hat's kickstart and cfengine. We had a long list of configuration steps documented, which was a good starting point; the majority of tasks required were simple things such as ensuring a service was enabled and a certain configuration option was set in a particular way.

It was shortly after that when I then tried to migrate the existing servers into this new automated adminisration infrastructure that I discovered the simple cfengine inputs I had created were not sufficient to ensure confidence in convergence to the corresponding correct configuration.

For example, the operation "setting option X in file Y to the value Z", a common idiom when configuring daemons, took the form of an editfiles script:

  { Y
    LocateLineMatching '^X =.*'
    ReplaceLineWith 'X = Z'
  }

but this doesn't cope when option X doesn't already exist in

  { Y
    AppendIfNoSuchLine 'X = Z'
  }

may work well for new systems that don't have X already specified in file Y, but breaks when we are migrating from a system that does have X set by

if the value of X is to change for some reason (say you want all new machines to use SSH protocol version 2 only, but leave the existing ones with both protocol 2 and 1) then we need to add a lot of editfiles

Remember: The problem I am trying to solve is the

It's true we could fill our cfengine inputs with lots of commands to delete lines which we recognise are deprecated, but that's a lot of overhead, especially when the value of X changes from Q to R to S to Z:

  { Y
    DeleteLinesMatching 'X = Q'
    DeleteLinesMatching 'X = R'
    DeleteLinesMatching 'X = S'
    AppendIfNoSuchLine 'X = Z'
  }

Without comments, this is meaningless to everyone, and within a week and a few beers it's meaningless to the author too. Even with comments, old deprecated configurations just seem to lie around and, without additional systems to check that those configurations are gone, one can never be sure that all the systems are up to date (for one reason or another, systems always seem to be switched off, or have cfengine disabled (but that's another story)).

editfiles scripts are quite tedious and cumbersome, and while they are relatively easy to read, their readability will decrease as the size of the script increases; and the less a human can understand in one reading, the more likely there's an error in the script. The solution then is to come up with a simple template for editing a configuration file which adds the key if necessary and then sets it to the correct value if it is not:

  { Y
    BeginGroupIfNoLineMatching '^X =.*'
      Append 'X ='
    EndGroup
    LocateLineMatching '^X =.*'
    ReplaceLineWith 'X = Z'
  }

That will make sure a key X exists in file Y, and then once we've ensured

The key difference between these two approaches is that the simple approach

are presented to cfengine. The second will cope with all forms of that configuration.

An advantage of this method is that we can now define a cfengine

control:

  Y_X = ( Z )

editfiles:

  { Y
    BeginGroupIfNoLineMatching '^X =.*'
      Append 'X ='
    EndGroup
    LocateLineMatching '^X =.*'
    ReplaceLineWith 'X = $(Y_X)'
  }

Wow. That means we only have to set a single cfengine variable in

control:

  class1::

    Y_X = ( Z )

  class2::

    Y_X = ( R )

editfiles:

  { Y
    BeginGroupIfNoLineMatching '^X =.*'
      Append 'X ='
    EndGroup
    LocateLineMatching '^X =.*'
    ReplaceLineWith 'X = $(Y_X)'
  }

That's probably enough abstract examples for now. I'll finish off now with the classes, control, and editfiles sections from ssh.cf.

classes:

  sshd_server = ( any )

  # use ssh protocol 2 only
  ssh_v2_only = ( anchor_secure )

control:

  ssh_v2_only::

    ssh_protocol = ( 2 )

  !ssh_v2_only::

    ssh_protocol = ( 2,1 )

editfiles:

  sshd_server::

    # fiddle with sshd settings
    { /etc/ssh/sshd_config
      Backup 'off'

      BeginGroupIfNoLineMatching '^X11Forwarding.*'
        Append 'X11Forwarding'
      EndGroup
      ResetSearch 1
      LocateLineMatching '^X11Forwarding.*'
      BeginGroupIfNoMatch '^X11Forwarding no$'
        ReplaceLineWith 'X11Forwarding no'
      EndGroup
      ResetSearch 1

      BeginGroupIfNoLineMatching '^Protocol.*'
          Append 'Protocol'
      EndGroup
      ResetSearch 1
      LocateLineMatching '^Protocol.*'
      BeginGroupIfNoMatch '^Protocol ${ssh_protocol}$'
          ReplaceLineWith 'Protocol ${ssh_protocol}'
      EndGroup
      ResetSearch 1

      BeginGroupIfNoLineMatching '^SyslogFacility.*'
        Append 'SyslogFacility'
      EndGroup
      ResetSearch 1
      LocateLineMatching '^SyslogFacility.*'
      BeginGroupIfNoMatch '^SyslogFacility AUTHPRIV$'
        ReplaceLineWith 'SyslogFacility AUTHPRIV'
      EndGroup
      ResetSearch 1

      BeginGroupIfNoLineMatching '^LogLevel.*'
        Append 'LogLevel'
      EndGroup
      ResetSearch 1
      LocateLineMatching '^LogLevel.*'
      BeginGroupIfNoMatch '^LogLevel INFO$'
        ReplaceLineWith 'LogLevel INFO'
      EndGroup
      ResetSearch 1

      DefineClasses 'sshd_restart'
    }

Note the use of the anchor_secure class for the machines that should be configured with SSH protocol v2 only. We also define a class sshd_restart to restart the SSH service after the configuration file has been changed. And to finish off, add the editfiles section to main.cf:

    actionsequence = (
        files
        editfiles
        processes
        shellcommands
    )

Author : Jamie Wilkinson

Related links


More articles : Web hosting support, dedicated server administration and useful hosting tools