Centrally sharing data using a Chef data bag and Hiera with Puppet

Now we have the basics of our LAMP infrastructure up and running, let's secure it a little by creating an htaccess file with a few authorized users in it. To achieve this, we could use different techniques, but the data bag feature in Chef is pretty convenient for our objective. A data bag is simply data in a JSON file stored on the Chef server, that can be searched from the cookbooks. It's especially useful for storing data that need to be accessed globally from a central point (such as users, service credentials, version numbers, URLs, even feature flags, and other similar features depending on your usage).

Getting ready

To work through this recipe, you will need the following:

  • A working Chef DK installation on the workstation
  • A working Chef client configuration on the remote host
  • The Chef code from the previous recipes

How to do it…

Our objective is to create two users—John and Mary. Here's a table of the required information:

User

Password

Hash

John

p4ssw0rd

$apr1$AUI2Y5pj$0v0PaSlLfc6QxZx1Vx5Se

Mary

s3cur3

$apr1$eR7H0C5r$OrhOQUTXfUEIdvWyeGGGy/

Note

To generate the encrypted passwords, you can use the simple htpasswd utility:

$ htpasswd -n -b mary s3cur3
mary:$apr1$eR7H0C5r$OrhOQUTXfUEIdvWyeGGGy/

We want to store that piece of information (username and password), inside a single entity: this is the data bag. Let's name it webusers, and we'll store our users under this directory.

  1. Let's create this directory inside our Chef repository for our revision control system (RCS, like git):
    $ mkdir -p data_bags/webusers
    
  2. To create the data bag entry on the Chef server, use the following knife command:
    $ knife data bag create webusers
    
  3. As we know, an entry is simple JSON structured data. Let's write the content of our data bag for our user John, in data_bags/webusers/john.json:
    {
      "id": "john",
      "htpasswd": "$apr1$AUI2Y5pj$0v0PaSlLfc6QxZx1Vx5Se."
    }
  4. Let's do the same for Mary in data_bags/webusers/mary.json
    {
      "id": "mary",
      "htpasswd": "$apr1$eR7H0C5r$OrhOQUTXfUEIdvWyeGGGy/"
    }
  5. Now let's send this data on the Chef server using the knife command:
    $ knife data bag from file webusers mary.json
    Updated data_bag_item[webusers::mary]
    $ knife data bag from file webusers john.json
    Updated data_bag_item[webusers::john]
    

Note

You can see the current entries in the data bag using the knife command:

$ knife data bag show webusers
john
mary

Now the data is globally available from the Chef server, how do we access it dynamically from inside our code? This is where the search feature in Chef is useful to create dynamically generated content.

Before starting any work in the mysite cookbook, let's bump the version in mysite/metadata.rb so we're sure not to break anything:

version '0.2.0'
  1. Let's create a new recipe named htaccess.rb under the mysite cookbook so we can create both the htaccess file under /etc/httpd/htaccess (this is an arbitrary location, adaptable to your needs) and the Apache configuration file under the web root:
    $ chef generate recipe cookbooks/mysite htaccess
    
  2. To have our entries automatically populated in the htaccess file, we'll have to iterate through all existing entries. This is done using the search in Chef, specifying the data bag, and the scope to search (in our case, everything). This is simply added in the mysite/recipes/htaccess.rb file:
    users = search(:webusers, "*:*")
  3. This variable, users, will then be passed to the template file to generate the content, like we did previously—except this time we have multiple entries, not just one. We're using the htpasswd.erb file as a source, that we'll create in a moment:
    template "/etc/httpd/htpasswd" do
      source "htpasswd.erb"
      owner 'root'
      group 'root'
      mode '0660'
      variables(
        :users => users
      )
    end
  4. Generate a new template for the htpasswd file, using the chef command:
    $ chef generate template cookbooks/mysite htpasswd
    
  5. Inside this ERB file in mysite/templates/htpasswd.erb, enter the following:
    <% @users.each do |user| -%>
    <%= user["id"] %>:<%= user["htpasswd"] %>
    <% end -%>

    The .each method loops around the users variable that we passed through the template, iterates on user, and extracts our two values of interest: id and htpasswd.

    While we're at it, let's create the template for the .htaccess file under our web root folder:

    $ chef generate template cookbooks/mysite htaccess
    

    Its content is the most basic we can find:

    AuthType Basic
    AuthName "Restricted Area"
    AuthUserFile /etc/httpd/htpasswd
    Require valid-user

    Note

    There's currently no variable in this template. As I know, files most often end up being dynamic, I always prefer to start them as templates, even if content is currently static. It's very likely that in the near future we'll want to use a variable for AuthUserFile.

  6. Back to our mysite/recipes/htaccess.rb recipe, let's add the template we just created:
    template "/var/www/mysite/.htaccess" do
      source "htaccess.erb"
      owner 'root'
      group 'root'
      mode '0644'
    end

