Restrict access in Puppet - part 1

This time we're going to lock the server down a bit with Puppet. Now that we have our own user, let's force login using a public key we'll create for the user. Eventually I also want to create a self signed certificate for the user to use with their public key to access resources via HTTPS with client certificates wrap the whole certificate up in a pkcs12 file and email it to them. I won't get into that yet. I was going to write the framework myself, even had a skeleton all laid out - I'd decided on two classes, the keystore which would handle creating, storing and revoking keys and later certificates and a user class for creating users.
  • create <user> creates a new user with the given username.
  • clientkey <user> [filename] installs the key for a given user, optionally set a filename. I already have some keys
  • serverkey <user> [login_as] installs the key into the authorized_keys allowing users to login to this server. Defaults to logging in as the same user as the key 'owner' if the. I also need to use my key to login as another user, the git and svn users for example.
Then I found the ssh::auth from the puppet wiki. it's released under the GPL, while everything else I write is Apache 2 compatible - if that's a problem you may want to stop here and look for another solution. If you find one that's Apache 2 compatible, I'd love to know about it. For what I'm doing it doesn't really matter, I went with my skeleton package as a thin wrapper around it and downloaded the module into my users package.
# wget http://projects.reductivelabs.com/attachments/download/935/auth.pp -O ./modules/user/manifests/auth.pp
The first thing I did was create a new keystore.pp file, the ssh::auth package made that pretty easy:
modules/user/manifests/keystore.pp
class user::keystore {
    include ssh::auth::keymaster
}
Then I needed to do was create a keystore. The keystore is just the host you nominate to control all your client and server keys. To do that in nodes.pp I properly named my only node and stopped using the default and added a couple of new includes.
manifests/nodes.pp
node build {
    include sudo, user, ssh::auth, user::keystore
}
Run puppet and you should end up with a directory
/var/lib/keys
ready to store keys.
# puppet -v --modulepath=/etc/puppet/modules /etc/puppet/manifests/site.pp
I decided I'd update my user package by removing some files and putting the user class into the init.pp file. I had too many files in there that just didn't seem to be doing anything.
# rm modules/user/manifests/virtual.pp
# rm modules/user/manifests/unixadmins.pp
Then I added a create method in the user class in my init.pp file:
modules/users/manifests/init.pp
import "*"
class user {

    define create {
            @user { "$title":
            ensure  => "present",
            gid   => "$title",
            home    => "/home/$title",
            shell   => "/bin/bash",
            managehome => true,
            require => [Group["$title"]],
        }
        @file { "/home/$title":
            ensure => "directory",
            mode   => 700,
            owner  => "$title",
            group  => "$title",
        }
        @file { "/home/$title/.ssh":
            ensure => "directory",
            mode   => 600,
            owner  => "$title",
            group  => "$title",
        }
        @group { "$title":
            ensure  => "present",
        }

        realize User[$title]
        realize Group[$title]
        realize File["/home/$title"]
        realize File["/home/$title/.ssh"]
        ssh::auth::key{"$title":}
    }
    define client_key ($ensure = "", $filename = "") {
        ssh::auth::client{"$title": ensure=>$ensure, filename=>$filename}
    }
    define server_key ($ensure = "", $user = "") {
        ssh::auth::server{"$title": ensure=>$ensure, user=>$user}
    }
}
This code defines a new method called create, and realises all the required resources, finally it generates a new key for the user. Now we need to update our node to create ourselves a user using this new method
manifests/nodes.pp
node my-server {
    include sudo, ssh::auth, ssh::auth::keymaster, user
    user::create{"andrewmccall":}
    user::client_key{"andrewmccall":}
    user::server_key{"andrewmccall":}
}
The last step is to copy the private and public key down to a machine. It's important that you test ssh logins to this host using the new keys. Otherwise you risk locking yourself out for good! I think that's probably a good place for me to call it a night - tomorrow I'll be locking down sshd so that only users with keys can login.

Groups in puppet.

