cfengine3

references

install server

required package

cfegine is written in C and is quite independant, but it needs at least these three packages

[root@cfengine3 ~]# yum install db4 pcre openssl 
[root@cfengine3 ~]# rpm -ivh cfengine-community-3.5.1-1.i386.rpm
Preparing...                ########################################### [100%]
   1:cfengine-community     ########################################### [100%]
Policy is not found in /var/cfengine/inputs, not starting CFEngine.

server keys

after the above installed package, the server keys have been created

[root@cfengine3 ~]# ls -l /var/cfengine/ppkeys/
total 8
-rw------- 1 root root 1743 Jul 10 17:06 localhost.priv
-rw------- 1 root root  426 Jul 10 17:06 localhost.pub

directory tree

initial directory tree /var/cfengine

[root@cfengine3 cfengine]# ls -l
total 48
drwxr-xr-x 2 root root 4096 Jul 10 17:06 bin
drwx------ 2 root root 4096 Jun 12 16:43 inputs
drwxr-xr-x 2 root root 4096 Jul 10 17:06 lastseen
drwxr-xr-x 2 root root 4096 Jul 10 17:06 lib
drwxr-xr-x 5 root root 4096 Jul 10 17:06 masterfiles
drwx------ 2 root root 4096 Jun 12 16:43 modules
drwx------ 2 root root 4096 Jun 12 16:43 outputs
drwx------ 2 root root 4096 Jul 10 17:06 ppkeys
-rw-r--r-- 1 root root 1024 Jul 10 17:06 randseed
drwxr-xr-x 2 root root 4096 Jul 10 17:06 reports
drwxr-xr-x 4 root root 4096 Jul 10 17:06 share
drwxr-xr-x 2 root root 4096 Jul 10 17:06 state

bootstrap server

our cfengine3 server (cfhub) is the policy server, we bootstrap it with it's own IP address in order to let him know that he is the policyserver

[root@cfengine3 masterfiles]# /var/cfengine/bin/cf-agent --bootstrap --policy-server 157.157.211.144
2013-07-10T18:15:14+0200  warning: Deprecated bootstrap options detected. The --policy-server (-s) option is deprecated from CFEngine community version 3.5.1.Please provide the address argument to --bootstrap (-B) instead. Rewriting your arguments now, but you need to adjust them as this support will be removed soon.
2013-07-10T18:15:15+0200   notice: Q: "...f-serverd"": 2013-07-10T18:15:15+0200   notice: Server is starting...

2013-07-10T18:15:15+0200   notice: R: This host assumes the role of policy server
2013-07-10T18:15:15+0200   notice: R: Updated local policy from policy server
2013-07-10T18:15:15+0200   notice: R: Started the server
2013-07-10T18:15:15+0200   notice: R: Started the scheduler
2013-07-10T18:15:15+0200   notice: Bootstrap to '157.157.211.144' completed successfully!
[root@cfengine3 masterfiles]# ps auwx | grep cf
root     16953  0.0  0.8   5824  2100 ?        Ss   18:15   0:00 /var/cfengine/bin/cf-execd
root     16958  0.0  0.6   5948  1804 ?        Ss   18:15   0:00 /var/cfengine/bin/cf-serverd

install client