Don't forget the last step: we have to call this new recipe from our main, default.rb recipe! In mysite/recipes/default.rb, include our new recipe, so it gets picked up by the client:

include_recipe "mysite::htaccess"

Just upload the new version of the cookbook:

$ knife cookbook upload mysite

After you've run chef-client on your node, the site will be protected and users mary and john will be able to use basic HTTP authentication.

There's more…

With Puppet, we can do it using Hiera. Hiera can be seen as datastore keeping site information out of manifests. Hiera can be customized in the way data is stored, but this will be out of the scope of this chapter; we will use default configuration.

First of all, we need to define the data in Hiera. This will be done by creating web.pomes.pro.yaml in the Hiera tree:

$ cd puppetcode/hieradata
$ mkdir nodes
$ cat > nodes/web.pomes.pro.yaml
webusers:
 - id: john
   htpasswd: $apr1$AUI2Y5pj$0v0PaSlLfc6QxZx1Vx5Se
 - id: mary
   htpasswd: $apr1$eR7H0C5r$OrhOQUTXfUEIdvWyeGGGy/
^D

This file now contains an array of hashes for authorized users, for the node web.pomes.pro.

From the main manifest, we need to look up our Hiera data using the following code:

$users=hiera('webusers');

Now it's easy to generate the password file using a new apache::htpasswd define statement, that we need to create in modules/apache/manifests/htpasswd.pp:

define apache::htpasswd (
     $filepath,
     $users
) {

  file { "$filepath":
     ensure  => present,
     owner   => 'root',
     group   => 'root',
     mode    => '0644',
     content => template('apache/htpasswd.erb'),
  }
}

For the corresponding template, this time, let's try an ERB template in modules/apache/templates/htpasswd.erb:

<% @users.each do |user| -%>
<%= user['id'] %>:<%= user['htpasswd'] %>
<% end -%>

From the main manifest, we can now create the password file:

apache::htpasswd{'htpasswd':
       filepath => '/etc/apache2/htpasswd',
       users    => hiera('webusers'),
}

We also need to create a .htaccess file. Let's create a new apache::htaccess statement in modules/apache/manifests/htaccess.pp:

define apache::htaccess (
     $filepath,
     $docroot
) {

  file { "$docroot/.htaccess":
     ensure  => present,
     owner   => 'root',
     group   => 'root',
     mode    => '0644',
     content => template('apache/htaccess.erb'),
  }
}

The associated template in modules/apache/templates/htaccess.erb is:

AuthType Basic
AuthName "Restricted Area"
AuthUserFile <%= @filepath %>
Require valid-user

From the main manifest, we can now create the .htaccess file:

apache::htaccess{"$docroot-htaccess":
   filepath => '/etc/apache2/htpasswd',
     docroot  => $docroot,
  }

As a result, here is the main manifest of the web.pomes.pro node:

node 'web.pomes.pro' {
    $website=$fqdn;
    $docroot="/var/www/$fqdn";
    $users=hiera('webusers');

    class {
      'apache':;
      'php':;
      'mariadb':;
    }
    apache::vhost{$website:
       website => $website,
       docroot => $docroot,
    }
    apache::htpasswd{'htpasswd':
       filepath => '/etc/apache2/htpasswd',
       users    => hiera('webusers'),
    }
    apache::htaccess{"$docroot-htaccess":
       filepath => '/etc/apache2/htpasswd',
       docroot  => $docroot,
    }
    file { $docroot:
      ensure => directory,
      owner   => 'www-data',
      group   => 'www-data',
      mode    => '0755',
    }
    file {"$docroot/index.php":
      ensure => present,
      owner   => 'www-data',
      group   => 'www-data',
      mode    => '0644',
      content => "<?php phpinfo() ?>",
    }
}

After running the Puppet agent, http://web.pomes.pro will now ask you for a login/password.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.135.175