1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
|
% System Operations using Cosmos & Puppet
% Leif Johansson / SUNET / 2013 / v0.0.3
Introduction
============
This document describes how to setup and run systems and service operations for a small to midsized
systems collection while maintaining scalability, security and auditability for changes.
The process described below is based on opensource components and assumes a Linux-based hosting
infrastructure. These limitations could easily be removed though. This document describes the
multiverse template for combining cosmos and puppet.
Design Requirements
===================
The cosmos system has been used to operate security-critical infrastructure for a few years before
it was combined with puppet into the multiverse template.
Several of the design requirements below are fulfilled by comos alone, while some (eg consistency)
are easier to achieve using puppet than with cosmos alone.
Consistency
-----------
Changes should be applied atomically (locally on each host) across multiple system components on multiple
physical and logical hosts (aka system state). The change mechanism should permit verification of state
consistency and all modifications should be idempotents, i.e the same operation
performend twice on the same system state should not in itself cause a problem.
Auditability
------------
It must be possible to review changes in advance of applying them to system state. It
must also be possible to trace changes that have already been applied to privileged
system operators.
Authenticity
------------
All changes must be authenticated by private keys in the personal possession of privileged
system operators before applied to system state aswell as at any point in the future.
Simplicity
----------
The system must be simple and must not rely on external services to be online to maintain
state except when new state is being requested and applied. When new state is being requested
external dependencies must be kept to a minimum.
Architecture
============
The basic architecture of puppet is to use a VCS (git) to manage and distribute changes to a
staging area on each managed host. At the staging area the changes are authenticated (using
tag signatures) and if valid, distributed to the host using local rsync. Before and after
hooks (using run-parts) are used to provide programmatic hooks.
Administrative Scope
--------------------
The repository constitutes the administrative domain of a multiverse setup: each host is
connected to (i.e runs cosmos off of) a single GIT repository and derives trust from signed
tags on that repository. A host cannot belong to more than 1 administratve domain but each
administrative domains can host multiple DNS domains - all hosts in a single repository
doesn't need to be in the same zone.
The role of Puppet
------------------
In the multiverse template, the cosmos system is used to authenticate and distribute changes
and prepare the system state for running puppet. Puppet is used to apply idempotent changes
to the system state using "puppet apply".
~~~~~ {.ditaa .no-separation}
+------------+ +------+
| cosmo repo |---->| host |-----+
+------------+ +------+ |
^ |
| |
(change) (manifests)
| |
+--------+ |
| puppet |<---+
+--------+
~~~~~
Note that there is no puppet master in this setup so collective resources cannot be used
in multiverse. Instead 'fabric' is used to provide a simple way to loop over subsets of
the hosts in a managed domain.
Private data (eg system credentials, application passwords, or private keys) are encrypted
to a master host-specific PGP key before stored in the cosmos repo.
System state can be tied to classes used to classify systems into roles (eg "database server"
or "webserver"). System classes can be assigned by regular expressions on the fqdn (eg all
hosts named db-\* is assigned to the "database server" class) using a custom puppet ENC.
The system classes are also made available to 'fabric' in a custom fabfile. Fabric (or fab)
is a simple frontend to ssh that allows an operator to run commands on multiple remote
hosts at once.
Trust
-----
All data in the system is maintained in a cosmos GIT repository. A change is
requested by signing a tag in the repository with a system-wide well-known name-prefix.
The tag name typically includes the date and a counter to make it unique.
The signature on the tag is authenticated against a set of trusted keys maintained in the
repository itself - so that one trusted system operator must be present to authenticate addition or
removal of another trusted system operator. This authentication of tags is done in addition
to authenticating access to the GIT repository when the changes are pushed. Trust is typically
bootstrapped when a repository is first established. This model also serves to provide auditability
of all changes for as long as repository history is retained.
Access to hosts is done through ssh with ssh-key access. The ssh keys are typically maintained
using either puppet or cosmos natively.
Consistency
-----------
As a master-less architecture, multiverse relies on _eventual consistency_: changes will eventually
be applied to all hosts. In such a model it becomes very imporant that changes are idempotent, so
that applying a change multiple times (in an effort to get dependent changes through) won't cause
an issue. Using native cosmos, such changes are achived using timestamp-files that control entry
into code-blocks:
```
stamp="${COSMOS\_BASE}/stamps/foo-v04.stamp"
if ! test -f $stamp; then
# do something here
touch $stamp
fi
```
This pattern is mostly replaced in multiverse by using puppet manifests and modules that
are inherently indempotent but it can nevertheless be a useful addition to the toolchain.
Implementation
==============
Implementation is based on two major components: cosmos and puppet. The cosmos system was
created by Simon Josefsson and Fredrik Thulin as a simple and secure way to distribute files
and run pre- and post-processors (using run-parts). This allows for a simple, yet complete
mechanism for updating system state.
The second component is puppet which is run in masterless (aka puppet apply) mode on files
distributed and authenticated using cosmos. Puppet is a widely deployed way to describe
system state using a set of idempotent operations. In theory, anything that can de done
using puppet can be done using cosmos post-processors but puppet allows for greater
abstraction which greatly increases readability.
The combination of puppet and cosmos is maintained on github in the 'leifj/multiverse'
project.
The Cosmos Puppet Module
========================
Although not necessary, a few nice-to-have utilities in the form of puppet modules have
been collected as the cosmos puppet module (for want of a better name). The source for
this module is at http://github.com/leifj/puppet-cosmos and it is included (but commented
out) in the cosmos-modules.conf file (cf below) for easy inclusion.
Operations
==========
Setting up a new administrative domain
--------------------------------------
The simplest way is to clone the multiverse repository. First install 'git'. On ubuntu/debian
this is in the 'git-core' package:
```
# apt-get install git-core
```
Also install 'fabric' - a very useful too for multiple-host-ssh that is integrated into
multiverse. Fabric provides the 'fab' command which will be introduced later on.
```
# apt-get install fabric
```
These two tools (git & fabric) are only needed on mashines where system operators work.
Next clone git://github.com/leifj/multiverse.git - this will form the basis of your cosmos+puppet
repository:
```
# git clone git://github.com/leifj/multiverse.git myproj-cosmos
# cd myproj-cosmos
```
Next rename the upstream from github - you will want to keep this around to get new
features as the multiverse codebase evolves.
```
# git remote rename origin multiverse
```
Now add a new remote pointing to the git repo where you are going to be pushing
changes for your administrative domain. Also add a read-only version of this remote
as 'ro'. The read-only remote is used by multiverse scripts during host bootstrap.
```
# git remote add origin git@yourhost:myproj-cosmos.git
# git remote add ro git://yourhost/myproj-cosmos.git
```
Now edit .git/config and rename the 'master' branch to use the new 'origin' remote or
you'll try to push to the multiverse remote! Finally create a branch for the 'multiverse'
upstream so you can merge changes to multiverse:
```
# git checkout -b multiverse --track multiverse/master
```
Note that you can maintain your repo on just about any git hosting platform, including
github, gitorius or your own local setup as long as it supports read-only "git://" access
to your repository. It is important that the remotes called 'origin' and 'ro' refer to
your repository and not to anything else (like a private version of multiverse).
Now add at least one key to 'global/overlay/etc/cosmos/keys/' in a file with a .pub extension
(eg 'operator.pub') - the name of the file doesn't matter other than the extension.
```
# cp mykey.pub global/overlay/etc/cosmos/keys/
# git add global/overlay/etc/cosmos/keys/mykey.pub
# git commit -m "initial trust" global/overlay/etc/cosmos/keys/mykey.pub
```
At this point you should create and sign your first tag:
```
# ./bump-tag
```
Make sure that you are using the key whose public key you just added to the repository! You
can now start adding hosts.
Adding a host
-------------
Bootstrapping a host is done using the 'addhost' command:
```
# ./addhost [-b] $fqdn
```
The -b flag causes addhost to attempt to bootstrap cosmos on the remote host using
ssh as root. This requires that root key trust be established in advance. The addhost
command creates and commits the necessary changes to the repository to add a host named
$fqdn. Only fully qualified hostnames should ever be used in cosmos+puppet.
The boostrap process will create a cron-job on $fqdn that runs
```
# cosmos update && cosmos apply
```
every 15 minutes. This should be a good starting point for your domain. Now you may
want to add some 'naming rules'.
Defining naming rules
---------------------
A naming rule is a mapping from a name to a set of puppet classes. These are defined in
the file 'global/overlay/etc/puppet/cosmos-rules.yaml' (linked to the toplevel directory
in multiverse). This is a YAML format file whose keys are regular expressions and whose
values are lists of puppet class definitions. Here is an example that assigns all hosts
with names on the form ns\<number\>.example.com to the 'nameserver' class.
```
'ns[0-9]?.example.com$':
nameserver:
```
Note that the value is a hash with an empty value ('namserver:') and not just a string
value.
Since regular expressions can also match on whole strings so the following is also
valid:
```
smtp.example.com:
mailserver:
relay: smtp.upstream.example.com
```
In this example the mailserver puppet class is given the relay argument (cf puppet
documentation).
Fabric integration
------------------
Given the above example the following command would reload all nameservers:
```
# fab --roles=nameservers -- rndc reload
```
Creating a change-request
-------------------------
After performing whatever changes you want to the reqpository, commit the changes as usual
and then sign an appropriately formatted tag. This last operation is wrapped in the 'bump-tag' command:
```
# git commit -m "some changes" global/overlay/somethig or/other/files
# ./bump-tag
```
The bump-tag command will ask for confirmation before signing and will rely on the git and
gpg commands to create, sign and push the correct tag.
Puppet modules
--------------
Puppet modules can be maintained using a designated cosmos pre-task that reads a file
global/overlay/etc/puppet/cosmos-modules.conf. This file is a simple text-format file
with 3 columns:
```
#
# name source (puppetlabs fq name or git url) upgrade (yes/no)
#
concat puppetlabs/concat no
stdlib puppetlabs/stdlib no
cosmos git://github.com/leifj/puppet-cosmos.git yes
ufw git://github.com/fredrikt/puppet-module-ufw.git yes
apt puppetlabs/apt no
vcsrepo puppetlabs/vcsrepo no
xinetd puppetlabs/xinetd no
#golang elithrar/golang yes
python git://github.com/fredrikt/puppet-python.git yes
hiera-gpg git://github.com/fredrikt/hiera-gpg.git no
```
This is an example file - the first field is the name of the module, the second is
the source: either a puppetlabs path or a git URL. The final field is 'yes' if the
module should be automatically updated or 'no' if it should only be installed. As usual
lines beginning with '#' are silently ignored.
This file is processed in a cosmos pre-hook so the modules should be available for
use in the puppet post-hook. By default the file contains several lines that are
commented out so review this file as you start a new multiverse setup.
In order to add a new module, the best way is to commit a change to this file and
tag this change, allowing time for the module to get installed everywhere before
adding a change that relies on this module.
HOWTO and Common Tasks
======================
Adding a new operator
---------------------
Add the ascii-armoured key in a file in `global/overlay/etc/cosmos/keys` with a `.pub` extension
```
# git add global/overlay/etc/cosmos/keys/thenewoperator.pub
# git commit -m "the new operator" \
global/overlay/etc/cosmos/keys/thenewoperator.pub
# ./bump-tag
```
Removing an operator
--------------------
Identitfy the public key file in `global/overlay/etc/cosmos/keys`
```
# git rm global/overlay/etc/cosmos/keys/X.pub
# git commit -m "remove operator X" \
global/overlay/etc/cosmos/keys/X.pub
# ./bump-tag
```
Merging new features from multiverse
------------------------------------
The multiverse template will continue to evolve and sometimes it may be desirable to fetch a new feature from the upstream multiverse repository. If you followed the setup guide and kept the 'multiverse' remote this how you go about synchronizing with that version:
```
# git checkout multiverse
# git pull
# git checkout master
# git merge multiverse
```
Now resolve any conflicts (hopefully few and far between) and you should end up with a _combination_ of the features in your domain and those in multiverse. Note that you can optionally add more remotes referencing other development branches of multiverse and merge changes from more than one upstream. The sky is the limit.
Changing administrative domain for a host
-----------------------------------------
Below `$old` and `$new` refers to local copies (git clone) of the old and new repository.
In the `$new` repository add the host and use fabric to change the repository of the host to the git URL of the new repository.
```
# ./addhost -b $hostname
# fab -H $hostname chrepo repository:git://other/repo.git
```
In the `$old` repository:
```
# rsync -avz $hostname/ $new/$hostname/
```
In the `$new` repository:
```
# git add $hostname/
# git commit -m "transfer from $old" $hostname
# ./bump-tag
```
In the `$old` repository:
```
# git rm -rf $hostname
# git commit -m "remove $hostname"
# ./bump-tag
```
Running a command on multiple hosts
-----------------------------------
On a single host:
```
# fab -H $hostname -- command -a one -b another -c
```
On multiple hosts based on category:
```
# fab --roles=webserver -- ls /tmp
```
On all hosts:
```
# fab -- reboot # danger Will Robinsson!
```
|