Pro Puppet (55 page)

Read Pro Puppet Online

Authors: Jeffrey McCune James Turnbull

BOOK: Pro Puppet
2.45Mb size Format: txt, pdf, ePub
A More Complex Type and Provider

In this section we're going to show you a slightly more complex type and provider used to manage HTTP authentication password files. It's a similarly ensureable type and provider, but with some more sophisticated components.

The httpauth Type

Let's start by looking at the
httpauth
type shown in
Listing 10-10
.

Listing 10-10.
The httpauth type

Puppet::Type.newtype(:httpauth) do
    @doc = "Manage HTTP Basic or Digest password files." +
           "    httpauth { 'user':                     " +
           "      file => '/path/to/password/file',    " +
           "      password => 'password',              " +
           "      mechanism => basic,                  " +
           "      ensure => present,                   " +
           "    }                                      "
    ensurable do
       newvalue(:present) do
           provider.create
       end
       newvalue(:absent) do
           provider.destroy
       end
       defaultto :present
    end
    newparam(:name) do
       desc "The name of the user to be managed."
       isnamevar
    end
    newparam(:file) do
       desc "The HTTP password file to be managed. If it doesn't exist it is created."
    end
    newparam(:password) do
       desc "The password in plaintext."
    
    end
    newparam(:realm) do
       desc "The realm - defaults to nil and mainly used for Digest authentication."
       defaultto "nil"
    end
    
    newparam(:mechanism) do
       desc "The authentication mechanism to use - either basic or digest. Default to basic."
       newvalues(:basic, :digest)
      
       defaultto :basic
    end
    # Ensure a password is always specified
    validate do
       raise Puppet::Error, "You must specify a password for the user." unless @parameters.include?(:password)
    end
end

In the
httpauth
type we're managing a number of attributes, principally the user, password and password file. We also provide some associated information, like the realm (A HTTP Digest Authentication value) and the mechanism we're going to use, Basic or Digest Authentication.

First, notice that we've added some code to our
ensurable
method. In this case, we're telling Puppet some specifics about the operation of our
ensure
attribute. We're specifying that for each state,
present
and
absent
, exactly which method in the provider should be called, here
create
and
destroy,
respectively. We're also specifying the default behavior of the
ensure
attribute. This means that if we omit the
ensure
attribute that the
httpauth
resource will assume
present
as the value. The resource will then check for the presence of the user we want to manage, and if it doesn't exist, then it will create that user.

We've also used some other useful methods. The first is the
defaultto
method that specifies a default value for a parameter or property. If the resource does not specify this attribute, then Puppet will use to this default value to populate it. The other is the
newvalues
method that allows you to specify the values that the parameter or property will accept. In
Listing 10-10
, you can see the
mechanism
parameter that the
newvalues
method specifies will take the values of
basic
or
digest
.

Lastly, you can see that we used the
validate
method to return an error if the
httpauth
resource is specified without the
password
attribute.

The httpauth Provider

Now let's look at the provider for the
httpauth
type, shown in
Listing 10-11
.

Listing 10-11.
The httpauth provider

begin
    require 'webrick'
rescue
    Puppet.warning "You need WEBrick installed to manage HTTP Authentication files."
end
Puppet::Type.type(:httpauth).provide(:httpauth) do
    desc "Manage HTTP Basic and Digest authentication files"
    def create
        # Create a user in the file we opened in the mech method
        @htauth.set_passwd(resource[:realm], resource[:name], resource[:password])
        @htauth.flush
    end
    def destroy
        # Delete a user in the file we opened in the mech method
        @htauth.delete_passwd(resource[:realm], resource[:name])
        @htauth.flush
    end
    def exists?
        # Check if the file exists at all
        if File.exists?(resource[:file])
            # If it does exist open the file
            mech(resource[:file])
            # Check if the user exists in the file
            cp = @htauth.get_passwd(resource[:realm], resource[:name], false)
            # Check if the current password matches the proposed password
            return check_passwd(resource[:realm], resource[:name], resource[:password], cp)
        else
            # If the file doesn't exist then create it
            File.new(resource[:file], "w")
            mech(resource[:file])
            return false
        end
    end
    # Open the password file
    def mech(file)
        if resource[:mechanism] == :digest
            @htauth = WEBrick::HTTPAuth::Htdigest.new(file)
        elsif resource[:mechanism] == :basic
            @htauth = WEBrick::HTTPAuth::Htpasswd.new(file)
        end
    end
    # Check password matches
    def check_passwd(realm, user, password, cp)
        if resource[:mechanism] == :digest
            WEBrick::HTTPAuth::DigestAuth.make_passwd(realm, user, password) == cp
        elsif resource[:mechanism] == :basic
            # Can't ask webbrick as it uses a random seed
            password.crypt(cp[0,2]) == cp
        end
    end
