Using Puppet to remove SSH keys not explicitly allowed

I'm using puppet to distribute SSH keys, like so:

ssh_authorized_key { "[email protected]":
   ensure => present,
   key => 'xxxx',
   type => 'ssh-rsa',
   user => 'deploy',
}

The ~/.ssh/authorized_keys file ends up containing a mix of keys from multiple classes, which is the desired result. However, if a key is manually added to $HOME/.ssh/authorized_keys, Puppet will leave it in place. Is there a way to always remove any key that has not been explicitly defined in a manifest?

I have puppet version 2.7.1.


Starting with Puppet 3.6 it is now possible to purge unmanaged SSH authorized keys via the user type. For example,

user { 'nick':
  ensure         => present,
  purge_ssh_keys => true,
}

Instead of using ssh_authorized_key resources, I decided to define an authorized_keys resource, which takes a list of all SSH keys for a single user. The define looks like this:

define authorized_keys ($sshkeys, $ensure = "present", $home = '') {
    # This line allows default homedir based on $title variable.
    # If $home is empty, the default is used.
    $homedir = $home ? {'' => "/home/${title}", default => $home}
    file {
        "${homedir}/.ssh":
            ensure  => "directory",
            owner   => $title,
            group   => $title,
            mode    => 700,
            require => User[$title];
        "${homedir}/.ssh/authorized_keys":
            ensure  => $ensure,
            owner   => $ensure ? {'present' => $title, default => undef },
            group   => $ensure ? {'present' => $title, default => undef },
            mode    => 600,
            require => File["${homedir}/.ssh"],
            content => template("authorized_keys.erb");
    }
}

$ssh_keys parameter takes all necessary keys as a list. The authorized_keys.erb template looks like this:

# NOTICE: This file is autogenerated by Puppet and should not be modified
<% sshkeys.each do |key| -%>
<%= key %>
<% end -%>

Usage

user {'mikko':
    ...
}
authorized_keys {'mikko':
    sshkeys => [
        'ssh-rsa XXXXXXYYYYYYYYYZZZZZZZZZ [email protected]',
        'ssh-rsa XXXXXXZZZZZZZZZHHHHHHHHH [email protected]',
    ],
}

Adding SSH keys conditionally (for example in different classes) is also easy, thanks to Puppet's +> operator:

Authorized_keys <| title == 'mikko' |> {
    sshkeys +> 'ssh-rsa ASDFASDFASDFASDF [email protected]'
}

With this method, the user will never have keys that are not explicitly specified in the Puppet configuration. The key string is used in authorized_keys just as it is, so adding options and restrictions is trivial.

I would be happy to hear if others have used this method successfully!


You should be able to do this using the resources metatype. E.G.

resources { 'ssh_authorized_key': noop => true, purge => true, }

Setting noop => true, prevents the removal from taking place. Instead, puppet will report what would be removed. If it is what you want, remove the noop statement.

The ideal syntax for performing operations on unmanaged resources is under discussion.

EDIT: As mentioned in the comments, this answer does not work.


On Puppet Forge a module has been published under the Apache License, Version 2.0 that offers this ability.

It relies on Puppet concat instead of templates though.

https://github.com/nightfly19/puppet-ssh_keys/tree/master/manifests

Instead of passing an array of keys as a parameter you define separate entries for each key.

Different approach from Mikko's, but same net result.