here we install manually cfengine package on the client (next we'll do it automatically via cobbler)

[root@b01-02 ~]# rpm -ivh cfengine-community-3.5.1-1.x86_64.rpm
Préparation...                       ################################# [100%]
Updating / installing...
   1:cfengine-community-3.5.1-1       ################################# [100%]
Starting cfengine3 (via systemctl):                        [  OK  ]

bootstrap client

[root@b01-02 ~]# /var/cfengine/bin/cf-agent -B --policy-server 157.157.211.144
2013-07-10T18:29:19+0200  warning: Deprecated bootstrap options detected. The --policy-server (-s) option is deprecated from CFEngine community version 3.5.1.Please provide the address argument to --bootstrap (-B) instead. Rewriting your arguments now, but you need to adjust them as this support will be removed soon.
2013-07-10T18:29:20+0200   notice: R: This autonomous node assumes the role of voluntary client
2013-07-10T18:29:20+0200   notice: R: Updated local policy from policy server
2013-07-10T18:29:20+0200   notice: R: Started the scheduler
2013-07-10T18:29:20+0200   notice: Bootstrap to '157.159.211.144' completed successfully!

Insteresting note from http://blog.normation.com/en/2012/01/03/interactive-key-exchange-with-cfengine/

Note 2: Since the version 3.2.0, if you are willing to automatically accept keys from the clients on the servers, you don’t need to copy any promises on the client, the bootstrap procedure from the Nova edition has been backported in the community edition; and it uses its own embedded promises

ppkeys

when clients bootstraps, their key is addeed to the ppkeys directory

[root@cfengine3 cfengine]# ls -ltr ppkeys/
total 16
-rw------- 2 root root  426 Jul 10 17:06 root-MD5=ba9cb6e2a6831b0a90e5ba594ae51041.pub
-rw------- 2 root root  426 Jul 10 17:06 localhost.pub
-rw------- 1 root root 1743 Jul 10 17:06 localhost.priv
-rw------- 1 root root  426 Jul 10 18:29 root-MD5=681e8f847ead1de58b1fe2ac0d01d8fe.pub

md5-keys <=> IP

association between keys and clients IP

[root@cfengine3 ppkeys]# cf-key -s
Direction  IP                Name                      Last connection            Key
Outgoing   157.157.211.144     cfengine.int-evry.fr     Wed Jul 10 18:15:14 2013   MD5=ba9cb6e2a6831b0a90e5ba594ae51041
Incoming   157.157.211.211    b01-02.int-evry.fr        Fri Jul 19 10:45:42 2013   MD5=681e8f847ead1de58b1fe2ac0d01d8fe
Incoming   157.157.51.196    arve.int-evry.fr          Wed Jul 17 18:55:06 2013   MD5=ab4d34e310a353f5ebda0505491135b2
Total Entries: 3

1st start on client

[root@b01-02 inputs]# /var/cfengine/bin/cf-agent -Kv
2013-07-10T19:01:15+0200  verbose: Work directory is /var/cfengine
2013-07-10T19:01:15+0200  verbose: Looking for a source of entropy in '/var/cfengine/randseed'
2013-07-10T19:01:15+0200  verbose: Making sure that locks are private...
2013-07-10T19:01:15+0200  verbose: Checking integrity of the state database
2013-07-10T19:01:15+0200  verbose: Checking integrity of the module directory
2013-07-10T19:01:15+0200  verbose: Checking integrity of the PKI directory
2013-07-10T19:01:15+0200  verbose: Loaded private key at '/var/cfengine/ppkeys/localhost.priv'
2013-07-10T19:01:15+0200  verbose: Loaded public key '/var/cfengine/ppkeys/localhost.pub'
2013-07-10T19:01:15+0200  verbose: Setting cfengine default port to 5308, '5308'
2013-07-10T19:01:15+0200  verbose: Reference time set to 'Wed Jul 10 19:01:15 2013'
2013-07-10T19:01:15+0200  verbose: CFEngine Core 3.5.1
2013-07-10T19:01:15+0200  verbose: Host name is: b01-02.int-evry.fr
...
2013-07-10T19:01:15+0200  verbose: This agent is bootstrapped to '157.157.211.144'
2013-07-10T19:01:15+0200  verbose: Input file is outside default repository, validating it
2013-07-10T19:01:15+0200  verbose: Promises seem to change
2013-07-10T19:01:15+0200  verbose: Input file is changed since last validation, validating it
2013-07-10T19:01:15+0200  verbose: Verifying the syntax of the inputs...
2013-07-10T19:01:15+0200  verbose: Checking policy with command '"/var/cfengine/bin/cf-promises" -c "/var/cfengine/inputs/promises.cf"'
2013-07-10T19:01:15+0200  verbose: Parsing file '/var/cfengine/inputs/promises.cf'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'main'
2013-07-10T19:01:15+0200  verbose: Parsing file '/var/cfengine/inputs/def.cf'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'def'
2013-07-10T19:01:15+0200  verbose: Parsing file '/var/cfengine/inputs/controls/cf_agent.cf'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'def'
2013-07-10T19:01:15+0200  verbose: Parsing file '/var/cfengine/inputs/controls/cf_execd.cf'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'def'
2013-07-10T19:01:15+0200  verbose: Parsing file '/var/cfengine/inputs/controls/cf_monitord.cf'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'def'
2013-07-10T19:01:15+0200  verbose: Parsing file '/var/cfengine/inputs/controls/cf_runagent.cf'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'def'
2013-07-10T19:01:15+0200  verbose: Parsing file '/var/cfengine/inputs/controls/cf_serverd.cf'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'def'
2013-07-10T19:01:15+0200  verbose: Parsing file '/var/cfengine/inputs/libraries/cfengine_stdlib.cf'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'def'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'debian_knowledge'
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'paths'
2013-07-10T19:01:15+0200  verbose: Checking common class promises...
2013-07-10T19:01:15+0200  verbose: Class identifier '_stdlib_has_path_$(all_paths)' contains illegal characters - canonifying
2013-07-10T19:01:15+0200  verbose: Class identifier '_stdlib_path_exists_$(all_paths)' contains illegal characters - canonifying
2013-07-10T19:01:15+0200  verbose: Resolving variables in bundle 'insert_lines'
...
2013-07-10T19:01:16+0200  verbose: Running full policy integrity checks
2013-07-10T19:01:16+0200  verbose: Discovered hard classes: 127_0_0_1 157_157_211_211 2001_660_3203_210_be30_5bff_febf_4089 2_cpus 64_bit Day10 Evening GMT_Hr17 Hr19 Hr19_Q1 July Lcycle_0 Min00_05 Min01 PK_MD5_681e8f847ead1de58b1fe2ac0d01d8fe Q1 Wednesday Yr2013 agent any b01_02 b01_02_int_evry_fr cfengine cfengine_3 cfengine_3_5 cfengine_3_5_1 cfengine_in_high cfengine_out_high community_edition compiled_on_linux_gnu cpu0_high cpu1_high cpu_high diskfree_high_normal entropy_misc_in_low entropy_misc_out_low entropy_postgresql_in_low entropy_postgresql_out_low fe80__be30_5bff_febf_4089 fedora fedora_19 fr inform_mode int_evry_fr ipp_in_high ipv4_127 ipv4_127_0 ipv4_127_0_0 ipv4_127_0_0_1 ipv4_157 ipv4_157_157 ipv4_157_157_211 ipv4_157_159_21_211 linux linux_3_9_5_301_fc19_x86_64 linux_x86_64 linux_x86_64_3_9_5_301_fc19_x86_64 linux_x86_64_3_9_5_301_fc19_x86_64__1_SMP_Tue_Jun_11_19_39_38_UTC_2013 loadavg_high localhost localhost4 localhost4_localdomain4 localhost_localdomain mac_bc_30_5b_bf_40_89 net_iface_em1 net_iface_lo otherprocs_high_normal redhat rootprocs_high_normal smtp_in_high smtp_out_high ssh_in_high ssh_out_high users_high verbose_mode x86_64
2013-07-10T19:01:16+0200  verbose: Additional classes: _stdlib_path_exists_awk _stdlib_path_exists_bc _stdlib_path_exists_cat _stdlib_path_exists_chkconfig _stdlib_path_exists_cksum _stdlib_path_exists_crontab _stdlib_path_exists_crontabs _stdlib_path_exists_cut _stdlib_path_exists_dc _stdlib_path_exists_df _stdlib_path_exists_diff _stdlib_path_exists_dig _stdlib_path_exists_domainname _stdlib_path_exists_echo _stdlib_path_exists_egrep _stdlib_path_exists_find _stdlib_path_exists_getfacl _stdlib_path_exists_grep _stdlib_path_exists_groupadd _stdlib_path_exists_groupdel _stdlib_path_exists_hostname _stdlib_path_exists_ifconfig _stdlib_path_exists_init _stdlib_path_exists_ip _stdlib_path_exists_iptables _stdlib_path_exists_iptables_save _stdlib_path_exists_ls _stdlib_path_exists_netstat _stdlib_path_exists_perl _stdlib_path_exists_ping _stdlib_path_exists_printf _stdlib_path_exists_rpm _stdlib_path_exists_sed _stdlib_path_exists_service _stdlib_path_exists_sort _stdlib_path_exists_svc _stdlib_path_exists_sysctl _stdlib_path_exists_test _stdlib_path_exists_tr _stdlib_path_exists_useradd _stdlib_path_exists_userdel _stdlib_path_exists_yum measurements:cpu1_high_ldt
2013-07-10T19:01:16+0200  verbose: Resolving variables in bundle 'def'
...
2013-07-10T19:01:16+0200  verbose: Skipping next promise '--> I'm a policy hub.', as context 'am_policy_hub' is not relevant
...
2013-07-10T19:01:16+0200  verbose: /main/methods/'INIT MSG'/init_msg/reports/'--> $(sys.policy_hub) is my policy hub.': Report: --> 157.157.211.144 is my policy hub.
2013-07-10T19:01:16+0200   notice: R: --> 157.157.211.144 is my policy hub.
2013-07-10T19:01:16+0200  verbose: Comment 'Display message on screen/email'
2013-07-10T19:01:16+0200  verbose: /main/methods/'INIT MSG'/init_msg/reports/'--> CFEngine is running on $(sys.fqhost)': Additional promise info: handle 'init_msg_reports_cfe_running' version 'Community Promises.cf 3.4.0' source path '/var/cfengine/inputs/services/init_msg.cf' at line 14 comment 'Display message on screen/email'
2013-07-10T19:01:16+0200  verbose: /main/methods/'INIT MSG'/init_msg/reports/'--> CFEngine is running on $(sys.fqhost)': Report: --> CFEngine is running on b01-02.int-evry.fr
2013-07-10T19:01:16+0200   notice: R: --> CFEngine is running on b01-02.int-evry.fr
2013-07-10T19:01:16+0200  verbose: Skipping next promise '--> I'm a policy hub.', as context 'am_policy_hub' is not relevant
2013-07-10T19:01:16+0200  verbose: Skipping next promise '--> I'm a policy hub.', as context 'am_policy_hub' is not relevant
...
2013-07-10T19:01:17+0200  verbose: No lock purging scheduled
2013-07-10T19:01:17+0200  verbose: Logging total compliance, total 'Outcome of version Community Promises.cf 3.4.0 (agent-0): Promises observed to be kept 44%, Promises repaired 56%, Promises not repaired 0%'

info

run the client to inform on policy actions only, here a simple policy that create a file in /tmp/

[root@b01-02 ~]# /var/cfengine/bin/cf-agent -Kv -I 
2013-07-11T11:20:31+0200   notice: R: --> 157.157.211.144 is my policy hub.
2013-07-11T11:20:31+0200   notice: R: --> CFEngine is running on b01-02.int-evry.fr
2013-07-11T11:20:31+0200     info: /test/files/'/tmp/cf_test_file': Created file '/tmp/cf_test_file', mode 0644
2013-07-11T11:20:31+0200     info: /test/files/'/tmp/cf_test_file': Group of '/tmp/cf_test_file' was 0, setting to 3

Server Access control

IP subnets control

in order to allow clients on from specific subnets , declare our subnets in def.cf

 # List here the IP masks that we grant access to on the server
    "acl" slist => {
                # Assume /24 LAN clients to start with
                   "$(sys.policy_hub)/24",

                 #  "2001:700:700:3.*", 
                 #  "217.77.34.18", 
                 #  "217.77.34.19",
                   "157.157.211.0/24",
                   "157.157.51.0/24",
                   },

negative test bootstraping from a client on the wrong subnet:

Jul 17 15:21:53 cfengine3 cf-serverd[8890]: Not allowing connection from non-authorized IP '157.157.61.196'

sample site policy

Now that client and server are installed and communicate correctly, we are going to really get some job done with cfengine.

ldap + nfs context

In that sample site / lab auto configuration we want to allow users to connect to client station through ldap authentication and NFS mount of homedirectories. that involves creating / copying files and restart services upon chnages to these files.

classes

we create a promise file to declare our local classes ⇒ groups of machines (beware that - are replace with _ for hotsname classes association)

[root@cfengine3 masterfiles]# cat cf-disi-classes.cf
bundle common disi_classes
{
classes:

"salle_b09" or => { "b09_01", "b09_02" };
"salle_b01" or => { "b01_01", "b01_02" };
"disi_hosts" or => { "arve", "arvin" };
}

policy

then we translate our needs in terms of cfengine promises in a dedicated promise file for that purpose

[root@cfengine3 masterfiles]# cat cf-disi-policies.cf
bundle agent disi_policies
{
files:

	salle_b01|disi_hosts::

	"/etc/sssd/sssd.conf"
	copy_from => secure_cp("/var/cfengine/master_disi_files/fedora19/sssd.conf","cfengine.int-evry.fr"),
	perms => m("600");

	b01_02::

	"/etc/sssd/sssd.conf"
	copy_from => secure_cp("/var/cfengine/master_disi_files/fedora19/sssd.conf","cfengine.int-evry.fr"),
	perms => m("600");

	salle_b01|disi_hosts::

	"/etc/sysconfig/autofs"
	copy_from => secure_cp("/var/cfengine/master_disi_files/fedora19/autofs","cfengine.int-evry.fr"),
	perms => m("644"),
	classes => if_repaired("autofs_restart");

	"/usr/local/bin/bash"
        link_from => ln_s("/bin/bash");


commands:
	autofs_restart::
		"/usr/bin/systemctl restart autofs.service";


}

bundlesequence

finally we need to tell cfengine server bundlesequence to use our promises above through promises.cf file

[root@cfengine3 masterfiles]# cat promises.cf
###############################################################################
#
#   promises.cf - Basic Policy for Community + DISI inputs
#
###############################################################################
body common control
{
 bundlesequence => {

                 # Common bundles first for best practice 
                    "def",
                 # Design Center
                    "cfsketch_run",
                 # Agent bundles from here
                    "main",

                 # classes / groups de machines
                    "disi_classes",
                 # disi policies
                    "disi_policies",
                   };
 inputs => {
         # Global common bundles
            "def.cf",
         # Control body for all agents
            "controls/cf_agent.cf",
            "controls/cf_execd.cf",
            "controls/cf_monitord.cf",
            "controls/cf_runagent.cf",
            "controls/cf_serverd.cf",
         # COPBL/Custom libraries
            "libraries/cfengine_stdlib.cf",
         # Design Center
             # MARKER FOR CF-SKETCH INPUT INSERTION
             "cf-sketch-runfile.cf",
         # User services from here
            "services/init_msg.cf",
            
         # classes disi
            "cf-disi-classes.cf",
         # disi policies
            "cf-disi-policies.cf",
           };
 version => "Community Promises.cf 3.4.0";
}
###############################################################################
bundle agent main
{
 methods:
  any::

   "INIT MSG" usebundle => init_msg,
                comment => "Just a pre-defined policy bundled with the package",
                 handle => "main_methods_any_init_msg";
}
###############################################################################

directory access rules

note that in cf-disi-policies.cf above we ditribute/copy files from a personnal (disi) added directory , as in

copy_from => secure_cp("/var/cfengine/master_disi_files/fedora19/sssd.conf","cfengine.int-evry.fr"),

/var/cfengine/master_disi_files/ beeing create by ourself, cfengine needs to allow acces to that directory tree from its def.cf global vars:

 # disi masterdir
   "dir_master_disi_files" string => translatepath("$(sys.workdir)/master_disi_files"),
                    comment => "Define master_disi_files path",
                     handle => "common_def_vars_dir_master_disi_files";

Now that “$(sys.workdir)/master_disi_files” is a defined variable, we can use it in the acces rules of cf_served :

[root@cfengine3 masterfiles]# vim controls/cf_serverd.cf 
bundle server access_rules()
{
 access:
  any::
  # disi
   "$(def.dir_master_disi_files)"
       handle => "server_access_rule_grant_access_disi_policy",
      comment => "Grant access to the policy disi updates",
        admit => { ".*\.$(def.domain)", @(def.acl) };

otherwise you get an error on the client telling you

verbose: Server returned error ' Unspecified server refusal (see verbose server output)'
2013-07-11T18:29:19+0200     info: /disi_policies/files/'/etc/sssd/sssd.conf': Can't stat '/var/cfengine/master_disi_files/fedora19/sssd.conf' in files.copyfrom promise

secure_cp

I first used remote_cp instaed of secure_cp to ciopy files, howerver when the file changed on the server it wasn't replaced on the client because it was more recent !

indeed from libraries/cfengine_stdlib.cf we can read the definitions :

body copy_from secure_cp(from,server)
{
source      => "$(from)";
servers     => { "$(server)" };
compare     => "digest";
encrypt     => "true";
verify      => "true";
}

##

body copy_from remote_cp(from,server)
{
servers     => { "$(server)" };
source      => "$(from)";
compare     => "mtime";
}

and from https://cfengine.com/docs/3.5/reference-promise-types-files.html reference we learn that what we needed was a compare based on digest and not mtime in our case !

bootstrap cfengine from cobbler

we use cobbler to install +100 stations (fedora) via PXEboot + kickstart. in order to fully automate the install + configuration of those stations we need to tell cobbler in its post install process to install cfengine and bootstrap the client station on the cfengine server

reference

in our kickstart template we call 2 snippets to do the job

[root@cobbler2 cobbler]# grep disi kickstarts/basef19.ks
$SNIPPET('disi_post_install_packages')
$SNIPPET('disi_cfengine_bootstrap')

those snippet repectlively install cfengine and then bootstrap the client the easy way since the version 3.2.0, if you are willing to automatically accept keys from the clients , cf http://blog.normation.com/en/2012/01/03/interactive-key-exchange-with-cfengine/

[root@cobbler2 snippets]# cat disi_post_install_packages
yum -y install cfengine-community
yum -y install autofs

[root@cobbler2 snippets]# cat disi_cfengine_bootstrap
# start cfengine3 registration 
/var/cfengine/bin/cf-agent -B --policy-server 157.157.211.144 > /root/disi_cfengine3_bootstraped.txt
# end cfengine3 registration