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.
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
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
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
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 ]
[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
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
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
[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%'
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
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'
Now that client and server are installed and communicate correctly, we are going to really get some job done with cfengine.
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.
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" }; }
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"; }
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"; } ###############################################################################
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
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 !
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