Skip to main content

Puppet and Subversion in fifteen minutes

Configuration and change management are extremely important in keeping a production environment running smoothly with high uptime. Puppet does a fantastic job of distributing and enforcing configurations, while Subversion is good for keeping an audit trail of configuration changes. Putting the two together gives you a world-class solution that easily scales into the tens of thousands of servers.

First, setup a Subversion repository and server. This can be done in under four minutes by following this excellent guide. For purposes of brevity, hereafter we'll just assume that svn://svnserve/var/svn/puppet is where all the Puppet files are being kept. The Subversion server does not need to be on the same server that will become the Puppetmaster; it can be anywhere. For the rest of this document we'll assume your Subversion repository exists as /var/svn.

Next, add a user account to Subversion that Puppet will use to checkout files. Edit the /var/svn/conf/passwd file and add the following line to the end of the file:

puppet=puppet

For added security, this user should only be able to checkout files, and not checkin files.

Configure svnserve to start on boot, and start the daemon (this assumes a Red Hat-ish system with Subversion installed from RPMs):

$ sudo chkconfig svnserve on
$ sudo service svnserve start

Now install Puppet on the server that is to become the Puppetmaster:
$ sudo yum install puppet-server

You'll want to install the client RPM (puppet) everywhere, including on the Puppetmaster server (it can be a client of itself.) Only install the server RPM (puppet-server) on the Puppetmaster.

