Tag Archives: GnuPG

My bastardized Masterless Puppet

I am currently using Puppet to control my laptop as well as my two VPS nodes. That is not exactly the scale where I feel the need to have a puppet master running. Especially not since I am not overly keen on the idea of giving an external machine control over my laptop.

That being said, I still want some central location from where my nodes can fetch the latest recipes, allowing me the freedom to push updated recipes even if a node don’t happen to be online at the time. I just don’t want to spend any actual resources on this central location, nor having to trust it more than necessary.

At first my recipes didn’t contain any secrets and I got away with pulling updated recipes from a (public) github repository. The only overhead was the need to have my puppet cron script verify that HEAD contained a valid gpg signed tag.

Now my puppet recipes do depend on secrets. These shouldn’t be available neither to the central location nor to the wrong node. That bringing us to my current homegrown, slightly bastardized, solution.

The current central location for my puppet recipes is a cheap web host. To it I am uploading gpg encrypted tarballs. These tarballs are individually generated as well as encrypted with each nodes own gpg key. For further details, see the included Makefile below.

default:
	apt-get moo

locally: manifests/$(shell facter hostname).pp
	./modules/puppet/files/etckeeper-commit.sh
	puppet apply --confdir . --ssldir /etc/puppet/ssl ./manifests/$(shell facter hostname).pp
	./modules/puppet/files/etckeeper-commit.sh

backup:
	tarsnap --configfile ./.tarsnaprc -c -f "$(shell date +%s)" .

manifests/%.pp: manifests/defaults.inc manifests/%.inc
	cat $^ > $@

validate:
	find -regextype posix-egrep -regex ".+.(pp|inc)" | xargs puppet parser validate
	find -name "*.erb" | xargs ./tools/validaterb.sh

exported/%.tar: manifests/%.pp validate
	tar cf $@ manifests/$*.pp modules/ secrets/common/ secrets/$*/

exported/%.tar.gpg: exported/%.tar
	gpg --batch --yes --recipient puppet@$*.arrakis.se --encrypt $<

exported/%.tar.gpg.sig: exported/%.tar.gpg
	gpg --batch --yes --detach-sign $<

upload-%: exported/%.tar.gpg exported/%.tar.gpg.sig
	scp -o BatchMode=yes exported/$*.tar.gpg.sig andol_andolpuppet@ssh.phx.nearlyfreespeech.net:/home/public/
	scp -o BatchMode=yes exported/$*.tar.gpg andol_andolpuppet@ssh.phx.nearlyfreespeech.net:/home/public/

hosts := halleck hawat leto
deploy: $(addprefix upload-, $(hosts))

.PHONY: default locally backup deploy validate

…and here is the download script running on the nodes. In addition to doing the gpg stuff the script also handles ETags for the http download.

#!/bin/bash

tarball="$(facter hostname).tar"
gpgball="${tarball}.gpg"
gpghead="${gpgball}.header"
sigfile="${gpgball}.sig"
etagfile="/usr/local/var/puppet/etag"
netrcfile="/usr/local/etc/puppet/netrc_puppet"

bailout () {
    rm -rf "$workdir"
    [ -n "$2" ] && echo "$2"
    exit $1
}

umask 0027

curretag=""
if [ -f "$etagfile" ]; then
    curretag=$(head -n1 "$etagfile" | grep -Ei "^[0-9a-f-]+$")
fi

workdir=$(mktemp --directory)
cd $workdir || exit 1

curl --silent --show-error 
    --netrc-file "$netrcfile" 
    --header "If-None-Match: "$curretag"" 
    --dump-header "$gpghead" --remote-name 
    "http://puppet.arrakis.se/$gpgball"

if grep -Eq "^HTTP/1.1 304" "$gpghead"; then
    bailout 0
elif grep -Eq "^HTTP/1.1 200" "$gpghead"; then
    newetag=$(sed -nre "s/^ETag: "([0-9a-f-]+)"s*$/1/pi" "$gpghead")
    [ -n "$newetag" ] && echo "$newetag" > "$etagfile"
else
    bailout 0 "Failed to get expected HTTP response."
fi

curl --silent --show-error 
    --netrc-file "$netrcfile" --remote-name 
    "http://puppet.arrakis.se/$sigfile"

gpgv --keyring /usr/local/etc/puppet/gnupg/trustedkeys.gpg "$sigfile" 2> /dev/null
if [ $? -ne 0 ]; then
    bailout 0 "Signature verification failed."
