As stated in part 1 of this tutorial series, normal client-side operations of CFEngine is for cf-agent to:
We stated that we dont want to break failsafe.cf or update.cf. When we write new CFEngine policies to implement, we import and call them from promises.cf. If we make a mistake and break the syntax, failsafe.cf / update.cf are still in pristine state. It will allow the clients to self-recover from the config breakage once we make the change through SVN.
Lets take a look at failsafe.cf
$ cat -n /var/cfengine/inputs/failsafe.cf
1 # failsafe.cf
2
3 # Whatever you do, DO NOT MODIFY THIS FILE. If you do, you can break the whole
4 # CFEngine infrastructure as failsafe.cf and update.cf is the "failsafe" to restore things back into a working
5 # state. With a broken update.cf or failsafe.cf, you will be touching boxes manually
6 # to recover. If you break CFEngine, may shame be brought upon you and your offspring for generations.
7 # You _should_ be modifying promises.cf to add additional bundlesequences and input files to extend CFEngine.
8
9 body common control
10 {
11 bundlesequence => { "update" };
12 inputs => { "update.cf" };
13 }
14
15 ############################################
16 bundle common g
17 {
18 # Define Master Policy Servers
19 vars:
20 "phost" string => "192.168.1.10";
21 }
22
23 ############################################
24
25 body depth_search recurse(d)
26
27 {
28 depth => "$(d)";
29 }
Nice. Breaking this down line-by-line again.
So, this is all pretty straightforward stuff. Lets see the interesting bits in update.cf.
$ cat -n /var/cfengine/inputs/update.cf
1 # update.cf
2
3 bundle agent update
4 {
5 vars:
6
7 "master_location" string => "/var/cfengine/masterfiles/client_inputs";
8 "master_modules" string => "/var/cfengine/masterfiles/client_modules";
9
10 files:
11 # /var/cfengine should remain 0700. Nobody but the root user should be poking around in here
12 "/var/cfengine/"
13 perms => m_u_g("0700","root","root"),
14 depth_search => avoid_inputs_recurse("inf"),
15 action => immediate;
16
17 # Update the config files from the policy master servers
18 "/var/cfengine/inputs"
19 perms => m_u_g("0600","root","root"),
20 copy_from => remote_copy("$(master_location)","$(g.phost)"),
21 depth_search => recurse("inf"),
22 action => immediate;
23
24 # Update the modules from the policy master servers
25 "/var/cfengine/modules"
26 perms => m_u_g("0700","root","root"),
27 copy_from => remote_copy("$(master_modules)","$(g.phost)"),
28 depth_search => recurse("inf"),
29 action => immediate;
30
31 # Update the binaries from the sbin directory
32 "/var/cfengine/bin"
33 perms => m_u_g("0700","root","root"),
34 copy_from => mycopy("/var/cfengine/sbin","localhost"),
35 depth_search => recurse("inf"),
36 action => immediate;
37 }
38 ############################################
39 body perms m_u_g(m,u,g)
40 {
41 mode => "$(m)";
42 owners => { "$(u)" };
43 groups => { "$(g)" };
44 }
45 #########################################################
46 body copy_from mycopy(from,server)
47 {
48 source => "$(from)";
49 compare => "digest";
50 purge => "true";
51 }
52
53 #########################################################
54 body action immediate
55 {
56 ifelapsed => "1";
57 }
58
59 #########################################################
60 body copy_from remote_copy(sourcedir,sourceserver)
61 {
62 source => "$(sourcedir)";
63 servers => { "$(sourceserver)" };
64 copy_backup => "true";
65 purge => "true";
66 trustkey => "true";
67 compare => "digest";
68 encrypt => "true";
69 verify => "true";
70 }
71 body depth_search avoid_inputs_recurse(d)
72 {
73 depth => "$(d)";
74 exclude_dirs => { "/var/cfengine/inputs", "/var/cfengine/state" };
75 }
Finally. Some really interesting CFengine stuff to talk about. Lots of things are happening in those 75 lines. Lets break it down line by line again.
That’s it! We’ve looked at our first complex CFEngine policy and saw how cf-agent is going to behave. This will be used to download new policies / auto recover in the case of a broken promises.cf policy.
One more note: Before we run this for the first time to “phone home” to the master policy servers, we need to generate a public / private key for this client machine. This works exactly like SSH public key authencation. On first contact of the client to the server, the server saves a copy of the public key from the client. Every attempt from the client to the server hence forward authenticates using this key. Data is encrypted between cf-serverd and cf-agent using this key. The client also saves a copy of the master policy server’s keys.
This key only needs to be generated once in the client’s lifetime. To generate the key (and I execute this from a postinstall package script) execute the cf-key binary.
$ /var/cfengine/bin/cf-key
Making a key pair for cfengine, please wait, this could take a minute...
$ls -l /var/cfengine/ppkeys/localhost.p*
-rw------- 1 root root 1743 Jul 2 12:33 /var/cfengine/ppkeys/localhost.priv
-rw------- 1 root root 426 Jul 2 12:33 /var/cfengine/ppkeys/localhost.pub
$ cat /var/cfengine/ppkeys/localhost.pub
-----BEGIN RSA PUBLIC KEY-----
MIIBCAKCAQEA57cGTBfsqTwfuawgyO9K9tLt7IOvns7lAku/8XcyUkJ0AY0AATVK
TVjI7E1HT/moTvvLo+t6QuCD6Eo3+K++OaeP4pmSXhcGRFhuK4IVSLjfuDtYfwmn
Kd730gP2KONQZiIiVkQfsd1ADMTxTtldv/UR1COG49wexKA3f13iBNEj7d6YehHy
PFabbFpjcGmelg5yu0nDopUrGGg402BLAc8Z9H/7QxrzktH9uVrFuLitGE8reyJQ
2A8wQErRgtgpVBC2M1NFo4bWIk6mkLCukF6EOuUxzEgUjcToCc8p5sr5j2kpj+Vi
n5m16pcjkQo+EX+t7wbnRFy1PK0d98SrfwIBIw==
-----END RSA PUBLIC KEY-----
Keys are saved in /var/cfengine/ppkeys. If you rebuild a client, or regenerate keys, you’ll need to remove the old public key entry on the master policy servers. Since clients also cache the public key of the master policy servers – if the server is rebuilt or keys regenerated then this old key will need to be removed from all the clients so they can re-cache the new key. In short: treat these keys like you would with SSH keys. If you loose / damage a private key, it could be a PITA the recover from.
One last note: lets execute failsafe.cf and watch corrections take place. Note, we do not specify the absolute path in -f because by default, cf-agent will look in /var/cfengine/inputs (where failsafe.cf and update.cf live). We execute in –inform mode using -I so we see the good changes that cf-agent makes and -K so we aren’t held on locks.
/var/cfengine $ touch mike /var/cfengine $ chmod 777 mike /var/cfengine $ /var/cfengine/bin/cf-agent -f failsafe.cf -I -K -> Object /var/cfengine/mike had permission 777, changed it to 700
/var/cfengine $ rm /var/cfengine/inputs/promises.cf /var/cfengine $ echo ‘this is a garbage statement’ > /var/cfengine/inputs/promises.cf /var/cfengine $ /var/cfengine/bin/cf-agent -f failsafe.cf -I -K -> Updated /var/cfengine/inputs/promises.cf from source /var/cfengine/masterfiles/client_inputs/promises.cf on 192.168.1.10