Base your files on high level services as you do with bundles, See How to choose and name bundles. The purpose of breaking up policy into files is to limit the scope of the policy to manageable amounts, making it easier to understand. It will only be easier to understand if the casual user can immediately locate promises from the name of the file.
You can place related files in subdirectories of the inputs to localize them. This also makes updating more efficient, as fewer objects need to be checked.
The Nova/Constellation Knowledge base allows you to search for promises, but everything will make more sense if promises are found in an intuitive place. |
Note that all CFEngine variables are globally accessable, by using their
fully qualified name :$(bundle.variable) , or @(bundle.variable) ,
so placing variables in one place or another does not affect their accessability.
|
Variables should be defined as close to the place where they are used as possible. The user will expect to find variables defined:
Variables that define global aspects of configuration, e.g.
common
bundles. This places them
in a neutral context.
The only reason to define a variable far from its place of use would be when writing generically re-usable methods and passing data as parameters, See When to use a paramaterized bundle or method. However, re-usability can make rules harder to understand.
Use the name of a bundle to represent a meaningful aspect of system adminstration, We recommend using a two or three-part name, that explains the context, general subject heading and special instance. Names should be service oriented and should guide non-experts to understand what they are about. e.g.
Put things into a single bundle if:
Put things into different bundles if:
In general, keep the number of bundles to a minimum. This is a knowledge management issue. Clarity comes from differentiation, but only if the number of things is small.
If you need to arrange for a managed convergent collection or sequence of promises that will occur for a list of (multiple) names or promisers, then use a bundle to simplify the code.
Write the promises, which may or may not be ordered, using a parameter for the different names, then call the method passing the list of names as a parameter to reduce the amount of code.
bundle agent testbundle { vars: "userlist" slist => { "mark", "jeang", "jonhenrik", "thomas", "eben" }; methods: "any" usebundle => subtest("$(userlist)"); } ########################################### bundle agent subtest(user) { commands: "/bin/echo Fix $(user)"; files: "/home/$(user)/." create => "true"; reports: linux:: "Finished doing stuff for $(user)"; }
common
bundles?common
bundles have global scope).
Note, if you are converting from CFEngine 2 you should know the following. In CFEngine 2, all classes were global and it was common to define all classes in a big unmanageable list. That meant that there was a chance of class name collisions. CFEngine 3 has both local and global classes, allowing you to limit the scope of classes and define them more in context. |
common
bundles?$(mybundle.myname)
are always
globally accessible, so this is a cosmetic issue.)
$(bundle.var)
adds clarity.
This chapter lists a number of recommended practices.
Never make system changes when humans are unavailable, e.g. just before going offline for the weekend. No matter how careful you have been, mistakes can be made and you need to have at least 24 hours experience with a running policy to lend it your trust.
Do not embed simple shell commands with cfengine commands
promises, like this:
commands: # Don't do this! "/bin/rm -r /tmp/xyz*"; "/bin/mkdir /tmp/abcd";
WHY? Embedded shell commands like this cannot be managed by cfengine, so none of the protections that cfengine offers can be applied to the process. Moreover, this starts a new process, adding to the burden on the system.
Most importantly, this approach works like a covert channel, making changes that are not directly visible to cfengine.
When you run cfengine, there is no reason to maintain separate cron jobs. Instead, use cfengine's time classes to work like a user interface for cron. This allows you to have a single, central cfengine file which contains all the cron jobs on your system without losing any of the fine control which cron affords you. All of the usual advantages apply:
WHY? This gives you a single point of definition for batch jobs. It encapsulates jobs under cfengine's tutelage, for improved control and security. Finally, cfengine can collate and summarize the outputs from multiple scripts in a rational monitoring process.
Do not spend your time writing scripts to embed within cfengine. If you are doing this, you are not using the potential of cfengine and you are not benefitting from the protections and efficiencies that cfengine offers. Custom scripts should be for your specific business operations, not for system maintenance.
If you are tempted to use scripts to achieve your needs, consider
jusing methods
, and if necessary consult with support personnel
for advice.
Cfengine's adaptive locking is an important system protection. You
should not run cfengine continuously without this protection, e.g. by
running with the ‘-K’ flag set, of by setting ifelapsed
to
zero for a promise.
WHY? System inconsistencies can result and unnecessary resources will be consumed.
Searching through files on a disk is one of the most time consuming operations for a computer. If you have to do it, make sure that you are getting the most for your CPU-cycles and combine operations in a single promise. This allows cfengine to optimize the resource use of the system.
files: "$(site)/app/webroot/img/inside/extmans" comment => "Copy the images for the private html documents", copy_from => cp("$(kbase)"), perms => p("root","644"), file_select => by_name(".*.png"), depth_search => recurse("1"), action => ifelapsed("60");
Changes to policy should always be part of a serious and considered plan. They should not be ad hoc. That said, consideration of changes should not be so time-consuming that it cripples human resources, or leads to change-avoidance because it seems daunting.
It is better to make many small changes than few large changes. Large changes involve many interdependencies, which make them fragile to unexpected contingencies. The risk of large changes is high. The risk of small changes is low.
CFEngine makes it easy to make small changes frequently, without operational repercussions. As long as humans are on hand during the change to observe possible side-effects this.
Always add comment attributes to your promises to explain the intention.
files: # This is a throw-away comment, below is a full-bodied promise "/tmp/testfile" # promiser comment => "This is for keeps...", # Live comment create => "true", # Constraint 1 perms => p("612"); # Constraint 2
If a promise has a stakeholder that is worthy of special mention, then use the promisee fields to add the name of this person.
files: "/tmp/testfile" -> { "stakeholder@company.com" }, comment => "This is for keeps...", # Live comment create => "true", # Constraint 1 perms => p("612"); # Constraint 2
If a promise depends on another promise being run before it, use the
depends_on
fields to document the handle of the other prior promise.
This allows tracing of the impact chain.
files: "/tmp" handle => "make_temp", comment => "This is for keeps...", # Live comment create => "true", # Constraint 1 perms => p("612"); # Constraint 2 "/tmp/testfile" depends_on => { "make_temp" }, comment => "This is for keeps...", # Live comment create => "true", # Constraint 1 perms => p("612"); # Constraint 2
If you are coming to cfengine from another scripting langauge, you will probably be tempted to add a lot of `logic' to your cfengine program, testing whether things are true and trying to control the order of things. This is not necessary. You should think of each promise as being a self-contained `nugget' that requires little additional coding. The more coding you add, the more fragile a configuration becomes.
The hardest part of using cfengine for programmers is letting go of the reins.
If you have a number of system resources that all make the same promise, then use lists to iterate over the resources in a single promise, rather than coding the same promise many times.
vars: "watch_files" slist => { "/etc/passwd", "/etc/shadow", "/etc/group", "/etc/services" }; files: "$(watch_files)" comment => "Change detection on the above", changes => change_management_trip_wire;
Familiarize yourself with the current cfengine_stdlib.cf
file in the
software distribution. This contains many body templates, e.g.
local_cp() remote_cp() secure_cp() if_elapsed() recurse()
Use these pre-existing body templates whenever possible, rather than inventing new ones. For example:
bundle agent update { files: "/path/to/copy" comment => "Update the policy files from the master", perms => u_p("600"), copy_from => local_cp("$(master_location)","localhost"), depth_search => recurse("inf"); }
WHY? The comprehensibility of your code to consultants and new employees is enhanced by standardization of practice. If the global cfengine community uses the same set of idioms, then communicating policy will be simpler.
CFEngine provides indirection (pointers) to particular resources,
through its ‘sys’ variable context. These variables adapt
to the operating system and user id under which cfengine is run.
You policy will be more readily portable and you will need to
code fewer exceptions if you use cfengine's automatically adapting
primitives, e.g. instead of writing /etc/resolv.conf for the
name-service configuration file, use $(sys.resolv)
.
files: "$(sys.resolv)" comment => "Add lines to the resolver configuration", create => "true", edit_line => resolver, edit_defaults => std_edits;
You should avoid coding paths and names of resources directly in promises. Use instead a local or possible global variable to point to the resource instead. This brings consistency to the coding, often shortens the references, and provides a single point of definition for change.
bundle agent update { vars: # A standard location for the source point (single point of definition) "master_location" string => "$(sys.workdir)/masterfiles"; files: "$(sys.workdir)/inputs" comment => "Update the policy files from the master", perms => u_p("600"), copy_from => u_cp("$(master_location)","localhost"), depth_search => recurse("inf"); }
This chapter concerns `workflow processes' that should typically be dealt with on systems. A workflow process is represented by a promise bundle in cfengine. None of the proposals here should be considered mandatory in any sense, but the do represent the norm.
We refer users to the cfengine solutions guide for implementation details of specific solutions.
Purpose:
The purpose of anomaly monitoring is to understand the stability of a system, both in terms of its run-time performance and its architectural structure. Sudden changes on a system can be separated from the normal slow variations.
Remarks:
Anomaly detection is enabled and performed by the cf-monitord
daemon.
Reporting of anomalies is not automatic however. Alerts must be promised
explicitly. This is normally handled by a reports
promise.
Change detection of the file system is handled by files
promises in cf-agent
.
Example:
bundle agent anomalies { vars: "sysdir" string => "/tmp"; "files" slist => { "passwd", "shadow" }; classes: "no_$(files)" not => fileexists("$(sysdir)/$(files)"); files: # backup "/var/cfengine/inputs/$(files)" copy_from => emergency_save("$(sysdir)/$(files)"); # restore "/tmp/$(files)" copy_from => emergency_save("/var/cfengine/inputs/$(files)"), ifvarclass => "no_$(files)"; reports: rootprocs_high_dev2:: "RootProc anomaly high 2 dev on $(mon.host) at $(mon.env_time) measured value $(mon.value_rootprocs) av $(mon.average_rootprocs) pm $(mon.stddev_rootprocs)" showstate => { "rootprocs" }; entropy_www_in_high&anomaly_hosts.www_in_high_anomaly:: "HIGH ENTROPY Incoming www anomaly high anomaly dev!! on $(mon.host) - measured value $(mon.value_www_in) av $(mon.average_www_in) pm $(mon.stddev_www_in)" showstate => { "incoming.www" }; entropy_www_in_low.anomaly_hosts.www_in_high_anomaly:: "LOW ENTROPY Incoming www anomaly high anomaly dev!! on $(mon.host) at $(mon.env_time) - measured value $(svalue_www_in) av $(average_www_in) pm $(stddev_www_in)" showstate => { "incoming.www" }; # etc. }
Purpose:
Batch jobs are run on systems in order to perform basic house keeping functions such as updating databases or executing business related tasks.
Remarks:
Batch jobs should not be run every time cfengine runs, so you need to limit the execution of each one carefully, using:
ifelapsed
parameter determined how much time has to have elapsed
before the job can be executed again.
Example:
bundle agent example { commands: # Exec on the first quarter after noon on Mondays Hr12.Q1.Monday:: "/path/myscript -arg1 -arg2"; # Exec every second quarter past hour, every day Q2:: "/path/otherscript"; }
Purpose:
Garbage collection is require on systems to prevent temporary or antiquated files from consuming all available storage resources. It is impossible for a system to survive in the long term without throwing some data away.
Remarks:
Needless to say, care should be exercised when deleting anything from the system.
There are many strategies to select carefully what is to be deleted.
The file_select
constraint is your friend.
Example:
bundle agent garbage_collection { files: "$(sys.workdir)/outputs" comment => "Garbage collection of any output files", delete => tidy, file_select => days_old("3"), depth_search => recurse("inf"); "/tmp" comment => "Garbage collection of any temporary files", delete => tidy, file_select => days_old("3"), depth_search => recurse("inf"); }
Purpose: Remarks: Example:
Purpose: Every computer needs to know how to perform name directory lookups in the Domain Name Service. On Unix systems this requires it to manage the /etc/resolv.conf file.
Remarks:
Always use the $(sys.resolve)
variable to refer to the file.
Example:
bundle agent name_resolution { files: "$(sys.resolv)" # test on "/tmp/resolv.conf" # comment => "Add lines to the resolver configuration", create => "true", edit_line => resolver, edit_defaults => std_edits; } bundle edit_line resolver { delete_lines: "search.*"; "nameserver 80.65.58.31"; insert_lines: "search cfengine.com" location => start; "nameserver 212.112.166.18"; "nameserver 212.112.166.22"; }
Purpose:
In a centralized model of policy suggestion, policy updates are downloaded from a single point of definition, from one or more policy servers. Maintaining this flow of communication from `central command' is what maintains that centralized command.
Remarks: It is not mandatory to centralize management, but usually there needs to be some automated process.
Example:
vars: "master_location" string => "/var/cfengine/masterfiles"; "policy_server" slist => { "62.109.39.150" }, comment => "IP address to locate your policy host."; files: "/var/cfengine/inputs" handle => "update_policy", perms => system("600"), copy_from => u_scp("$(master_location)",@(policy_server)), depth_search => recurse("inf"), file_select => input_files, action => immediate;
Purpose: Keeping services up and running, or taking down services that should not be running is both a matter of productivity and security.
Remarks: Example:
bundle agent services { vars: "serlist" slist => { "dhcp", "ntp", "sshd" }; "sindex" int => readstringarray ( "service", "$(g.workdir)/inputs/fixservices-array", "#[^\n]*", ":", "10", "4000" ); methods: "any" usebundle => fixservice ( "$(service[$(serlist)][0])", "$(service[$(serlist)][1])", "$(service[$(serlist)][2])", "$(service[$(serlist)][3])", "$(service[$(serlist)][4])" ); } bundle agent fixservice(service,tfiles,mfiles,procs,restart) { files: "$(tfiles)" perms => system("0600","root","root"), copy_from => mycopy("$(g.masterfiles)/config/$(mfiles)","$(g.phost)"), classes => cdefine( "$(service)_restart", "failed"); processes: "$(procs)" restart_class => canonify("$(service)_restart"); commands: "$(restart)" ifvarclass => canonify("$(service)_restart"); }
Purpose: Security is a vast topic. You need to start with a security policy and then translate this into promises about the system. For instance you might promise file permissions and access rules. You might promise change monitoring or anomaly detection.
Remarks: This is an open ended topic. Security should be discussed as a human process, since most breaches come from within the system. CFEngine can then be used to implement hardening measures, and monitoring of important assets.
Example:
vars: "system_files" slist => { "/etc/passwd", "/etc/group", "/etc/services" }; "secret_files" slist => { "/etc/shadow" }; files: "$(secret_files)" comment => "Check permissions are secret on the above", perms => mo("o-rwx","root"); "$(system_files)" comment => "Check permissions are secret on the above", perms => mo("644","root");
Purpose: Installing software and updating
These days most systems have some kind of package based management system. These vary in their intelligence from self-updating robots to simple dumb file repositories. CFEngine can manage the installation and subsequent customization/configuration.
Remarks: Installing software from some kind of source is only the first step. Thereafter, special settings must be harmonized with security policies and operational requirements.
Example:
vars: "match_package" slist => { "apache2", "apache2-mod_php5", "apache2-prefork", "php5" }; packages: "$(match_package)" package_policy => "add", package_method => yum, classes => ok("software_ok");
A powerful tool like cfengine can do great good, or cause enormous damage if used carelessly. It is essential to have a strict discipline when making changes. This is a human quality assurance process.
Your general rule of thumb should be: make small changes, not big releases.
Changes to policy should always be part of a serious and considered plan. They should not be ad hoc. That said, consideration of changes should not be so time-consuming that it cripples human resources, or leads to change-avoidance because it seems daunting.
It is better to make many small changes than few large changes. Large changes involve many interdependencies, which make them fragile to unexpected contingencies. The risk of large changes is high. The risk of small changes is low.
CFEngine makes it easy to make small changes frequently, without operational repercussions. As long as humans are on hand during the change to observe possible side-effects this.
Consider the following issues in quality assurance:
There are four commonly cited phases in managing systems, summarized as follows (see figure):
These separate phases originate with a model of system management based on transactional changes. CFEngine's conception of management is some different, as transaction processing is not a good model for system management, but we can use this template to see how cfengine works differently.
In cfengine, what you build is a template of proposed promises for the
machines in an organization such that, if the machines all make and
keep these promises, the system will function seamlessly as
planned. This is how it works in a human organization, and this is how
is works for computers too.
ROLL-OUT and ROLL-BACK? You should not think of cfengine with a roll-out system, i.e. one that attempts to force out absolute changes and perhaps reverse them in case of error. Roll-out and roll-back are theoretically flawed concepts that only sometimes work in practice. With cfengine, you publish a sequences of policy revisions, always moving forward (because like it or not, time only goes in one direction). All of the desired-state changes are managed locally by each individual computer, and continuously repaired to ensure on-going compliance with policy. |
CFEngine does not make many absolute choices. Almost everything about its behaviour is matter of policy and can be changed. However, a structure for use, like the following, is recommended (see figure).
In order to keep operations as simple as possible, cfengine maintains
a private working directory on each machine referred to in
documentation as WORKDIR and in policy by the variable
$(sys.workdir)
. By default, this is located at
/var/cfengine or C:\var\cfengine. It contains everything
cfengine needs to run.
The figure below shows how decisions flow through the parts of a system.
In this introduction, we shall assume that there is only one central
policy distribution server, a specially-appointed server which is
referred to simple as the policy server
.
Once a client machine has a copy of the policy, it extracts only those promise proposals that are relevant to it, and implements any changes without human assistance. This is how cfengine manages change.
WHY DO THIS? CFEngine tries to minimize dependencies by decoupling processes. By following this pull-based architecture, cfengine will tolerate network outages and will recover from deployment errors easily. By placing the burden of responsibility for decision at the top, and for implementation at the bottom, we avoid needless fragility and keep two independent quality assurance processes apart. |
CFEngine does not provide specific tools for versioning promise specifications. It is recommended to use a tool such as subversion for this. CFEngine does allow you to track changes and keep versions of non-trivial changes, such as file content changes.
Subversion maintains revision numbers on files. It is useful to be able to refer to version names or numbers also in cfengine. A version string can be added to files as follows:
body common control { version => 1.2.3 }
This defines the version number of a set of configuration files which is referred to in reference messages from cfengine.
When cfengine saves a current version of a file that it is modifying
or replacing, by default such files are given a new extension and
remain within the same directory which they were
encountered. Alternatively, one can specify a repository directory to
which such files can be moved instead. The repository location is
specified in the control
section:
body agent control { default_respository => "/var/cfengine/repository"; }
Files moved to the repository are given names reflecting their full path, with slashes replaced by underscore characters. For some, this creates a clearer overview of the changes that have occurred.
In a large organization, you delegate responsibility for different issues to different teams. CFEngine has no meta-access control mechanism which can decide who may write policy rules on what issue. To create such a mechanism, there would have to be a monitor which could identify users, and an authority mechanism that would disallow certain users to write rules of certain types about certain objects on certain hosts. Although it is possible to create such a system, it would be both technically difficult, very cumbersome to use and would add a whole new level of complexity to policy and potential error to the configuration process.
To keep matters as simple as possible, we avoid this and propose a different approach. Promise theory (cfengine's basis) reveals a straightforward answer to model the security implications of this (see the figure of the bow-tie structure). A simple method of delegating is the following.
A review procedure for policy promises is a good solution if you want to delegate responsibility for different parts of a policy to different sources. Human judgement is irreplaceable, and tools can be added to make conflicts easier to detect.
Promise theory underlines that, if a host of computing device accepts policy from any source, then it is alone and entirely responsible for this decision. The ultimate responsibility for the published version policy is the vetting agent. This creates a shallow hierarchy, but there is no reason why this formal body could not be comprised of representatives from the multiple teams.
Run several cfengines? Another way to delegate cfengine control for users that only require limited privileges would be to run several agents as non-root users. This only works however if the tasks delegated are very self-contained and require no special privilege. |
Table of Contents
common
bundles?
common
bundles?