I've been a bit quiet on the server front and to be honest a bit stuck. I'm new to Puppet and it took me a while to figure out how to get users working the way I wanted them to. Sleeping on it and reading some more and eventually I got there. I also have a day job to hold down and a family to entertain on the weekend, so as much as I really wanted to get this done life got in the way. My goal now is to get my repositories back online and to create a new one and start committing this Puppet config I've been building somewhere. I decided I'd go with Git for my main projects, the ones that had graduated out of the sandbox but that the sandbox would keep running subversion. I've also planned to run them both via SSH to lower the ports I open and to stick with the same key infrastructure I was building to access everything else. The only problem I had was that I couldn't figure out how to use the same key in multiple places with the otherwise good ssh::auth module from the Puppet patterns wiki. And I wasn't the only one, even the module author thought it was a good idea but difficult to implement. After working on it a few evenings I eventually learned enough about puppet to have a crack at it myself- it may no be the most elegant solution, but it seems to work. I allowed root to login via ssh and turned password authentication on while I did this - I didn't want to lock myself out of my server if I made a mistake. You may want to do the same, or you may not. It's up to you. If you do undo the edits you made to your sshd_config a few days ago In the most simple terms I added a parameter $key throughout ssh::auth which when present indicated you're using a different key, not the one normally used in title.
modules/user/manifests/auth.pp
# ========= # ssh::auth # ========= # # The latest official release and documentation for ssh::auth can always # be found at http://reductivelabs.com/trac/puppet/wiki/Recipes/ModuleSSHAuth . # # Version: 0.3.2 # Release date: 2009-12-29 class ssh::auth { $keymaster_storage = "/var/lib/keys" Exec { path => "/usr/bin:/usr/sbin:/bin:/sbin" } Notify { withpath => false } ########################################################################## # ssh::auth::key # Declare keys. The approach here is just to define a bunch of # virtual resources, representing key files on the keymaster, client, # and server. The virtual keys are then realized by # ssh::auth::{keymaster,client,server}, respectively. The reason for # doing things that way is that it makes ssh::auth::key into a "one # stop shop" where users can declare their keys with all of their # parameters, whether those parameters apply to the keymaster, server, # or client. The real work of creating, installing, and removing keys # is done in the private definitions called by the virtual resources: # ssh_auth_key_{master,server,client}. define key ($ensure = "present", $filename = "", $force = false, $group = "puppet", $home = "", $keytype = "rsa", $length = 2048, $maxdays = "", $mindate = "", $options = "", $user = "", $key = "") { ssh_auth_key_namecheck { "${title}-title": parm => "title", value => $title } # apply defaults $_filename = $filename ? { "" => "id_${keytype}", default => $filename } $_length = $keytype ? { "rsa" => $length, "dsa" => 1024 } $_user = $user ? { "" => regsubst($title, '^([^@]*)@?.*$', '\1'), default => $user, } $_home = $home ? { "" => "/home/$_user", default => $home } ssh_auth_key_namecheck { "${title}-filename": parm => "filename", value => $_filename } @ssh_auth_key_master { $title: ensure => $ensure, force => $force, keytype => $keytype, length => $_length, maxdays => $maxdays, mindate => $mindate, } @ssh_auth_key_client { $title: ensure => $ensure, filename => $_filename, group => $group, home => $_home, user => $_user, } @ssh_auth_key_server { $title: ensure => $ensure, group => $group, home => $_home, options => $options, user => $_user, key => $key, } } ########################################################################## # ssh::auth::keymaster # # Keymaster host: # Create key storage; create, regenerate, and remove key pairs class keymaster { # Set up key storage file { $ssh::auth::keymaster_storage: ensure => directory, owner => puppet, group => puppet, mode => 644, } # Realize all virtual master keys Ssh_auth_key_master } # class keymaster ########################################################################## # ssh::auth::client # # Install generated key pairs onto clients define client ($ensure = "", $filename = "", $group = "", $home = "", $user = "") { # Realize the virtual client keys. # Override the defaults set in ssh::auth::key, as needed. if $ensure { Ssh_auth_key_client { ensure => $ensure } } if $filename { Ssh_auth_key_client { filename => $filename } } if $group { Ssh_auth_key_client { group => $group } } if $user { Ssh_auth_key_client { user => $user, home => "/home/$user" } } if $home { Ssh_auth_key_client { home => $home } } if $key { Ssh_auth_key_client { key => $key } } realize Ssh_auth_key_client[$title] } # define client ########################################################################## # ssh::auth::server # # Install public keys onto clients define server ($ensure = "", $group = "", $home = "", $options = "", $user = "", $key="") { # Realize the virtual server keys. # Override the defaults set in ssh::auth::key, as needed. if $ensure { Ssh_auth_key_server { ensure => $ensure } } if $group { Ssh_auth_key_server { group => $group } } if $options { Ssh_auth_key_server { options => $options } } if $user { Ssh_auth_key_server { user => $user, home => "/home/$user" } } if $home { Ssh_auth_key_server { home => $home } } if $key { Ssh_auth_key_server { key => $key } } realize Ssh_auth_key_server[$title] } # define server } # class ssh::auth ########################################################################## # ssh_auth_key_master # # Create/regenerate/remove a key pair on the keymaster. # This definition is private, i.e. it is not intended to be called directly by users. # ssh::auth::key calls it to create virtual keys, which are realized in ssh::auth::keymaster. define ssh_auth_key_master ($ensure, $force, $keytype, $length, $maxdays, $mindate) { Exec { path => "/usr/bin:/usr/sbin:/bin:/sbin" } File { owner => puppet, group => puppet, mode => 600, } $keydir = "${ssh::auth::keymaster_storage}/${title}" $keyfile = "${keydir}/key" file { "$keydir": ensure => directory, mode => 644; "$keyfile": ensure => $ensure; "${keyfile}.pub": ensure => $ensure, mode => 644; } if $ensure == "present" { # Remove the existing key pair, if # * $force is true, or # * $maxdays or $mindate criteria aren't met, or # * $keytype or $length have changed $keycontent = file("${keyfile}.pub", "/dev/null") if $keycontent { if $force { $reason = "force=true" } if !$reason and $mindate and generate("/usr/bin/find", $keyfile, "!", "-newermt", "${mindate}") { $reason = "created before ${mindate}" } if !$reason and $maxdays and generate("/usr/bin/find", $keyfile, "-mtime", "+${maxdays}") { $reason = "older than ${maxdays} days" } if !$reason and $keycontent =~ /^ssh-... [^ ]+ (...) (\d+)$/ { if $keytype != $1 { $reason = "keytype changed: $1 -> $keytype" } else { if $length != $2 { $reason = "length changed: $2 -> $length" } } } if $reason { exec { "Revoke previous key ${title}: ${reason}": command => "rm $keyfile ${keyfile}.pub", before => Exec["Create key $title: $keytype, $length bits"], } } } # Create the key pair. # We "repurpose" the comment field in public keys on the keymaster to # store data about the key, i.e. $keytype and $length. This avoids # having to rerun ssh-keygen -l on every key at every run to determine # the key length. exec { "Create key $title: $keytype, $length bits": command => "ssh-keygen -t ${keytype} -b ${length} -f ${keyfile} -C \"${keytype} ${length}\" -N \"\"", user => "puppet", group => "puppet", creates => $keyfile, require => File[$keydir], before => File[$keyfile, "${keyfile}.pub"], } } # if $ensure == "present" } # define ssh_auth_key_master ########################################################################## # ssh_auth_key_client # # Install a key pair into a user's account. # This definition is private, i.e. it is not intended to be called directly by users. define ssh_auth_key_client ($ensure, $filename, $group, $home, $user) { File { owner => $user, group => $group, mode => 600, require => [ User[$user], File[$home]], } $key_src_file = "${ssh::auth::keymaster_storage}/${title}/key" # on the keymaster $key_tgt_file = "${home}/.ssh/${filename}" # on the client $key_src_content_pub = file("${key_src_file}.pub", "/dev/null") if $ensure == "absent" or $key_src_content_pub =~ /^(ssh-...) ([^ ]+)/ { $keytype = $1 $modulus = $2 file { $key_tgt_file: ensure => $ensure, content => file($key_src_file, "/dev/null"); "${key_tgt_file}.pub": ensure => $ensure, content => "$keytype $modulus $title\n", mode => 644; } } else { notify { "Private key file $key_src_file for key $title not found on keymaster; skipping ensure => present": } } } # define ssh_auth_key_client ########################################################################## # ssh_auth_key_server # # Install a public key into a server user's authorized_keys(5) file. # This definition is private, i.e. it is not intended to be called directly by users. define ssh_auth_key_server ($ensure, $group, $home, $options, $user, $key) { # on the keymaster: $key_src_dir = "${ssh::auth::keymaster_storage}/${key}" $key_src_file = "${key_src_dir}/key.pub" # on the server: $key_tgt_file = "${home}/.ssh/authorized_keys" File { owner => $user, group => $group, require => User[$user], mode => 600, } Ssh_authorized_key { user => $user, target => $key_tgt_file, } if $ensure == "absent" { ssh_authorized_key { $title: ensure => "absent" } } else { $key_src_content = file($key_src_file, "/dev/null") if ! $key_src_content { notify { "Public key file $key_src_file for key $_key not found on keymaster; skipping ensure => present": } } else { if $ensure == "present" and $key_src_content !~ /^(ssh-...) ([^ ]*)/ { err("Can't parse public key file $key_src_file") notify { "Can't parse public key file $key_src_file for key $_key on the keymaster: skipping ensure => $ensure": } } else { $keytype = $1 $modulus = $2 ssh_authorized_key { $title: ensure => "present", type => $keytype, key => $modulus, options => $options ? { "" => undef, default => $options }, } }} # if ... else ... else } # if ... else } # define ssh_auth_key_server ########################################################################## # ssh_auth_key_namecheck # # Check a name (e.g. key title or filename) for the allowed form define ssh_auth_key_namecheck ($parm, $value) { if $value !~ /^[A-Za-z0-9]/ { fail("ssh::auth::key: $parm '$value' not allowed: must begin with a letter or digit") } if $value !~ /^[A-Za-z0-9_.:@-]+$/ { fail("ssh::auth::key: $parm '$value' not allowed: may only contain the characters A-Za-z0-9_.:@-") } } # define namecheck
I won't go through all the changes line by line, the important things to note are that I added the variable $key and limited the ssh_auth_key_master automatic realisation to only calls that have not provided a key. Basically if you give it a key you're trying to use an exisiting key, otherwise you're looking to create a new key for a user.

Usage

Using the new calls is easy. Assuming you've cerated your user addding the following lines will allow the previously created andrewmccall key to login as root:
manifests/nodes.pp
node build { include sudo, user, sshd, ssh::auth, user::keystore, repos user::create{"andrewmccall": groups => "sudo"} user::client_key{"andrewmccall":} user::server_key{"andrewmccall":} ssh::auth::key{"andrewmccall-root": key=>"andrewmccall"} ssh::auth::server{"andrewmccall-root": key=>"andrewmccall", user=>"root", home=>"/root"} }
In lines 7 & 8 you can see I use the andrewmccall key to allow logins as root. Apologies for the brevity of the post, I guess you can probably tell I'm sick of looking at this code. If you do have any questions or there is any interest in it I'll elaborate in the comments or a future post.