Chapter 5. The CFEngine Design Center

The CFEngine Design Center is a repository of pre-made, ready-to-use components called sketches that allow entirely data-driven system management. You can install, configure, and deploy sketches across your infrastructure without modifying or even looking at the code used to implement them. Sketches may range in complexity from simple system-configuration tasks (e.g., configuring the system timezone) to complex, multi-host operations (e.g., coordinating and managing cloud instances in Amazon EC2).

You may wonder what the difference is between the CFEngine Design Center and the CFEngine Standard Library. The latter contains reusable but low-level building blocks that you use on your own policies to perform certain common tasks. Most bundles and bodies in the Standard Library have very limited scope and functionality: editing or copying files, defining classes, etc. By contrast, the Design Center contains more encompassing components that perform complete tasks that can stand on their own, such as configuring DNS, setting up the system timezone, installing MySQL, or managing EC2 virtual machines. Most Design Center sketches use the Standard Library in their code, just like any other policy.

The Design Center also provides tools for managing and creating sketches, and a framework for encapsulating your own policies, for your own use or to share with the community. The Design Center is a community project hosted on GitHub, so you are encouraged to use it and contribute to it.

In this chapter we will describe the basic concepts behind the CFEngine Design Center, and how to make use of it both as a user and as a contributor.

Getting Started with the Design Center

To start getting familiarized with the workflow and capabilities of the Design Center tools, we will perform a task we did earlier: configuring the SSH daemon like we did in Editing /etc/sshd_config, but this time using a Design Center sketch. Later we will explain in detail the concepts behind the Design Center functionality.

Installing cf-sketch

As of this writing, the best way to install cf-sketch is to fetch it directly from the design-center repository hosted on GitHub. Inside the repository, cf-sketch can be found under tools/cf-sketch/.

Note

For now the cf-sketch tool needs to be installed separately, but most likely it will, at some point, be incorporated into the core CFEngine distribution.

# git clone https://github.com/cfengine/design-center/
Cloning into 'design-center'...
...
# cd design-center/tools/cf-sketch
# ls
Makefile         cf-dc-api.pl     config-root.json constdata.conf
README.md        cf-sketch.pl     config.json      perl-lib

Before running cf-sketch.pl, you should install the Term::ReadLine::Gnu Perl module, for a nicer interactive-prompt experience. Depending on your operating system, it may be available as a package. For example, on Ubuntu 12.04 you can simply run this command to install it:

# apt-get -y install libterm-readline-gnu-perl

If the package for this Perl module is not available on your system’s package repositories, you can install it using the Perl CPAN utility:

# cpan
   (the first time you run it, cpan will ask for some configuration information)
cpan> install Term::ReadLine::Gnu

You are now ready to start using cf-sketch.

Exploring cf-sketch

You can run cf-sketch.pl directly from the design-center/tools/cf-sketch/ directory. It will enter its interactive mode, which presents you with a prompt where you can type commands. You can type help to get a description of available commands.

# ./cf-sketch.pl
Welcome to cf-sketch version 3.5.1b1.
CFEngine AS, 2013.

Enter any command to cf-sketch, use 'help' for help, or 'quit' or '^D' to quit.

cf-sketch> help