end

This provider is more complex than what we've seen before. We've still got the methods that handle Puppet's ensurable capabilities:
create
,
destroy
and
exists?
. In addition, though, we've got additional methods that manipulate our password files.

Our provider first checks for the existence of the Webrick library, which it needs in order to manipulate HTTP password files. The provider will fail to run if this library is not present. Fortunately, Webrick is commonly present in most Ruby distributions (and indeed, is used by Puppet as its basic server framework, as we learned in 2).

Tip
As an alternative to requiring the Webrick library, we could use Puppet's feature capability. You can see some examples of this in
https://github.com/puppetlabs/puppet/blob/master/lib/puppet/feature/base.rb.
This capability allows you to enabled or disable features based on whether certain capabilities are present or not. The obvious limitation is that this approach requires adding a new feature to Puppet's core, rather than simply adding a new type or provider.

Our provider then has the three ensurable methods. The
create
and
destroy
methods are relatively simple. They use methods from the Webrick library to either set or delete a password specified in the HTTP password file managed by the resource. The file being referred to here using the
resource[:file]
value which is controlled by setting the
file
attribute in the
httpauth
resource, for example:

httpauth { “bob”:
  file => “/etc/apache2/htpasswd.basic”,
  password => “password”,
  mechanism => basic,
}

Lastly, you'll also see in the
create
and
destroy
methods that we call the
flush
method. This flushes the buffer and writes out our changes.

The
exists?
method is more complex and calls several helper methods to check whether the user and password already exist, and if they do, whether the current and proposed passwords match.

Testing Types and Providers

Like facts, you can test your types and providers. The best way to do this is add them to a module in your development or testing environment and enable pluginsync to test them there before using them in your production environment, for example let's add our HTTPAuth type to a module called httpauth, first adding the required directories:

$ mkdir –p /etc/puppet/modules/httpauth/(manifests,files,templates,lib}
$ mkdir –p /etc/puppet/modules/httpauth/lib/{type,provider}
$ mkdir –p /etc/puppet/modules/httpauth/lib/provider/httpauth

Then copying in the type and provider to the requisite directories.

# cp type/httpauth.rb /etc/puppet/modules/lib/type/httpauth.rb
# cp provider/httpauth.rb /etc/puppet/modules/lib/provider/httpauth/httpauth.rb

When Puppet is run (and pluginsync enabled) it will find your types and providers in these directories, deploy them and make them available to be used in your Puppet manifests.

Writing Custom Functions

The last type of custom Puppet code we're going to look at is the function. You've seen a number of functions in this book already, for example:
include
,
notice
and
template
are all functions we've used. But you can extend the scope of the available functions by writing your own.

There are two types of functions: statements and rvalues. Statements perform some action, for example the
fail
function, and rvalues return a value, for example if you pass in a value, the function will process it and return a value. The
split
function is an example of an rvalue function.

Note
Remember that functions are executed on the Puppet master. They only have access to resources and data that are contained on the master.

We're going to write a simple function and distribute it to our agents. Like plug-ins, we can use plug-in sync to distribute functions to agents; they are stored in:

custom/lib/puppet/parser/functions

The file containing the function must be named after the function it contains; for example, the
template
function should be contained in the
template.rb
file.

Let's take a look at a simple function in
Listing 10-12
.

Listing 10-12.
The SHA512 function

Puppet::Parser::Functions::newfunction(:sha512, :type => :rvalue, :doc => "Returns a SHA1
hash value from a provided string.") do |args|
  require 'sha1'
  Digest::SHA512.hexdigest(args[0])
end

Puppet contains an existing function called sha1 that generates a SHA1 hash value from a provided string. In
Listing 10-12
, we've updated that function to support SHA512 instead. Let's break that function down. To create the function we call the
Puppet::Parser::Functions::newfunction
method and pass it some values. First, we name the function, in our case
sha512
. We then specify the type of function it is, here rvalue, for a function that returns a value. If we don't specify the type at all then Puppet assumes the function is a statement. Lastly, we specify a
:doc
string to document the function.

The
newfunction
block takes the incoming argument and we process it, first adding in support for working with SHA hashes by requiring the
sha1
library, and then passing the argument to the
hexdigest
method. As this is an rvalue function, it will return the created hash as the result of the function.

Note
The last value returned by the
newfunction
block will be returned to Puppet as the rvalue.

Other books

The UnAmericans: Stories by Antopol, Molly
Passing Through Midnight by Mary Kay McComas
Unfriendly Competition by Jessica Burkhart
Bloodmark by Whittet, Aurora
Blood and Gold by Anne Rice