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.
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.
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/.
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.
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 ...
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:
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.
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
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.
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.
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.
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.
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).
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.
If you run cf-sketch on the policy hub,
all the files (installed sketches and runfile) are stored under
/var/cfengine/masterfiles/, which means they
will be automatically distributed to all the clients that get
their policy updates from the hub. Note that this doesn’t mean
that all the sketches will be executed in all the clients! This
depends on how the sketches are activated, in particular the
environment with which each sketch and parameter set is activated.
In our earlier example, the Security::SSH
sketch will be executed with the parameters contained in the
sshd_base_config
parameter set
only on Linux machines, according to the
environment with which it was activated.
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!
Manage the sshd
configuration parameters,
as shown before.
Monitor a set of files or directories for changes.
Configure the system timezone.
Configure the system host and domain names.
Manage kernel configuration parameters on Linux.
Maintain a checkout of a git repository.
Periodically ping
a list of hosts and
report whether they are reachable.
Stop CFEngine from executing if a certain file exists. Also known as “Cowboy mode”, this is good for those times when you need to fix or debug something without CFEngine getting in the way. As soon as you remove the file, CFEngine starts working again.
Manage EC2 virtual machines. Allows you to create and destroy VMs according to arbitrary conditions. Two similar sketches, Cloud::Services::VMWare and Cloud::Services::OpenStack, allow you to do similar tasks on VMware and OpenStack infrastructure.
Install and configure MySQL. Similar sketches exist for PostgreSQL and SQLite.
Install and configure the NewRelic system-monitoring daemon.
Install Postfix and configure it as a client.
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
};inputs
=>
{@(cfsketch_g.inputs)
}; }bundle
common
linux_machines
{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
{vars
: "inputs
"slist
=>
{"sketches/libraries/dclib/library.cf"
,"sketches/libraries/copbl/cfengine_stdlib.cf"
,"sketches/networking/ssh/ssh.cf"
}; }bundle
agent
cfsketch_run
{vars
:...
"__Security_SSH_1_001_Security_SSH_sshd_params[Protocol]
"string
=>
"2"
; "__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
::"__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"
; }
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.
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.
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.
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.
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
.
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.
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
.
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
) {vars
:# We store the individual parameters in an array,
# for easier reference and file editing
"logindefs[PASS_MAX_DAYS]
"string
=>
"
$(pass_max_days)
"
; "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
::"/etc/login.defs"
handle
=>
"edit_logindefs"
,comment
=>
"Set desired login.defs parameters"
,edit_line
=>
default
:set_config_values
("cflearn_password_expiration:password_expiration.logindefs"
);"/etc/shadow"
handle
=>
"edit_shadow_
$(params)
"
,comment
=>
"Modify
$(params)
for individual users."
,edit_defaults
=>
default
:backup_timestamp
,edit_line
=>
default
:set_user_field
("
$(users)
"
,"
$(fieldnum[$(params)])
"
,"
$(logindefs[$(params)])
"
),ifvarclass
=>
"!skip_
$(cusers[$(users)])
"
;reports
:!linux
::"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:
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.
We use the new parameters throughout the code, instead of the hard-coded values we had before.
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.
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.
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.
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.
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.
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
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.
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:
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.
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!