The current commands are: ([]'s denote optional, caps need values)
activate [-n ACTIVATION_ID] SKETCH PARAMSETNAME|FILE[,...] [ENVIRONMENT|CLASSEXP]
                              Activate the given sketch using the named
                              parameter sets and environment. If a FILE path is
                              provided, it is read to create a new parameter
...
deactivate SKETCH|ACTIVATION_ID|all ...
                              Remove the given activations. If the sketch name
                              is specified, all its activations are removed. If
                              an activation ID is specified, only that
...

Note

You can also run commands non-interactively by providing commands as arguments to cf-sketch in the command line. For example:

# ./cf-sketch.pl search system
The following sketches match your query:

System::Logrotate Sets defaults and user permissions in the sudoers file
System::Routes Sets defaults and user permissions in the sudoers file
System::Sudoers Sets defaults and user permissions in the sudoers file
System::Syslog Configures syslog
System::access Manage access.conf values
System::config_resolver Configure DNS resolver
System::cron Manage crontab and /etc/cron.d contents
System::etc_hosts Manage /etc/hosts
System::motd Configure the Message of the Day
System::set_hostname Set system hostname. Domain name is also set on Mac,
        Red Hat and and Gentoo derived distributions (but not Debian)
System::sysctl Manage sysctl values
System::tzconfig Manage system timezone configuration

The rest of this chapter will show the interactive mode usage.

Let us now go through the typical sequence to get the SSH-management sketch installed and deployed:

  1. First, we search for the sketch we want, and get it installed:

    cf-sketch> list
    
    No sketches are installed. Maybe use 'search' instead?
    
    cf-sketch> search ssh
    
    The following sketches match your query:
    
    Security::SSH Configure and enable sshd
    
    cf-sketch> install Security::SSH
    
    Sketch Security::SSH installed under /var/cfengine/masterfiles/sketches.
    
    cf-sketch> list
    
    The following sketches are installed:
    
    CFEngine::dclib Design Center standard library
    CFEngine::stdlib The portions of the CFEngine standard library
            (also known as COPBL) that are compatible with 3.4.0 releases
    Security::SSH Configure and enable sshd

    Note that two sketches are installed in addition to the one we requested: the CFEngine standard library (CFEngine::stdlib) and the Design Center standard library (CFEngine::dclib). They are installed automatically because the Security::SSH declares them as dependencies.

  2. The next step is to provide the appropriate parameters for the sketch through a parameter set. Each sketch declares a list of parameters that it needs, and that list can be queried using the info -v command.

    cf-sketch> info -v Security::SSH
    
    The following sketches match your query:
    
    Sketch Security::SSH
    Description: Configure and enable sshd
    Authors: Diego Zamboni <diego.zamboni@cfengine.com>,
             Ted Zlatanov <tzz@lifelogs.com>
    Version: 1.1
    License: MIT
    Tags: cfdc
    Installed: Yes, under /var/cfengine/masterfiles/sketches
    Activated: No
    Parameters:
      For bundle sshd
        params: array

    We can see here some general information about the sketch, including its installation state and the list of parameters that it takes. The params parameter contains a list of ssh configuration values, like those shown in Editing /etc/sshd_config.

    Use the define paramset command to create a new parameter set named sshd_base_config (the name is arbitrary; note that cf-sketch automatically generates a name if you don’t provide one). We will use the same parameters we configured in Editing /etc/sshd_config. At the end we use the list command to verify that the parameter set was correctly created.

    cf-sketch> define paramset Security::SSH
    
    Please enter a name for the new parameter set
    (default: Security::SSH-sshd-000): sshd_base_config
    Querying configuration for parameter set 'sshd_base_config'
    for bundle 'sshd'.
    Please enter parameter params.
      (enter STOP to cancel)
    Next key (Enter to finish): Protocol
    params[Protocol]: 2
    Next key (Enter to finish): X11Forwarding
    params[X11Forwarding]: yes
    Next key (Enter to finish): UseDNS
    params[UseDNS]: no
    Next key (Enter to finish):
    Defining parameter set 'sshd_base_config' with the entered data.
    Parameter set sshd_base_config successfully defined.
    
    cf-sketch> list -v params
    
    The following parameter sets are defined:
    
    sshd_base_config: Sketch Security::SSH
      [Security::SSH][params][Protocol]: 2
      [Security::SSH][params][UseDNS]: no
      [Security::SSH][params][X11Forwarding]: yes
  3. You can have the same sketch running with different parameters on different machines, or even on the same machine but under different conditions. To differentiate them, you define environments, which use CFEngine class expressions to define groups of machines. For our example, we want the SSH parameters we just defined to be applied on all Linux machines, so we will create an environment that contains all the Linux machines:

    cf-sketch> define environment
    
    Please enter a name for the new environment: linux_machines
    I will now prompt you for the conditions for activation, test, and verbose
    mode that will be associated with environment 'linux_machines'. Please
    enter them as CFEngine class expressions.
    Please enter the activation condition: linux
    Please enter the test condition: !any
    Please enter the verbose condition: !any
    Environment 'linux_machines' successfully defined.
    
    cf-sketch> list -v env
    
    The following environments are defined:
    
    linux_machines
      [activated]: linux
      [test]: !any
      [verbose]: !any

    We define a new environment called linux_machines, which has its activation condition set to linux. This gets interpreted as a CFEngine class expression, which means that the linux_machines environment will be active on all the Linux machines. Environments also define conditions for when to activate test and verbose modes (these have to be explicitly supported by the sketch, and not all of them do), which by default are always disabled through the !any class expression.

    Tip

    The conditions in an environment are arbitrary CFEngine class expressions! This means that you can have them depend on arbitrary conditions, both static (e.g. operating system, Linux distribution, architecture, etc.) and dynamic (e.g. time of day, day of the week, CPU load, etc.)—basically anything that can be differentiated through a CFEngine class. If you need a refresher on class expressions, please see Classes and Decision Making.

  4. Having defined the parameters you want to use and the environment in which you want to use them, you need to activate the sketch. Activation connects a sketch with a parameter set and an environment. For our example, what we want can be summarized as, “I want to activate the Security::SSH sketch using the parameter set sshd_base_config in all my Linux machines”. We use the activate command:

    cf-sketch> activate Security::SSH sshd_base_config linux_machines
    
    Using generated activation ID 'Security::SSH-1'.
    Using existing parameter definition 'sshd_base_config'.
    Using existing environment 'linux_machines'.
    Activating sketch Security::SSH with parameters sshd_base_config.
    
    cf-sketch> list activations
    
    The following activations are defined:
    
    Activation ID Security::SSH-1
      Sketch: Security::SSH
      Parameter sets: [ sshd_base_config ]
      Environment:  'linux_machines'

    Note that you can activate the same sketch using different parameter sets on different environments, which would allow you, for example, to have different SSH configurations for Linux and Solaris machines, for machines that belong in your DMZ versus hosts in your internal VLAN, or during workdays and during the weekend. The possibilities are limited only by what you can express as CFEngine class expressions in the activation condition for each environment.

    Activations also have a name. By default the activate command generates a name automatically, as shown in this example (the generated name is Security::SSH-1). If you want to explicitly name an activation, you can do so by passing the -n option to the activate command.

  5. The remaining step is to actually execute the sketches. You can do so using the run command:

    cf-sketch> run
    
    Runfile /var/cfengine/masterfiles/cf-sketch-runfile-standalone.cf
    successfully generated.
    Now executing the runfile with:
    /usr/local/sbin/cf-agent \
      -f /var/cfengine/masterfiles/cf-sketch-runfile-standalone.cf

    Executing the sketches implies generating a runfile, which is a CFEngine policy file that contains all the necessary information and code. Once this file is generated, cf-sketch executes it with cf-agent. After execution finishes, you can inspect the /etc/ssh/sshd_config file to verify that the Protocol, UseDNS, and X11Forwarding parameters have been set to the correct values.

    The run command performs a one-time execution of the currently-activated sketches, and is useful for testing your sketch activations while configuring them. For actual deployment of your sketches, you do not want to run them by hand all the time, but integrate them into the regular execution of CFEngine. For this, use the deploy command:

    cf-sketch> deploy
    
    Runfile /var/cfengine/inputs/cf-sketch-runfile.cf successfully generated.

    Note that this generates the runfile (with a different filename from the earlier one, to indicate it is not meant to be run standalone), but does not execute it. For this to happen, you need to integrate it into your /var/cfengine/masterfiles/promises.cf file. As of this writing, you need to make the following changes. You need to do this just once, the first time you deploy a Design Center runfile.

    1. Remove the line from the inputs attribute that loads @(cfengine_stdlib.inputs) (on 3.5.1 and later) or “libraries/cfengine_stdlib.cf” (on 3.5.0 and older).

    2. Add the following line after the line that loads “cf-sketch-runfile.cf”:

      @(cfsketch_g.inputs),

    Now you can verify your promises.cf file with the following command. If there is no output, the file is correct:

    # cf-promises -f /var/cfengine/masterfiles/promises.cf

    Now, every time cf-agent runs, it will automatically load and execute the activated sketches. If you make any changes in the sketch installations or configuration, simply run the deploy command again, and your changes will be picked up automatically.

Now that you know how to interact with the Design Center through cf-sketch, I invite you to explore the existing sketches, which allow you to perform a wide range of tasks. Here are some of the sketches that I find particularly useful. This is only a sample, and new sketches are being contributed all the time, so use that cf-sketch search command and explore on your own too!

Peeking Under the Hood

We have seen how to interact with the Design Center through the cf-sketch tool. Let us look a little under the hood so you can better understand what’s going on. I invite you to take a look at the generated /var/cfengine/masterfiles/cf-sketch-runfile-standalone.cf file—by now you should be able to understand most of it. Among other sections, you will see the following (some lines abbreviated or rewrapped to fit on the page):

body common control
{
      bundlesequence => { cfsketch_g, cfsketch_run };  1
      inputs => { @(cfsketch_g.inputs) };
}

bundle common linux_machines   2
{
  vars:
      "activated" string => "linux";
      "env_vars" slist => { "activated", "test", "verbose" };
      "test" string => "!any";
      "verbose" string => "!any";
  classes:
      "runenv_linux_machines_activated" expression => "linux";
      "runenv_linux_machines_test" expression => "!any";
      "runenv_linux_machines_verbose" expression => "!any";
}

bundle common cfsketch_g   3
{
  vars:
      "inputs" slist => { "sketches/libraries/dclib/library.cf",
                          "sketches/libraries/copbl/cfengine_stdlib.cf",
                          "sketches/networking/ssh/ssh.cf" };
}

bundle agent cfsketch_run   4
{
  vars:
...
      "__Security_SSH_1_001_Security_SSH_sshd_params[Protocol]" string => "2";   5
      "__Security_SSH_1_001_Security_SSH_sshd_params[UseDNS]" string => "no";
      "__Security_SSH_1_001_Security_SSH_sshd_params[X11Forwarding]" string => "yes";
  methods:
...
    runenv_linux_machines_activated::   6
      "__Security_SSH_1_001_Security_SSH_sshd"
        usebundle => cfdc_sshd:sshd("linux_machines",
  "default:cfsketch_run.__..._001_Security_SSH_sshd_metadata",
  "default:cfsketch_run.__..._001_Security_SSH_sshd_params"),
        ifvarclass => "any",
        useresult =>
          "return___Security_SSH_1_001_Security_SSH_sshd";
}
1

This file has a body common control definition because it is designed to be run by itself (hence the “standalone” in the filename). It calls two bundles: cfsketch_g, which defines some common variables, and cfsketch_run, which invokes all the activated sketches. Note that the inputs definition is taken from the cfsketch_g.inputs variable, which contains all the files that need to be loaded for the currently activated sketches.

If you look at the non-standalone file /var/cfengine/masterfiles/cf-sketch-runfile.cf, generated by the deploy command, you’ll see that the only difference is the absence of the body common control definition, which makes it possible to load the sketch from your main promises.cf policy file.

2

Environments defined in the Design Center framework are implemented using common bundles. In this case, we have a bundle named linux_machines, just like the environment we defined in step 4 during the activation of the sketch. All environments contain at least three fields named activated, test and verbose, which are declared as both variables and classes so that they can be used for decision making later on. In our example we defined the environment automatically with the activate command and we set only the activated class expression (setting it to linux); the other two class expressions took default values. Note that the runenv_linux_machines_activated class is defined to evaluate the class expression “linux”. This way, the runenv_linux_machines_activated class can be used to determine whether the environment should be activated during execution.

3

The cfsketch_g bundle contains useful general information needed for the execution of the sketches. In this particular case, it contains the list of files that need to be loaded. These are all the CFEngine files installed as part of the sketches that are going to be executed. This list is used in the inputs declaration in the earlier body common control definition.

4

We finally come to the bundle that executes the sketches, called cfksetch_run. This is the bundle that takes care of executing all the sketches, with the appropriate parameters, under the appropriate conditions.

5

All the parameters that we defined in the parameter set in step 2 are declared as variables here, for passing to the appropriate bundles. The variables are named according to an internal naming convention to make them unique, but you can clearly see the SSH parameter names and values here, just as we provided them to cf-sketch.

6

The bundle that implements the Security::SSH sketch functionality is called from a methods: promise inside cfsketch_run. Note that the execution of this promise is conditioned according to the activation class for the linux_machines environment, and the bundle is called with the appropriate parameters, in particular the array that contains the defined parameter values, called __Security_SSH_1_001_Security_SSH_sshd_params in this example.

Contributing to the Design Center

Now you know how to use the Design Center to install, configure, and deploy sketches. With this you are able to use any of the sketches available in the Design Center repository. But at some point you may want to write your own! We will now look at how to create your own sketches.

Suppose you have written a very useful piece of CFEngine policy, and you would like to share it with the world, or at least with your colleagues, so that they can all benefit from it. How do you go about it?

The foundation of any Design Center sketch should be a working piece of CFEngine policy, in the form of a bundle of type agent that performs the appropriate functionality. This bundle can call other bundles or bodies as appropriate, but it should be callable as a single point of entry. At least until you become more familiar with how sketches are structured, I would advise you to write your bundles first as regular CFEngine policy, and then convert them to sketches. This is what we will do in this section. As an example, we will use the password_expiration() bundle that we developed in Password expiration periods.

The first step is to define a name for our new sketch. We can use arbitrary names, but the Design Center by convention encourages us to use names of the form Category::Sketch, or even Category::Subcategory::Sketch. For our password-expiration configuration sketch, we will use Security::password_expiration.

We now need to define the interface for the sketch. In our original example, all the parameters are specified as variables inside the password_expiration() bundle, but for a sketch, we want those values as parameters specified by the user when they configure the sketch. Let us look through the original code, make a list of what those configurable parameters should be, and decide on their names while we are at it:

pass_max_days

The maximum password age in days.

pass_min_days

The minimum password age, also in days.

pass_warn_age

The warning period before a password expires, in days.

min_uid

The minimum UID for setting password-expiration parameters. Users with UID below this threshold will not be modified.

skipped_users

A comma-separated list of usernames to skip when setting password-expiration parameters.

skipped_uids

A comma-separated list of UIDs to skip when setting password-expiration parameters.

All of these can be specified as strings, just as they were in the original policy code.

We also need to decide on a namespace in which to place the sketch. I suggest using a namespace that contains a reference of the origin of the sketch (for example, all CFEngine-produced sketches have namespaces that start with cfdc_ for “CFEngine Design Center”), and also the name of the sketch (or a shortened, representative version of it). We will use cflearn_password_expiration.

Note

Namespaces are top-level naming divisions that help avoid conflicts in bundle, body or class names. Please refer to Namespaces for background.

Once we have this information, we can rewrite our policy file a bit to make it ready to use as a sketch. Here is the updated code, with some comments about the changes we made (as you go through these, please compare them to the original code in ):

bundle agent password_expiration(pass_max_days, pass_min_days, pass_warn_age,
      min_uid, skipped_users, skipped_uids)  1
{
  vars:
      # We store the individual parameters in an array,
      # for easier reference and file editing
      "logindefs[PASS_MAX_DAYS]" string => "$(pass_max_days)";   2
      "logindefs[PASS_MIN_DAYS]" string => "$(pass_min_days)";
      "logindefs[PASS_WARN_AGE]" string => "$(pass_warn_age)";

      # Position of each parameter in /etc/shadow
      "fieldnum[PASS_MIN_DAYS]"  string => "4";
      "fieldnum[PASS_MAX_DAYS]"  string => "5";
      "fieldnum[PASS_WARN_AGE]"  string => "6";
      
      # List of parameters to modify
      "params" slist => getindices("logindefs");

      # Get list of users, and also generate them in canonified form
      # This list already excludes users specified by UID or name.
      "users" slist => getusers("$(skipped_users)", "$(skipped_uids)");
      "cusers[$(users)]" string => canonify("$(users)");

  classes:
      # Define classes for users that must not be modified by UID threshold
      "skip_$(cusers[$(users)])"  expression => islessthan(getuid("$(users)"),
                                                           "$(min_uid)");
      
  files:
    linux::   3
      "/etc/login.defs"
        handle => "edit_logindefs",
        comment => "Set desired login.defs parameters",
        edit_line =>
          default:set_config_values(
            "cflearn_password_expiration:password_expiration.logindefs"); 4
      
      "/etc/shadow"
        handle => "edit_shadow_$(params)",
        comment => "Modify $(params) for individual users.",
        edit_defaults => default:backup_timestamp,   5
        edit_line => default:set_user_field("$(users)",
                                            "$(fieldnum[$(params)])",
                                            "$(logindefs[$(params)])"),
        ifvarclass => "!skip_$(cusers[$(users)])";

  reports:
    !linux::   6
      "Warning: Security::password_expiration only works on Linux for now.";
}

The logic of the code has not changed, but a few things have been updated or rearranged:

1

We have added all the configurable parameters we determined earlier as arguments to our password_expiration() bundle. All of these values are now accepted as arguments instead of being hardcoded into the policy. This will be the entry point for our sketch.

2

We use the new parameters throughout the code, instead of the hard-coded values we had before.

3

We have added a class expression to limit the execution of the sketch to systems that support its behavior. This is necessary because a sketch might be activated on many different systems, and it needs to make sure to do the right thing regardless of where it is running. In this case, we have limited it to Linux systems, in which we know the password-expiration parameters are configured using the /etc/login.defs file.

4

Here we see the first use of namespaces, in two places: we have added the default: namespace specification to the standard library bundle set_config_values(), and we have specified our sketch namespace in the fully-qualified name of the logindefs array that we pass to set_config_values(). The fully-qualified name of the array ("cflearn_password_expiration:password_expiration.logindefs") contains the namespace, the bundle name, and the array name.

5

We need to add the default: namespace to all the standard library components we use—in this case also to the backup_timestamp body and the set_user_field() bundle.

6

Finally, and to complement the limitation of functionality of the sketch to Linux systems, we added a reports: promise that prints a warning on non-Linux systems, to let the user know that the sketch is non-functional on them.

We now have the policy file in a shape that is well suited for conversion into a sketch. The last step is to actually wrap that policy file into the appropriate structure required by a sketch, which includes putting the file into its own directory. Add to that directory a README file and a file named sketch.json that contains all the metadata about the sketch, as well as all the information needed to configure and invoke it. You can find the full specification in the “Writing a Design Center Sketch” guide, but you can also use the sketchify command of cf-sketch to do it automatically. sketchify reads the policy file, asks you for the appropriate information, and produces a ready-to-use sketch in your local checkout of the Design Center repository. This is what we will use now.

The sketchify command takes as its only argument the file containing our policy file, which it reads and analyzes for bundles of type agent. In our case there is only one such bundle, so it is used automatically as the entry point for the sketch (if more than one agent bundle is found, you will be asked which one you want to use as the sketch entry point).

# ./cf-sketch.pl sketchify /vagrant/password_expiration.cf
Reading file '/vagrant/password_expiration.cf'.
Automatically choosing the only agent bundle in /vagrant/password_expiration.cf:
    'password_expiration'
I will now prompt you for the data needed to generate the sketch.
Please enter STOP at any prompt to interrupt the process.

Note

The Design Center framework supports sketches with more than one entry point, but sketchify as of this writing lets you choose only one of them.

Next, sketchify asks us for some general information about the sketch, including its name, description, version number, license (most sketches in the Design Center use the MIT license), tags, and author information. You can also enter the names of other CFEngine policy files that should be included in this sketch. Most sketches are contained in a single .cf file, but if you have a very complex sketch, the ability to package multiple .cf files withing the same sketch could be useful.

Tip

This has nothing to do with sketch dependencies—any files you specify here will be included within the sketch you are creating. As of this writing, sketchify does not handle sketch dependencies. You need to include them by hand in the generated sketch.json file.

Please enter the sketch name: Security::password_expiration
Please enter a one-line description for the new sketch:
      Manage password expiration and warning periods
Please enter a version number: 1.0
Please enter a license for this sketch: MIT
Please enter a comma-separated list of tags for this sketch: 
      security, cflearn, passwords
Please enter a comma-separated list of author names
(preferably of the form Name <email>):
      Diego Zamboni <diego.zamboni@cfengine.com>
Please enter any other files that need to be included with this sketch
(press Enter to stop):

Now, sketchify queries us for the information needed for defining the sketch API. For each parameter of the entry bundle, sketchify prompts for its type, a description, and an optional default value. In our example, we give default values for all the parameters except skipped_users and skipped_uids.

Thank you. I will now prompt you for the information regarding the parameters
of the entry point for the sketch.
For each parameter, you need to provide a type and a description.
(enter STOP at any prompt to abort)

For parameter 'pass_max_days':
  Please indicate the type as
    (1) string, (2) boolean, (3) list, (4) array (1-4): 1
  Please give me a short description for this parameter: 
      Maximum password age in days
  Please enter the default value for this parameter (empty for no default): 180
For parameter 'pass_min_days':
  Please indicate the type as
    (1) string, (2) boolean, (3) list, (4) array (1-4): 1
  Please give me a short description for this parameter: 
      Minimum password age in days
  Please enter the default value for this parameter (empty for no default): 5
For parameter 'pass_warn_age':
  Please indicate the type as
    (1) string, (2) boolean, (3) list, (4) array (1-4): 1
  Please give me a short description for this parameter:
      Warning period before password expires, in days
  Please enter the default value for this parameter (empty for no default): 2
For parameter 'min_uid':
  Please indicate the type as
    (1) string, (2) boolean, (3) list, (4) array (1-4): 1
  Please give me a short description for this parameter: 
      Minimum UID to consider when updating existing accounts
  Please enter the default value for this parameter (empty for no default): 500
For parameter 'skipped_users':
  Please indicate the type as
    (1) string, (2) boolean, (3) list, (4) array (1-4): 1
  Please give me a short description for this parameter:
      Comma-separated list of usernames to skip when updating existing accounts
  Please enter the default value for this parameter (empty for no default):
For parameter 'skipped_uids':
  Please indicate the type as
    (1) string, (2) boolean, (3) list, (4) array (1-4): 1
  Please give me a short description for this parameter:
      Comma-separated list of UIDs to skip when updating existing accounts
  Please enter the default value for this parameter (empty for no default):

Having defined the sketch API, sketchify now queries you for information about the namespace to use for this sketch. We decided before which namespace to use, but the namespace declaration does not yet appear in the policy file we are using, so sketchify offers to insert it automatically.

We are done with the API. Now checking the namespace declaration.

The file '/vagrant/password_expiration.cf' does not have a namespace declaration.
It is recommended that every sketch has its own namespace to avoid potential
naming conflicts with other sketches or policies.
I can insert the appropriate namespace declaration, and have generated a
suggested namespace for you: cfdc_security_password_expiration
Please enter the namespace to use for this sketch: cflearn_password_expiration

Note

If you insert the namespace declaration in the policy file by hand, before running it through sketchify, the command will automatically detect and use the declaration.

In addition to the parameters defined in the API, a sketch entry bundle can receive two special parameters of type environment and metadata. If used, these parameters will be automatically generated and passed by the Design Center framework when executing the sketch.

  • The environment parameter contains the name of the environment with which the sketch has been activated. This allows the sketch to access the characteristics of the environment, including the verbose and testing fields (interpreted as classes, so that the sketch can easily use them as conditions to alter its behavior).

  • The metadata parameter contains the name of an array in which the Design Center framework automatically stores all the sketch metadata, including its name and description, authors, etc.

If these parameters are not already passed to the entry bundle in the input file, sketchify will ask you if you want to add them.

The entry point 'password_expiration' doesn't seem to receive
parameters of type 'environment' or 'metadata'.  These arguments
are not necessary, but can be useful for the sketch to respond to
different run environment parameters (i.e. test or verbose mode)
or to have access to its own metadata.

I can automatically add these parameters to the bundle, together
with some code to put their information in classes and variables,
and also to create an activation_id variable that will make it
possible to use the new sketch with the CFEngine Enterprise
Design Center GUI.

Would you like me to do this? (Y/n) y

In addition to adding the parameters to the bundle, sketchify will also add some boilerplate code to do the following:

  • Extract the values of all fields defined in the active environment (at least activated, verbose and testing, and possibly others if defined) into both classes and variables. For example, it will create a string variable named verbose that contains the class expression stored in that field, and also a class named verbose that will be set to the result of evaluating that class expression. You can then use that class within your sketch to easily enable additional reports, when verbose mode has been activated in the current environment.

  • Create a string variable named activation_id that contains a unique identifier for the current sketch activation. Multiple activations of the same sketch will have different activation_id values, so you can use the IDs to differentiate among the activations. This is used mainly by the Enterprise GUI interface to the Design Center.

As of this writing, this is the code that is automatically inserted by sketchify at the top of the bundle (code reformatted to fit on the page):

  classes:
      "$(vars)" expression => "default:runenv_$(runenv)_$(vars)";
      "not_$(vars)" expression => "!default:runenv_$(runenv)_$(vars)";
  vars:
      "activation_id"
         string => canonify("$(this.bundle)_$($(metadata)[activation]
             [identifier])_$($(metadata)[activation][timestamp])");
      "vars" slist => { "@(default:$(runenv).env_vars)" };
      "$(vars)" string => "$(default:$(runenv).$(vars))";

Finally, sketchify asks you for the location under the currently-used sketch repository where the new sketch should be stored, automatically generates a skeleton README file (including the parameter descriptions you provided), and regenerates the cfsketches.json file used as an index of available sketches.

Thank you! We are almost done.
Please enter the directory within the sketches repository where this sketch
should be stored: security/password_expiration
Your new sketch will be stored under
/design-center/sketches/security/password_expiration/
Writing /design-center/sketches/security/password_expiration/sketch.json
Transferring /vagrant/password_expiration.cf to
  /design-center/sketches/security/password_expiration/password_expiration.cf
Adding new sketch to /design-center/sketches/cfsketches.json
Generating a README file for the new sketch.
wrote /design-center/sketches/security/password_expiration/README.md...
We are done! Please check your new sketch under
/design-center/sketches/security/password_expiration

We are done! You can verify that the new sketch is ready for installation and use using cf-sketch:

cf-sketch> search password

The following sketches match your query:

Security::password_expiration Manage password expiration and warning periods

cf-sketch> install Security::password_expiration

Sketch Security::password_expiration installed under
/var/cfengine/masterfiles/sketches.

cf-sketch> info -v Security::password_expiration

The following sketches match your query:

Sketch Security::password_expiration
Description: Manage password expiration and warning periods
Authors: Diego Zamboni <diego.zamboni@cfengine.com>
Version: 1.0
License: MIT
Tags: passwords, security, sketchify_generated, cflearn
Installed: Yes, under /var/cfengine/masterfiles/sketches
Activated: No
Parameters:
  For bundle password_expiration
    pass_max_days: string (Maximum password age in days) [default value: '180']
    pass_min_days: string (Minimum password age in days) [default value: '5']
    pass_warn_age: string (Warning period before password expires, in days)
      [default value: '2']
    min_uid: string (Minimum UID to consider when updating existing accounts)
      [default value: '500']
    skipped_users: string (Comma-separated list of usernames to skip when
      updating existing accounts)
    skipped_uids: string (Comma-separated list of UIDs to skip when updating
      existing accounts)

While sketchify automates most of the process of creating a sketch from an existing bundle, there are a few things that it doesn’t handle. You may want to take a look at the files it generates for sanity checking. Here are some of the things you may want or need to fix by hand:

  • Dependencies: If your sketch depends on other sketches, you need to add them by hand to the depends metadata element in the generated sketch.json file. At the moment, sketchify automatically inserts a dependency on CFEngine 3.5.0, which is the minimum recommended version of using Design Center sketches.

  • Multiple entry points: The Design Center framework supports multiple entry points per sketch (to different bundles), this is not supported at the moment by sketchify, so you need to add any additional entry points by hand.

If you create a useful sketch, why not contribute it to the community? After all, the Design Center will only get better as more people contribute to it. This is very easy to do by submitting the new sketch as a pull request to the design-center project at Github.

Learning More About the Design Center

In this chapter we have touched only on the basics for using and contributing to the Design Center, but there is much more to explore! I invite you to review the Design Center documentation to learn more. We have mentioned sketches as the main type of content in the Design Center repository, but it also hosts other types of content:

examples

Contributed examples of CFEngine policy. These are not meant to be ready to use like sketches, or organized in any way, but rather to serve as starting points for others to write their own policies or to see how certain things can be achieved in CFEngine.

tools

Miscellaneous tools that have been contributed by the CFEngine community to help you work with CFEngine. It also contains cf-sketch, which manages sketches on a system.

As a user, you interact with the Design Center mostly through tools like cf-sketch or the Design Center GUI in CFEngine Enterprise. But these tools do not directly manipulate sketches on the system—instead, these operations are done through the Design Center API, which performs all operations related to sketches, parameter sets, environments, validations, and deployment. The API is implemented through the cf-dc-api.pl program, which is part of the Design Center installation. You can find detailed information about the Design Center API and its operations in the Design Center API documentation.

The CFEngine Design Center is the easiest way of interfacing with CFEngine, allowing you to perform arbitrarily complex configurations without touching the CFEngine policy code. It is a project in active development, so by the time you try them, some things may have changed from the descriptions in this chapter. The Design Center depends on active contributions from the user community, so I encourage you to participate!