Writing your first policy – the essentials

To define new Desired States in CFEngine, you need to write policy files. These are plain text-files with .cf extension.

/var/cfengine/inputs and promises.cf

In CFEngine,  cf-agent executes all policies. cf-agent runs every 5 minutes by default, and it executes policies found locally in the /var/cfengine/inputs directory. The main policy is a file called promises.cf. In this file you normally reference bundles and other policy-files.

The concept of Bundles, Promise-types and Classes

Bundles

are re-usable and composable snippets of CFEngine policy code. The best practice is to think of bundles as micro-services. The following code defines a bundle called my_test, and it is a bundle for the agent.

bundle agent my_test{..policy-code...}

A bundle contains one or more promise-types.

Promise-types

are built-in functions in CFEngine. Think of a promise-type as a way to abstract yourself away from details. For instance, there is a promise-type called users. This promise-type allows you to manage local users on your system. Instead of using low-level commands to manage users, with the promise-type, you only have one simple syntax that works across all operating systems.

The most populare promise-types are files, packages, users, processes, services, commands and reports. Whenever you want to do some file configuration for example, you would use the files promise-type. The following code ensures the existence of the /tmp/hello-world file:

files:
"/tmp/hello-world"
  create => "true";

When defining desired states it is important to be clear about when and where you want this policy to apply. For that, CFEngine has the concept of classes.

A class

is an identifier which is used by the agent to decide when and where a part of a policy shall run. A class can either be user-defined, a so called soft-class, or it can be a hard class which is automatically discovered and defined by cf-agent during each run. Popular classes are any which means run this policy on all hosts, policy_server which means run this if the host is a policy server. There are more than 50 hard classes, and combined with regular expressions this gives you very granular control.

To see a list of available classes on your host, just type the following command:

# cf-promises --show-classes

Now let’s put the Bundle, Promise-type and Class components together in a final policy. As for classes we will use linux to define that the file /tmp/hello-world must exists on all hosts of type linux:

bundle agent my_test{
 files:
  linux::
   "/tmp/hello-world"
     create => "true";
}

Let’s save this code in a file called /tmp/my-policy.cf.

You can now run this policy either in Distributed (client-server) System or in a Stand Alone system. The next two sections will cover each of the options.

Option#1: Running the Policy on a Stand alone system

Since CFEngine is fully distributed we can run policies locally. This can come in handy as the result of a run is instant, especially during the design phase where you would like to test out various policies.

To run the file locally, you can log into any of your hosts that has CFEngine installed and follow these steps.For this tutorial, use your Policy Server for this as it is the same cf-agent that runs on the hosts as on the Policy Server.

When running in stand-alone mode we would need to add the bundlesequence my_test to the policy file. We do this by using a body common control. Below is the complete policy-file which has the bundlesequence in it.

body common control
{
  bundlesequence => { "my_test" };
}

bundle agent my_test{
 files:
  any::
   "/tmp/hello-world"
     create => "true";
}

Let’s save the file as /tmp/my-policy.cf

Tip!
Whenever you make or modify a policy, you can use the cf-promises command to run a syntax check:

# cf-promises -f /tmp/my-policy.cf

Unless you get any output, the syntax is correct. Now, to run this policy, simply type:

cf-agent -Kf /tmp/my-policy.cf

As you can see, the response is immediate! Running CFEngine locally like this is ideal for testing out new policies. To check that the file has been successfully created type:

ls /tmp/hello-world -l

If you want to see what the agent is doing during its run, you can run the agent in verbose mode. Try:

cf-agent -Kf /tmp/my-policy.cf --verbose

In a Stand Alone system, to make and run a policy remember to:

  • Create a new bundle
  • Include the body common control for correct bundlesequence
  • Save the bundle as a .cf-file
  • Run the policy with the cf-agent -Kf <path-to-cf-file>

Option#2: Running the Policy on a Distributed System

CFEngine is designed for large-scale systems. It is fully distributed which means that all the logic and decision making takes place on the end-points, or hosts as we call them in CFEngine. The hosts fetch their policies from one central distribution point. To continue with this option you need to have CFEngine running on at least one host and one policy server.

The CFEngine Server typically acts as a policy distribution point. On each host, the cf-agent process runs regularly. This process will by default, every 5 minutes, try to connect to cf-serverd on the policy server to check for policy updates.

By default cf-serverd will serve policy from the /var/cfengine/masterfiles directory. Upon updates, cf-agent will be notified and start to download these before executing them locally.

This means that by default you should store all your policies in the /var/cfengine/masterfiles directory on your policy server. So, now create /var/cfengine/masterfiles/my-policy.cf file with the following content:

bundle agent my_test{
 files:
  any::
   "/tmp/hello-world"
     create => "true";
}

NOTE: We recommend that you use a version control system to store the audit log of your policy.

Now we need to tell CFEngine that there is a new policy in town:

1. Modify the /var/cfengine/masterfiles/promises.cf file and insert the bundle name my_test in the bundlesequence in body common control. By doing so it will look something like this:

bundle-sequence

2. Include the my-policy.cf in the inputs section of body common control in promises.cf. Remember to add paragraphs around the file name. By doing so it will look something like this:

inputs

Save the file, and you are done!

On the policy server you can run the following command to make sure it all looks good

cf-agent -K

After some period of time (CFEngine runs by default every 5 minutes), log in to any of the bootstrapped clients and you will find the /tmp/-hello-world file there.

Whenever a host connects to the Policy Server, the host will ensure that it has the my-policy.cf file in the masterfiles directory. The promises.cf file will also be downloaded with the new instruction that there is a new policy. Within 5 minutes, whether you have 5 Linux hosts or 50,000 Linux hosts, they will all have the /tmp/hello-world file on their system. Yeah!

If you delete the file, it will be restored by CFEngine at its next run. We call this promise-repaired. If the file exists during a run, the result would be promise-kept.

In a distributed system, to make and ensure the continuous run of a policy remember to:

  • Create a new bundle
  • Save the bundle as a .cf-file
  • Copy the .cf file to /var/cfengine/masterfiles
  • Modify /var/cfengine/masterfiles/promises.cf to include your new bundle in the bundlesequence section
  • Modify /var/cfengine/masterfiles/promises.cf to include your new .cf file in the inputs section

 
Congratulations! You now have the basic knowledge needed to write and run CFEngine policies. Let's continue with an example on how to manage users. Click here to continue.






Please help us improve:

49 15

Do you have ideas / feedback to share with us? Send feedback