Last time I ended up not being able to create my user because I didn't have a group for it and ran out of time before I needed to call it a night. So I'll start tonight by creating a group for my user. Go to the puppet config dir:
# cd /etc/puppet
Then edit the virtual file we created in our users module last time and add a group, like this:
users/manifests/virtual.pp
class user::virtual {
    @user { "andrewmccall":
        ensure  => "present",
        uid     => "1001",
        gid     => "1001",
        comment => "Andrew McCall",
        home    => "/home/andrewmccall",
        shell   => "/bin/bash",
        managehome => true,
        require => [Group["andrewmccall"]],
    }

    @group { "andrewmccall":
        ensure  => "present",
        gid     => "1001", 
    }

}
Next in the unixadmins file we need to realize our new group, it should look like this:
users/manifests/virtual.pp
class user::unixadmins inherits user::virtual {
    realize(
        Group["andrewmccall"],
        User["andrewmccall"]
    )
}
Run puppet on the local files and you'll have a new user and a group for it. It should look something like this:
# puppet -v --modulepath=/etc/puppet/modules /etc/puppet/manifests/site.pp
info: Autoloaded module sudo
info: Autoloaded module user
info: Applying configuration version '1281368198'
notice: //user::virtual/Group[andrewmccall]/ensure: created
notice: //user::virtual/User[andrewmccall]/ensure: created
Don't forget to commit the changes to the git repo.
# git add .
# git commit -a -m "Added a users module to manage users"

Setting up users and securing my server.

Last night I got the server upgraded to ububtu 10.04 and installed puppet. The first and only recipe we've got is one to make sure our sudoers file has the proper permissions. Tonight I'm hoping to:
  • Add myself a user
  • Automatically create me ssh key
  • Prevent root from logging in via ssh
  • Prevent users from logging in other than with a key.
  • It would be nice to be able to email me my key since I don't run puppet on my mac, though I may be convinced to go down that route if it proves too difficult.
Longer term I'm hoping to extend this same process to create user certificates for https client certificate authentication and it would be nice to use the same key, certificates and revocation process to issue new credential to users or to even lock them out. Bearing that all in mind, but not getting too hung up on stuff I'm doing later, off we go.

Create the users module

We're going to do this pretty much straight out of the puppet best practice guide, the first thing we'll do is flesh out our users module.
# cd /etc/puppet
# mkdir -p modules/user/manifests
Then create a virtual users file:
modules/user/manifests/virtual.pp
# virtual.pp
#
# People accounts of interest as virtual resources
class user::virtual {
    @user { "andrewmccall":
        ensure  => "present",
        uid     => "1001",
        gid     => "1001",
        comment => "Andrew McCall",
        home    => "/home/andrewmccall",
        shell   => "/bin/bash",
    }
}
Next let's move me from a virtual user to an actual user
modules/users/manifests/unixadmins.pp
# unixadmins.pp
#
# Realize the members of the Unix team and include any contractors

class user::unixadmins inherits user::virtual {
    # Realize our team members
    realize(
        User["andrewmccall"]
    )
}
Next we need to create an init.pp for the module.
modules/users/manifests/init.pp
import "*"
class user {
    include user::virtual, user::unixadmins
}
Now we need to go back and update the site.pp - at this stage I also add a node.pp below to manage individual hosts.
manifests/site.pp
# site.pp

import "nodes"

Exec { path => "/usr/bin:/usr/sbin/:/bin:/sbin" }
manifests/site.pp
# node.pp

node default {
  include sudo, user
}
Once I got that all setup I ran puppet from the command line:
# puppet -v --modulepath=/etc/puppet/modules /etc/puppet/manifests/site.pp
And I got an error:
info: Autoloaded module sudo
info: Autoloaded module user
info: Applying configuration version '1281041300'
err: //user::virtual/User[andrewmccall]/ensure: change from absent to present failed: Could not create user andrewmccall: Execution of '/usr/sbin/useradd -u 1001 -g 1001 -s /bin/bash -c Andrew McCall -d /home/andrewmccall andrewmccall' returned 6: useradd: group '1001' does not exist
Unfortunately I'm going to have to leave it at that for the night, see you tomorrow.

Upgrading my server and installing puppet

