Puppet Data Centre Automation Solution, Part 3: Resource Types & Example Configs

Puppet resource types

The previous article in this series focused on creating users, groups and files (based on home directories). Let’s now look at the various types of resources provided by Puppet, by default, and at some exciting ways to use what we have already learnt till now.

Before we move on with our discussion today, let us first learn about the different resource types that are provided by Puppet.

Resource types

These are the building blocks of any Puppet configuration. Let me list all of them, even though we will not be using all; we should understand that we have so many blocks at our disposal.

  • Access and permissions
    • File (we discussed it in Part 2)
    • Group (we discussed it in Part 2)
    • User (we discussed it in Part 2)
    • k5login
    • Macauthorization (only available on Macintosh OS X)
    • mcx (only available on Mac OS X)
  • Packaging
    • Package
    • Yumrepo
  • Services
    • Service
    • Cron (we’ll discuss this in this article)
  • Filesystems and containers
    • File (we discussed it in Part 2)
    • Mount
    • zfs
    • zone (only available on Solaris)
    • zpool
  • Hosts
    • Host
    • Computer (only available on Mac OS X)
  • Configuration files
    • Augeas (we’ll discuss this in this article)
    • Mail
    • Mailalias
    • Maillist
  • Monitoring (see the Nagios types)
  • Security policy (see the SELinux types — this is only available on SELinux)
  • Executing scripts — exec

For more details on the above resource types, please check out the official documentation.

We are yet to study the concepts of modules and classes, something strictly recommended by the creators of Puppet. We will look into this higher-level setup of Puppet in our next article. For now, let us flex our administration muscles on simple but interesting tasks, such as:

  • Changing local account passwords
  • Managing sudo users
  • Setting up cron entries
  • Setting up Puppet via KickStart in RHEL/Fedora/CentOS

Changing local account passwords

Admin: Now what’s so difficult about changing local passwords? I will login to a machine and change mine and, if I have the root privilege, then set anyone else’s too.

Puppet: Is it so simple?

Admin: Why yes…

Puppet: Then what about changing 10, 100 or 1,000 of them?

Admin: Simple. I will write a for loop in a Bash script and use the Expect module for SSH and change passwords. Each time I have to just add a machine name to the list file that goes into the for loop.

Puppet: Smart!! Then you must surely know the total time to run the script?

Admin: Well, if it takes 0.5 seconds for one machine, then for 1,000 machines it should be 500 seconds or 8 minutes?

Puppet: Using my format user: password => “encrypted_password” for N number of users, it takes the same time on every box; they are run in parallel — at the same time. That is, it takes just 0.5 seconds to run on all boxes — without any SSH logins. Moreover, in this case, all users could change their own passwords if they had access to this proposed system.

Admin: The time saved is mind-blowing. But don’t kid me about users changing passwords on their own — what do you mean? Do you think I would give them write access?

Puppet: You can create Subversion accounts for them over HTTP, where they can update their password file using TORTOISE SVN or any such SVN client, which I will have ready as my configuration file specifies. All my client machines will pull the new information and update local accounts for this user.

Admin: Totally brilliant!

Puppet: I know :-)

Before we go ahead with how to do this, ensure that the ruby-shadow package (in RHEL/Fedora/CentOS) or libshadow-ruby (in Debian/Ubuntu) is installed; otherwise this will not work.

Please make sure that the lfy user account exists (it should, if you’ve practised the exercises in Part 2).

[email protected]: ~# cd /etc/puppet/manifests
[email protected]: /etc/puppet/manifests# grep lfy /etc/shadow
lfy:$1$ceJImY8b$pyog3eJFfC.kpqRaeUkf9/:14933:0:99999:7:::

$1$ means it is an MD5 encryption. We can use that. In Debian/Ubuntu, you need to use the md5pass command to create an encrypted password. Each time you run it, you will get a new encrypted string, for the given base password:

[email protected]: /etc/puppet/manifests# md5pass "Lf^paswd"
$1$/LRlQXC1$xnZDhA2sSRACsqNKvAPOq/

Our Puppet configuration manifest file would look like what’s shown below (note the single quotes; do not use double quotes):

[email protected]: /etc/puppet/manifests# cat site.pp 
user 
{
    lfy:
    password => '$1$/LRlQXC1$xnZDhA2sSRACsqNKvAPOq/'
}

To test if the command is right, run the following commands on the server:

[email protected]:/etc/puppet/manifests# puppet -v lfy.pp 
info: Applying configuration version '1290336628'
    notice: /Stage[main]//User[lfy]/password: changed password

Now, puppetd will do the magic on all the clients. Just imagine that this one file, with just 4-5 lines, will replicate to all the servers in our data centre!

Now, what happens when users want to change their passwords on their own. For this, we might need a Subversion setup, where we have a Puppet file named after each user. In our case, let’s rename the site.pp file to lfy.pp (the name of the user). We can call the lfy.pp file from site.pp using import lfy.pp or import lfy. Thus, site.pp can be created as follows:

