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).
To work through this recipe, you will need the following:
Our objective is to create two users—John and Mary. Here's a table of the required information:
User |
Password |
Hash |
---|---|---|
John |
|
|
Mary |
|
|
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.
$ mkdir -p data_bags/webusers
knife
command:$ knife data bag create webusers
data_bags/webusers/john.json
:{ "id": "john", "htpasswd": "$apr1$AUI2Y5pj$0v0PaSlLfc6QxZx1Vx5Se." }
data_bags/webusers/mary.json
{ "id": "mary", "htpasswd": "$apr1$eR7H0C5r$OrhOQUTXfUEIdvWyeGGGy/" }
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]
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'
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
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, "*:*")
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
htpasswd
file, using the chef
command:$ chef generate template cookbooks/mysite htpasswd
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
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.
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.
52.15.135.175