I woke up this morning to a fresh install of Ubuntu 8.04 I was just itching to configure, but I had to work. So it wasn't until after the kids got fed and put to bed I could get started. What I'm aiming to accomplish tonight it to get the server configured and upgraded. The first thing I did was trim the install, I don't really want a LAMP server. I want to cut everything I don't need. The only daemon process I want is SSH at the moment so I went into aptitude and selectively removed packages I didn't want. It's worth noting here that if you're removing packages that you've no intention of installing again, make sure you do an apt-get purge or equivalent so you lose the config as well. You could probably do this with puppet and I want to automate as much of this process as I can, but I can't figure out how I can automate the upgrade, unfortunately, so I'm not going to bother with anything until I get 10.04 installed. The next step was to run the upgrade by installing the upgrade manager, and launching the process.
# apt-get install update-manager-core
# do-release-upgrade -p -m server
You'll be asked a few questions, I went for all the defaults except when asked about the grub menu. I chose to throw away the existing config and go with the package maintainer's - it's the only one that worked for me. Eventually you'll be told the upgrade is complete and asked if you want to reboot. You and I now have an Ubuntu 10.04 LTS server, next stop puppet.

Puppet

The first big question I started having about my infrastructure was how I wanted to send the config to the servers. It wasn't such a big deal with this install, since it was just the one box. There are two main ways of managing config with puppet, the first is to use the bundled puppetmaster server. There is one puppetmaster, each client machine contacts at regular intervals and asks it if there are any changes and applies the ones that it needs. Out of the box they say it's good to 20-30 machines, you can tweak a few things to get a little more out of it or run it in a more powerful web process. The second way of running it is as a more or less standalone system. You update the config however you choose on each node, then execute the puppet command to apply it. The real benefit here is you control when the config is applied. One simple way of doing that is with post-recieve or post-update hooks in git and there is an excellent article from Bitfield Consulting about doing just that. I'm leaning towards manging the Hadoop cluster I'm looking to manage with puppet after this initial install in a very similar manner. I want to either make a change and have the change pushed immediately across the cluster or control the deployment of a change - it wouldn't do for the whole cluster to suddenly go down to update some software at exactly the same time. Part of the point of using puppet on top of hudson and the other tools I'll be getting into over the coming weeks and months is to allow me to push changes across the cluster, but more importantly roll back the bad ones. Let's get the packages
# apt-get install puppet git-core
By default puppet won't be configured to start puppetd, which is fine - we'll be triggering it manually.

Chicken or the egg

We want to store all of our configuration in git, but we also need puppet to manage and ensure there is a git repository on the server that's storing the config. With git it's actually pretty easy, we can create a repo in place, commit to it, then add the actual remote repo later.
# cd /etc/puppet
# mkdir modules
# git init .
That's our repository created, the only change we want to make is we need to ignore the puppet.conf file - we'll be managing that with puppet as well. Create the file
/etc/puppet/.gitignore
puppet.conf
Basically once we've set the repository up, managing a machine with puppet should be as simple as cloning the repository and running puppet. So let's get there - Now that everything seems to be setup properly, let's create a simple puppet recipe, as is the standard, let's enforce some permissions on /etc/sudoers, just to make sure it all works. This is a bit of an extension to the standard first recipe, but we'll be extending it later.

Sudoers module

First let's create the package, this is a pretty standard layout most modules will follow.
# cd /etc/puppet/modules
# mkdir -p sudo/files sudo/manifests
Create the following files:
sudo/manifests/init.pp
import "*"
class sudo {
    include sudo::install, sudo::service
}
sudo/manifests/install.pp
class sudo::install {
  package{ "sudo": ensure => installed,}
}
sudo/manifests/sudoers.pp
class sudo::sudoers {

  file { "/tmp/sudoers":
    mode => 440,
    source => "puppet:///modules/sudo/sudoers",
    notify => Exec["check-sudoers"],
  }

  exec { "check-sudoers":
    command => "/usr/sbin/visudo -cf /tmp/sudoers && cp /tmp/sudoers /etc/sudoers",
    refreshonly => true,
  }

}

Set the rest of puppet up to call the module

Finally we need to set up puppet to call the module we've just created. We need to create a site.pp, everything else is loaded from there. In our installation we could call the file anything we like, but to keep things 'standard' we'll keep it as site.pp. Create the file
/etc/puppet/manifests/site.pp
import "sudo"

node default {
  include sudo::install
  include sudo::sudoers
}
That's it, puppet should now be up and running - test your configuration by executing the command:
# puppet -v --modulepath=/etc/puppet/modules /etc/puppet/manifests/site.pp