[email protected]: /etc/puppet/manifests~# echo 'import \"lfy\"' >  site.pp
[email protected]: /etc/puppet/manifests~# cat site.pp
import "lfy"

We can have this modified to work for any number of users. Also, lfy.pp could be accessed as a soft link over Subversion/HTTP, which the user can update by creating a password using md5pass and replace just the string.

There is one more option, where the users log into the Puppet Server box, and change their own passwords. There is a script that will check from Puppet, if there is a change in the /etc/shadow entry for the user, in the password field. If there is a change, it will extract that string and populate it to all Puppet clients — this might need some more, strong, Puppet language coding. :)

Managing sudo users

Managing sudo users has been a real pain for most admins, if they have not been using LDAP. Maintaining control over an /etc/sudoers file is really difficult. We can provide access either to a user, or to a group. But either way, maintaining this on a per-machine basis is difficult. This can be easily handled using Puppet in one of two ways:

  1. Storing a ready-made sudoers file on the Puppet server, and populating it to every Puppet client. This is the preferred method, which will need modules (something we will handle in our next session).
  2. Editing the sudo file on every machine. This uses a configuration-editing tool called Augeas. There is a Ruby package that makes use of this utility — libaugeas-ruby in .deb package format, and ruby-augeas in the RPM package format.

What is Augeas?

  • An API provided by a C library
  • A command-line tool to manipulate configuration from the shell (and shell scripts)
  • Language bindings to do the same from your favourite scripting language
  • Canonical tree representations of common configuration files
  • A domain-specific language to describe configuration file formats

Goals

  • Manipulate configuration files safely, compared to the ad-hoc techniques generally used with grep, sed, awk and the like, in shell scripts
  • Provide a local configuration API for Linux
  • Make it easy to integrate new configuration files into the Augeas tree.

We can install Augeas straight away to test the same set of commands. This can be done from an augtool command line that we could try through Puppet. This is done by either installing the package, or compiling Augeas from source and installing it. Next, enter the following lines into sudo.pp:

$group = "%unixadmin"
     # Specifying the group that needs sudo permission.
# this also can be a user, but without the % symbol

 augeas  
{
  "/etc/sudoers":
    context => "/files",
    # This is for mentioning the scope and type of input
         changes => [
                "set etc/sudoers/spec[last() + 1]/user $group",
                "set etc/sudoers/spec[last()]/host_group/host ALL",
                "set etc/sudoers/spec[last()]/host_group/command ALL",
                "set etc/sudoers/spec[last()]/host_group/command/runas_user ALL",
               ],
 onlyif => "get etc/sudoers/spec[last()]/user != $group"
}

What does this do? It checks for the last line where it cannot find %unixgroup as the user. So the drawback here is that in case someone is able to overwrite the /etc/sudoers file, then we might have more entries than planned. But our main mission now is to just include this group. We can run augtool and see what it results in:

[email protected]: /etc/puppet/manifests# augtool
augtool> print /files/etc/sudoers/spec[last()]
/files/etc/sudoers/spec[3]
/files/etc/sudoers/spec[3]/user = "%admin"
/files/etc/sudoers/spec[3]/host_group
/files/etc/sudoers/spec[3]/host_group/host = "ALL"
/files/etc/sudoers/spec[3]/host_group/command = "ALL"
/files/etc/sudoers/spec[3]/host_group/command/runas_user = "ALL"

Make sure there is a group called unixgroup in /etc/group; if not, then create one.

Add an extra line to site.pp to call our sudo.pp file as well:

[email protected]: /etc/puppet/manifests# echo 'import "sudo"' >> site.pp

Try running puppet -v sudo.pp on the server, for testing. It should create a line in /etc/sudoers. Then run augtool again to see what it prints:

[email protected]:/etc/puppet/manifests# puppet -v sudo.pp 
info: Applying configuration version '1290336571'\
notice: /Stage[main]//Augeas[/etc/sudoers]/returns: executed successfully

[email protected]: /etc/puppet/manifests/etc/puppet/manifests# augtool
augtool> print /files/etc/sudoers/spec[last()]
/files/etc/sudoers/spec[4]\
/files/etc/sudoers/spec[4]/user = "%unixadmin"
/files/etc/sudoers/spec[4]/host_group
/files/etc/sudoers/spec[4]/host_group/host = "ALL"
/files/etc/sudoers/spec[4]/host_group/command = "ALL"
/files/etc/sudoers/spec[4]/host_group/command/runas_user = "ALL"

You can surely test out all different options for the permissions. Now you can run your puppetd on the clients or wait for it to run on its own as per cron.

Setting up cron entries

We have been talking about setting up cron entries since Part 2. How are we going to do it on each and every machine? There is an easy way through Puppet — the Cron resource type, which we pointed out at the beginning of this article.