Once Puppet is installed, go to the /etc directory and setup an initial module for controlling the /etc/sudoers file (it's as good a starting place as any other):

$ sudo mkdir -p /etc/puppet/modules/sudo/manifests

Create the /etc/puppet/modules/sudo/manifests/init.pp file with the following contents:
    class sudo {
        file { "/etc/sudoers":
            owner   => root,
            group   => root,
            mode    => 440,
            source  => "puppet:///sudo/sudoers",
        }
    }

The block of code instructs Puppet to create the file /etc/sudoers if it does not exist; ensure that it is owned by root:root at all times; is always read-only to the owner and group, with no access to the world; and that the file's contents exactly match puppet:///sudo/sudoers, which we'll create next.

Create the directory that will hold our master sudoers file:

$ sudo mkdir -p /etc/puppet/modules/sudo/files

Grab a copy of sudoers from somewhere (the local server unless you have a more authoritative copy) and put it in the directory you just created:
$ sudo cp /etc/sudoers /etc/puppet/modules/sudo/files/

WARNING: this is the file that will shortly overwrite every single other sudoers file in your environment. If nothing else, make sure you yourself exist in it!

For the sake of convenience, change the permissions on the file (remember, the permissions that the file is given when it is deployed by Puppet are governed by the configuration file init.pp, and not by the permissions found on the source file):

$ sudo chmod 644 /etc/puppet/modules/sudo/files/sudoers

We will now instruct Puppet to import our shiny new module. Create and edit the /etc/puppet/manifests/modules.pp file, and place the following line in it:
import "sudo"

Let's also create the /etc/puppet/manifests/nodes.pp file, which we'll use in the future as we add more files beyond sudoers to Puppet:
    node basenode {
        include sudo
    }

Last but not least, we will now create the /etc/puppet/manifests/site.pp file with the following content:
    import "modules"
    import "nodes"
 
    filebucket { main: server => puppet }
 
    File { backup => main }
    Exec { path => "/usr/bin:/usr/sbin/:/bin:/sbin:/usr/local/bin:/usr/local/sbin" }
 
    node default {
            include sudo
    }

At this point you should have a /etc/puppet directory structure that looks like this:
$ find /etc/puppet
/etc/puppet
/etc/puppet/manifests
/etc/puppet/manifests/site.pp
/etc/puppet/manifests/modules.pp
/etc/puppet/manifests/nodes.pp
/etc/puppet/puppet.conf
/etc/puppet/fileserver.conf
/etc/puppet/modules
/etc/puppet/modules/sudo
/etc/puppet/modules/sudo/manifests
/etc/puppet/modules/sudo/manifests/init.pp
/etc/puppet/modules/sudo/files
/etc/puppet/modules/sudo/files/sudoers

Import put the entire /etc/puppet directory and its contents into Subversion:
$ cd /etc
$ svn import ./puppet svn://svnserve/var/svn/puppet -m "Add initial Puppet configuration directories"

Make sure the directories and files were successfully imported:
$ svn list -vR svn://svnserve/var/svn/puppet
      24507 puppet            381 May 22 18:47 fileserver.conf
      24507 puppet                May 22 18:47 manifests/
      24507 puppet             14 May 22 18:47 manifests/modules.pp
      24507 puppet             32 May 22 18:47 manifests/nodes.pp
      24507 puppet            177 May 22 18:47 manifests/site.pp
      24507 puppet                May 22 18:47 modules/
      24507 puppet                May 22 18:47 modules/sudo/
      24507 puppet                May 22 18:47 modules/sudo/files/
      24507 puppet            151 May 22 18:47 modules/sudo/files/sudoers
      24507 puppet                May 22 18:47 modules/sudo/manifests/
      24507 puppet            129 May 22 18:47 modules/sudo/manifests/init.pp
      24507 puppet            979 May 22 18:47 puppet.conf

You'll note that I checked them in as myself; it's a good idea to never use the puppet Subversion user for anything other than checkout.

Next, rename or delete the existing directory:

$ sudo mv /etc/puppet /etc/puppet.orig

Now edit the /etc/crontab file and add the following line to the bottom of the file:
* * * * * root cd /etc && svn --username puppet --password puppet checkout svn://svnserve/var/svn/puppet

Wait a moment, and the /etc/puppet directory should reappear. If it does not, check the cron line and verify that you correctly setup the puppet user in Subversion.

It's a good idea at this point to create a DNS alias (CNAME) record named puppet that points to your Puppetmaster's FQHN. Both the Puppet client and server strongly prefer to talk to a host named puppet as the master, and it's a lot of fighting to get it to behave otherwise. Throw the alias in, and your life will be a lot easier.

Initialize the Puppetmaster for the first time by running:

    $ sudo puppetmasterd --mkusers

Enable starting the Puppetmaster at boot, and then restart the daemon (the above command will have started it):
    $ sudo chkconfig puppetmaster on
    $ sudo service puppetmaster restart

We now need to setup our first Puppet client. The mechanism for adding a client to the Puppetmaster is as follows:

  • Have the client contact the server and ask to be authorized
  • Instruct the server to authorize the client
  • The client will discover that it is now authorized and begin policy enforcement

Install Puppet on the server that is to become a Puppet client:

    $ sudo yum install puppet

Now have the Puppet client send its certificate to the Puppetmaster:
    $ sudo puppetd --waitforcert 90 --test

Go to the server, and run:
    $ sudo puppetca --list

This should list the hostname of the new Puppet client. To sign the client's certificate, run:
    $ sudo puppetca --sign hostname

Note: If you have many clients, you can accomplish this step in one line by running:
    $ for client in `sudo puppetca --list`; do sudo puppetca --sign $client; done

Test the Puppet client connection by running:
    $ sudo puppetd --test

You should see output like the following:
    notice: Ignoring cache
    info: Caching configuration at /var/lib/puppet/localconfig.yaml
    notice: Starting configuration run
    notice: Finished configuration run in 0.11 seconds

Furthermore, if everything is working correctly, the /etc/sudoers file on your client should have been overwritten with the master copy. You can try altering it (for example, by adding a comment to it) and then running puppetd --test again:
    notice: Ignoring cache
    info: Caching configuration at /var/lib/puppet/localconfig.yaml
    notice: Starting configuration run
    notice: /Class[main]/Node[default]/Class[sudo]/File[/etc/sudoers]/checksum: checksum changed '{md5}4dbc63b33c8a8f5ff3e41f3d6a996576' to '{md5}46b4b281acb865468616f3208f111c3c'
    info: /Class[main]/Node[default]/Class[sudo]/File[/etc/sudoers]: Filebucketed to main with sum 46b4b281acb865468616f3208f111c3c
    notice: /Class[main]/Node[default]/Class[sudo]/File[/etc/sudoers]/source: replacing from source puppet:///sudo/sudoers with contents {md5}4dbc63b33c8a8f5ff3e41f3d6a996576
    notice: Finished configuration run in 0.30 seconds

Last but not least, enable the Puppet client to start at system boot:
    $ sudo chkconfig puppet on
    $ sudo service puppet restart

That's it! You now have a working Subversion, Puppetmaster, and Puppet client network setup, with your /etc/sudoers file under Puppet control. For more information, see the Puppet recipes here and read the Puppet documentation here.

Good luck!