fi

export GNUPGHOME=/usr/local/etc/puppet/gnupg
gpg --quiet --batch "$gpgball" 2> /dev/null
if [ $? -ne 0 ]; then
    bailout 0 "Decryption failed."
fi

tar --no-same-owner --no-same-permissions -xf "$tarball"
if [ $? -ne 0 ]; then
    bailout 0 "tar extract failed."
fi

rsync --archive --delete --chmod=o-rxw,g-w 
    manifests modules secrets /usr/local/etc/puppet/

if [ $? -ne 0 ]; then
    echo beef > "$etagfile"
    bailout 1 "rsync update failed."
fi

rm -rf "$workdir"

Of course, this approach involves a bit more work while setting up Puppet on a new node. So while I feel that it is a good fit for my current situation it isn’t anything I would use in a larger environment. Also, with a larger amount of nodes there are puppet master features, such as reporting and storeconfigs, being potentially more valuable.

Managing passwords using GnuPG, Git and Emacs

Like any other security conscious and/or slightly paranoid computer geek I have lots and lots of unique and nontrivial passwords to keep track of.  My solution to this problem involves having one GnuPG encrypted text file per username/password pair.

andreas@stilgar:~/safe$ gpg < example.gpg

You need a passphrase to unlock the secret key for
user: "Andreas Olsson <andreas@arrakis.se>"
4096-bit RSA key, ID 9A943D4A, created 2010-07-11 (main key ID 13CD4F59)
  Here gnupg-agent calls pinentry-gtk2 to prompt me for the passphrase
gpg: encrypted with 4096-bit RSA key, ID 9A943D4A, created 2010-07-11
      "Andreas Olsson <andreas@arrakis.se>"

https://127.0.0.1/

username: sigge
password: sigge

andreas@stilgar:~/safe$

As I need to have access to those passwords on more than one computer I use Git, and a remote repository, to keep my encrypted files in sync. Other options might be to mount a SFTP folder using SSHFS, or to simply put the files in your Dropbox. Yet, if you too decide to go with Git, here is a .gitignore you might want to use.

andreas@stilgar:~/safe$ cat .gitignore
*
!*.gpg
!.gitignore
andreas@stilgar:~/safe$

Thanks to Emacs and EasyPG it is a breeze to  create new GnuPG encrypted text files, as well as to modify existing ones. Just use the file extension .gpg, and EasyPG will do its thing. The first time, when you actually create the file, you will be prompted for which public keys you want to encrypt against.

andreas@stilgar:~/safe$ emacs yet_another_example.gpg

(EasyPG is included in Emacs 23, and don’t need to be installed separately.)

Do note that this method also works when there are multiple people involved. Just make sure that the intended users have access to the share/repository in question, and that their public keys are included when you create the GnuPG files.

OpenPGP key transition

I’ve recently set up a stronger (4096R) OpenPGP key, and will be transitioning away from my old (1024D) one. To a large extent this is about being able to use the SHA-2 family for signatures.

The old key will continue to be valid for some time, but I prefer all future correspondence to come to the new one. I would also like this new key to be re-integrated into the web of trust. Please see this statement signed with both keys, certifying the transition.

The old key was:

pub   1024D/FAF2463A 2006-11-20
      Key fingerprint = 4947 BB72 9192 8645 CC8B  F142 8AF2 8D1C FAF2 463A

The new key is:

pub   4096R/13CD4F59 2010-07-11
      Key fingerprint = AFEB 2D24 4715 3F0D 9250  8A8B 5882 A0DC 13CD 4F59
uid                  Andreas Olsson
uid                  Andreas Olsson
uid                  Andreas Olsson
uid                  Andreas Olsson
sub   4096R/9A943D4A 2010-07-11

To fetch my new key from a public key server, you can simply do:

  $ gpg --keyserver pool.sks-keyservers.net --recv-key 0x13CD4F59

If you already know my old key, you can now verify that the new key is signed by the old one:

  $ gpg --check-sigs 0x13CD4F59

If you are satisfied that you’ve got the right key, and the UIDs match what you expect, I’d appreciate it if you would sign my key:

  $ gpg --sign-key 0x13CD4F59

Lastly, if you could upload these signatures, I would appreciate it:

  $ gpg --keyserver pool.sks-keyservers.net --send-key 0x13CD4F59

Please let me know if there is any trouble, and sorry for the inconvenience.