Initially, when setting up Puppet, we might need to first set up cron on the Puppet server itself. Then, we need to run puppetd only once on all clients. After that, life is a breeze. Be sure to backup your original cron entries. Please create a cron.pp in your /etc/puppet/manifests folder, where the rest of our .pp files are, with the following content:

cron
{
  "Puppet":
   command => "/usr/bin/puppet --server=puppet --onetime",
   user => "root",
   hour => "*",
   minute => "*/15"
}

Of course, add an import of the new cron.pp to the site.pp:

[email protected]: /etc/puppet/manifests #echo 'import "cron"' >> site.pp

Now, to test if it is working, run a puppet -v on the server for cron:

[email protected]:/etc/puppet/manifests# puppet -v cron.pp
info: Applying configuration version '1290336920'
notice: /Stage[main]//Cron[Puppet]/ensure: created
root@server.linuxforu:/etc/puppet/manifests# crontab -l
# HEADER: This file was autogenerated at Sun Nov 21 16:25:21 +0530 2010 by puppet.
# HEADER: While it can still be managed manually, it is definitely not recommended.
# HEADER: Note particularly that the comments starting with 'Puppet Name' should
# HEADER: not be deleted, as doing so could cause duplicate cron jobs.
# Puppet Name: Puppet
*/15 * * * * /usr/bin/puppet --server=puppet --onetime

Now, puppetd could be run once on all machines, and then it is the turn of cron to take over for every 15 minutes on all our clients, provided the cron daemon is also active.

Setting up Puppet via KickStart in RHEL/Fedora/CentOS

As per this Puppet wiki documentation on Bootstrapping with Puppet, the following code would be enough to install Puppet in a KickStart configuration:

%packages
@base
ruby
ruby-libs
puppet
facter
ruby-shadow
rubygems

However, this did not work for me, so I shifted to YUM. And this is how a part of my ks.cfg looked:

# Package Selection
%packages
@gnome-desktop
@graphical-internet
@base-x
#package for intel driver
xorg-x11-drv-i810

%post
# Stopping all unwanted services
services=(pcscd avahi-daemon hidd bluetooth anacron atd auditd autofs cups ip6tables iptables nfslock portmap sendmail yum-updatesd);

for i in ${services[@]}; 
do 
echo -e "Stopping $i...\n"
chkconfig --level 12345 $i off; 
done

# The following line is for reading variables from the command line just before pressing 
# enter for reading the kickstart. I add the variable $myhostname after the usual 
# kickstart line
# linux ks=http://server/ks.cfg myhostname=client.linuxforu
# The above line can be re-read from below. It is better to even set your IP address here itself, 
# in the %post section -- if you are sure of your IP address
< /proc/cmdline sed 's/ /\n/g' | grep ^my > /tmp/boot_parameters
. /tmp/boot_parameters
if [[ "$myhostname" ]]; then
 echo "Setting hostname to $myhostname"
 sed -i s/HOSTNAME.*/HOSTNAME=$myhostname/ /etc/sysconfig/network
fi

echo -e "`ifconfig | grep Bcast | awk '{print $2}' | awk -F: '{print $2}'` \t $myhostname">> /etc/hosts
 
# extracting the IP address and hostname to place into /etc/hosts file for first time 
# name resolution which is must for Puppet to register with Puppet CA Server.
# Setting Puppet Server's name resolution

echo -e "Puppet_Server_IP\tPuppet_Server_FQDN\tpuppet">> /etc/hosts
# This is not necessary if /etc/resolv.conf is able to resolve 
# the Puppet Server, by the alias dns name puppet.

# Installing Puppet
# First we need to create the yum client pointing to the right repository.
rm -rf /etc/yum.repos.d/*
cat >> /etc/yum.repos.d/CentOSrepo.repo << EOF
[CentOSrepo]
name=CentOSrepo
baseurl=http://YUM_Server/centos/5.4/base
gpgcheck=0
EOF
yum -y install puppet ruby-rdoc
# this is for avoiding any interactive session. This will only work if the packages 
# have been properly registered in YUM server.

# Configure Puppet Client for contacting server
sed -i 's/#PUPPET_SERVER/PUPPET_SERVER/g' /etc/sysconfig/puppet
sed -i 's/#PUPPET_PORT/PUPPET_PORT/g' /etc/sysconfig/puppet
sed -i 's/#PUPPET_LOG/PUPPET_LOG/g' /etc/sysconfig/puppet

hostname $myhostname
/usr/bin/puppet -dt --waitforcert=60

The above will install the Puppet client and register itself as a new client, to the Puppet CA server. It will wait for 60 seconds for the new certificate, which will return when we sign the registered certificate on the CA server (this is recommended, in case we want to set up a cron entry to run Puppet) or let the OS installation complete, after which we can register whenever we want.

In the next article, we will look at modules and classes, and discover how they offer better control over Puppet.

All published articles are released under Creative Commons Attribution-NonCommercial 3.0 Unported License, unless otherwise noted.
Open Source For You is powered by WordPress, which gladly sits on top of a CentOS-based LEMP stack.

Creative Commons License.