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 -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:
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.
node build { include sudo, user, ssh::auth, user::keystore }
Run puppet and you should end up with a directory
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:
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
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.