Finished for the day.

That's about all I can manage tonight. Tomorrow I'll continue with the puppet configuration, get it creating users and lock the server down a bit. After that I'll finally be able to start installing the software I need on the box.

Let's use puppet instead.

So I've just trashed a perfectly good, working server install. That's right, I threw it all away, and for what you ask? For you gentle reader... and because I found a new toy that I wanted to play with, puppet. Puppet is a configuration management tool, Adobe use it to manage their hadoop cluster and it was something I knew I wanted to look at when it came time to start to manage mine. It wasn't really until I came to the process of writing down all the steps I took setting my server up, so I could reproduce the process if necessary that it occurred to me puppet was something I could should use. The previous post I'd partly written for today  was about the process of getting my server, updating it to Ubuntu 10.04 and generally getting started. Basically I was breaking the steps I took setting my server up into bite sized chunks and them writing down, from memory. For general background see yesterday's post, most of that still applies. Also reading the Atlassian Dragon Quest is useful background, the biggest difference between that and my install is probably that I'm using MySQL instead of Postgress. There isn't any real reason except I'm familiar with it and so tend to prefer it. I'll also be using hudson, not bamboo - I don't have anything against Bamboo specifically, but 10 plans wont be enough for me and I'm not willing to splash the extra cash to get more when hudson is free and in some respects better suits my needs. On top of the Atlassian stuff I'm also installing nexus, putting everything behind nginx so that it shows up neatly as a single host and locking most of the site away behind client SSL certificates. With a few holes poked through for public services. I also assume you've got a domain and that it's properly pointed at the server. I'll be using dev.andrewmccall.com throughout - which coincidentally, is mine. So, without further ado, let's get going.

The Server

I had a look around at the cost of servers, thought about hosting things at amazon, rackspace or elsewhere in the cloud but in the end settled on a hosted server at Server4You. It was recommended by a friend as fairly cheap and reliable, the server is in the EU so it's quicker for me to access than going across the atlantic. The signup process was fairly painless, I even got a phone call from a real person welcoming me and telling me my server would be online soon. After a few hours I got an email telling me it was online and ready to go. I just had to log in and select my OS. I logged in and was unsurprised to find that Server4You, like most most hosting companies, seem to only offer Ubuntu 8.10 LTS, so I installed the Ubuntu 8.04 LTS - LAMP - 64bit image. The same image I'm in the process of overwriting my fully setup server with right now. It can take up to 2 hours, so I'm going to call it a night at that and I'll carry on tomorrow.

New development server

Things around here have stalled a bit I've not had much free time what with a new job and travelling. The time I have had I've spent getting a build server setup. Replacing my ageing trac/subversion install is something I've meant to do for a while and just never got around to. Ultimately I'm working on a continuous deployment solution for Sproozi, but while I do that I'm also keen to get my other public and private projects into a CI build cycle and get my maven artifacts deployed so they're useful. The other major goal was to move all my main projects out of trac/svn and into JIRA/Confluence/Fisheye/Git. Trac and subversion are fine tools, and they've worked well for me for many years, but trac suffers when it's running more than one project; you either need to work around the tool or have separate installs per project and I can get JIRA, Fisheye and Confluence for $10 each which is a great deal. I have no real complaints about svn, except I've started using git as my preferred client and I'm sick of typing 'git svn command' whenever I want to do anything. Also my subversion repository holds a fair bit of code from a few projects both new, old and dormant as it grows checking out gets irritating. I'm happy with the move to GitHub and public hosting of my other projects, I like git and I liked the idea of promoting projects into repositories of their own and git lets me take the history with the project. So I've decided I'll keep my subversion repository, use it as I do now as a sandbox to keep code I'm working on in, for code I don't think is quite ready to be promoted into a project of it's own. When a project is ready I'll move it (and all of it's history) out of subversion and into a git repository of it's own. My public projects are all in GitHub and I don't see any point in using my local tools to manage them in anyway. So I'll keep them all over there. Any new ones will get promoted over there too. Over the course of the next few days/weeks I'll document what I did here, partly in case anyone else cares, partly so I can do it again if I ever need to. I'm not sure how many posts it will take me because I'm not quite finished the process yet.
Media_httpimgzemantac_fujbt