Puppet: System Administration Automated

Support

Centralised sudoers recipe

Problem: How to Centralize the Sudoers file with Puppet

Sudo is one of the most important tools in the sysadmin arsenal, because it makes it simple to enable small chunks of super-user rights without the full power of the root password, and it also provides logging so that you can always tell who executed what commands as root. Just the logging is significant enough that it's a good idea to do all root work through sudo, even if you have the root password.

One of the other great things about sudo is that its configuration file, the sudoers file, was designed well enough that the same file can usually be used on every single machine on a network. The importance of a functional sudo combined with the simplicity of centralizing it make it a natural first step for centralized management.

This recipe works will in combination with the SVNIntegrationRecipe and is largely a follow-on to the Simplest Puppet Install Recipe.

Solution

There are two phases to this recipe. You must first set up your Puppet server so that it is capable of serving out files to clients, and then you must write a Puppet manifest that configures the clients to retrieve the central sudoers file.

Fileserver Setup

Puppet's file server operates somewhat like Rsync's server -- you use module names to conceal true path locations from clients. For instance, I usually make most of my files available in a module I call dist, but I can store those files wherever I want on disk. In an attempt to pander to LSB standards, I will use /var/lib/puppet/dist for the actual file storage, and I will put my sudoers file in apps/sudo in that directory:

sudo mkdir -p /var/lib/puppet/dist/apps/sudo
sudo cp /etc/sudoers /var/lib/puppet/dist/apps/sudo
sudo chown -R puppet:puppet /var/lib/puppet/dist/

This assumes that the puppet user and group already exist, as described in the Simplest Puppet Recipe?. It also assumes your sudoers file is in /etc; adjust as necessary.

Now that the file is in place, we just need to configure Puppet to look there. Create the fileserver configuration file at /etc/puppet/fileserver.conf:

[dist]
    path /var/lib/puppet/dist
    allow *.domain.com

Replace domain.com with your domain or IP space.

If you have already started your puppetmaster daemon, you will have to restart it, since it disables file serving if this file is missing on startup:

sudo pkill -HUP puppetmasterd

Puppet Configuration

Now we just need to configure Puppet to pull this file down to clients. Fortunately, this is incredibly easy, since we can just copy the Simplest Puppet Recipe? for everything, with one small change -- we just need to add a source attribute for our sudoers file:

class sudo {
    file { "/etc/sudoers":
        owner => root,
        group => root,
        mode => 440,
        source => "puppet://myserver.domain.com/dist/apps/sudo/sudoers"
    }
}

Like all URLs, the source for our file breaks down into three pieces. The first piece is the name of our protocol, which is puppet (no, this isn't an officially registered protocol; so sue me). The next bit is our server name. The last bit is the path to our file. Notice that the path starts with the module name -- we set up the file server to translate dist to /var/lib/puppet/dist, so clients just specify dist, not the full path. Clearly this is shorter, reduces the likelihood of security problems, and isolates clients from caring if files are moved on the server.

That's it. With this configuration, all of your Puppet clients will get your centralized sudoers file, and any changes made to the central file will be automatically downloaded to each client.

Modifications

Heterogeneous networks might have platforms that store the sudoers file in different places; it's relatively easy to abstract your configuration to suit each platform:

class sudo {
    file { sudoers: # a common title for all platforms
        path => $operatingsystem ? {
            solaris => "/usr/local/etc/sudoers",
            default => "/etc/sudoers"
        },
        owner => root,
        group => root,
        mode => 440,
        source => "puppet://myserver.domain.com/dist/apps/sudo/sudoers"
    }
}

This change allows us to set the path appropriately for each platform. Notice that we've moved the path to an explicit path parameter; this is done mostly for reasons of readability, but it also simplifies any dependency management we might do. If some other element depends on the sudoers file, it can refer to the file as file[sudoers], rather than having to copy the os-based selection each time.

Discussion

This recipe provided a simple solution for centralizing the sudo configuration file, which is generally the best file to begin a centralization project with. The pattern presented here can be reused for centralization of just about any other centralized file.