The Puppet Labs Issue Tracker has Moved:

This issue tracker is now in read-only archive mode and automatic ticket export has been disabled. Redmine users will need to create a new JIRA account to file tickets using See the following page for information on filing tickets with JIRA:


ssh::auth is currently in an unstable release. Please test it, and report bugs and feature requests on the Puppet users mailing list. Based on the results of people’s testing, the interface may change for the first stable release.


ssh::auth provides centralized creation, distribution, and revocation of ssh keys for users. There are three types of hosts involved:

  • keymaster – the host that creates and distributes ssh keys.
  • server – a host running an ssh server.
  • client – a host with users who want to log in to servers.

One host may fulfil multiple roles. At present though, the keymaster has to be located on the same host as the puppetmaster, since all files in Puppet are read on the puppetmaster.


  • Each user may have one or more ssh key pairs, centrally created on the keymaster and distributed to servers and clients.
  • Each key pair may be installed onto any set of clients, and enabled for authentication as any user(s) on any set of servers.
  • Keys may have login options set as in authorized_keys(5), e.g. to force certain commands to run or limit port forwarding.
  • Keys can be uninstalled or revoked, either manually or automatically at given intervals, and new ones automatically created and distributed.

Possible future features:

  • Allow revoked keys to remain authorized for X days before they’re removed (gracedays).
  • Allow one key to be authorized for multiple users.
  • Automatically remove any key that’s not managed (purge).
  • ssh::auth::server and ssh::auth::client accept just ‘*’ as an argument, to distribute all available keys.

ssh::auth aims to provide a complete solution for managing ssh keys for users, with a well-defined and -documented interface. You may want to compare it to Recipes/ModuleSSHAuth available in Puppet.

Installation and Requirements

To install ssh::auth, place the file auth.pp (attached to this page) into the ssh/manifests directory in your module path: for example, /etc/puppet/modules/ssh/manifests/auth.pp.

ssh::auth has some modest requirements on the keymaster:

  • ssh-keygen(1) (provided by e.g. the openssh-client package in Debian) must be present on the keymaster, or include ssh::auth::keymaster will fail.
  • find(1) on the keymaster must support the -path, -mtime, and -newermt options. GNU find supports these options.

No other ssh commands or packages are required, and there are no additional requirements (other than Puppet itself) on the clients or servers.

Basic Usage

To create and distribute a key pair for user alice:

include ssh::auth

ssh::auth::key { "alice": } # declare a key for alice: RSA, 2048 bits

node keymaster {
  include ssh::auth::keymaster

node sshclient {
  ssh::auth::client { "alice": }

node sshserver {
  ssh::auth::server { "alice": }

This example creates an RSA key pair for alice on the keymaster. It installs the key pair into her \~/.ssh directory on sshclient (the file names are id_rsa and, the defaults). Finally, it installs the public key of the pair into her \~/.ssh/authorized_keys file on sshserver, so that she can log in there using the private key.

In this example “alice” is just the title of the generated key. By default the ssh::auth functions also take it to be the name of the key’s user. The Recipes/ModuleSSHAuth below describes how to override the defaults and set many key-related parameters.


Throughout the examples, assume that we have users alice and bob, and nodes named keymaster, sshserver, and sshclient, with the obvious interpretations.

1. With account management

ssh::auth doesn’t manage users or their accounts; it just manages ssh keys. That’s a feature: it provides the key building blocks, and you can combine them with users in any way you wish.

If you want to manage users and their keys together, it’s easy to do with definitions. For example, you can start by declaring virtual users and their keys together in the global scope:

define sshuser {
  @user { $title: }
  @file { "/home/${title}/.ssh":
    ensure => "directory",
    mode   => 600,
    owner  => $title,
    group  => "puppet",
  ssh::auth::key { "${title}": }
sshuser {[ "alice", "bob" ]:}

Note that you also have to ensure that a user’s $home/.ssh directory exists. On a client host, you can then realize the users and install their keys:

define sshclientuser {
  realize User[$title]
  realize File["/home/${title}/.ssh"]
  ssh::auth::client { "${title}": }
node sshclient { sshclientuser {[ "alice", "bob" ]: } }

After the above, users alice and bob would both exist on sshclient, and have their private keys installed there. On a server you’d do the analogous thing, but suppose we allow more flexibility by providing the ensure parameter:

define sshserveruser ($ensure = "present") {
  realize File["/home/${title}/.ssh"]
  User <| title == $title |> { ensure => $ensure }
  ssh::auth::server { "${title}": ensure => $ensure }
node sshserver1 {
  sshserveruser {
    "alice": ;
    "bob"  : ;
node sshserver2 {
  sshserveruser {
    "alice": ensure => "absent";
    "bob"  : ;

Now both alice and bob can authenticate to sshserver1 using their private keys on sshclient. On sshserver2, bob exists and can authenticate, but alice doesn’t and can’t.

Every call to ssh::auth::client and ssh::auth::server implicitly includes a require => [User[$title], File[“$home/.ssh”]]. So you don’t have to include those in your code— it’s done for you.

2. Multiple keys for one server user

Any set of users can be allowed to authenticate with their private keys as another user. For example:

node sshserver {
  ssh::auth::server { ["alice", "bob"]: user => "admin" }

Here alice’s and bob’s public keys are installed into admin’s authorized_keys file on sshserver. alice and bob may now both authenticate there as user admin.

3. Multiple keys for one client user

For most purposes, it’s not a good idea to give a user more than one private key to authenticate with. A user should be identified with exactly one key pair, so that that key always represents that person on your network. But you can give them more than one if you want to:

ssh::auth::key {
  "alice@server1": filename => "server1" ;
  "alice@server2": filename => "server2" ;
node sshclient {
  ssh::auth::client {[ "alice@server1", "alice@server2" ]:}
node sshserver1 { ssh::auth::server { "alice@server1": } }
node sshserver2 { ssh::auth::server { "alice@server2": } }

Just be sure that the keys have different Recipes/ModuleSSHAuth s, or the keys will copy over each other and the result will be unpredictable. It may even change at each run :(

Really though, I can’t think of any good reason why you’d want to do this. You’re better off keeping a one-to-one correspondence between users and keys. Remember: the user and the key are one. Still, the capability is there.

You might think of giving a user a group key in addition to their individual key, something like:

ssh::auth::key { "admin": user => ["alice", "bob"] }  # INVALID

However ssh::auth doesn’t support assigning a single private key to multiple users. Fortunately, again there’s no reason you’d need to do this, since you can accomplish the same thing by allowing alice and bob to authenticate as admin with their individual keys, as in the previous example. That’s better in fact, since you can tell whether it was alice or bob who logged in as admin, by looking at the key fingerprint in the log.

4. Revoking keys

a. User is gone

alice has left the network. We want to remove her key from the keymaster, servers, and clients (although this isn’t sufficient to revoke her access; see Recipes/ModuleSSHAuth below).

ssh::auth::key { "alice": ensure => "absent" } } # in the global scope

node sshserver { ssh::auth::server { "alice": } }
node sshclient { ssh::auth::client { "alice": } }

Note that we set alice’s key to be absent in the main declaration, and then leave it in the node definitions, at least for now. If we remove it from the definitions, it won’t be managed any more and so will presumably be left in place on the nodes! By leaving it in the node definitions, we allow ensure => “absent” to propagate out to the nodes, so that Puppet removes the key. At some later time when we’re sure that the key has been removed from all of the hosts, we can remove its declarations from the manifests. (You don’t have to do anything else to remove the key from the keymaster; it will already be removed because of ensure => “absent”.) See Recipes/ModuleSSHAuth .

b. Compromised key

alice is still with us, but her key has been compromised. We want to revoke the existing key, and create and distribute a new one. If today is November 30, 2009, then a simple way to do this is:

ssh::auth::key { "alice": mindate => "2009-11-30" }

This says that any existing “alice” key created before 2009-11-30 should be removed, and a new one generated in its place. The new key will be distributed to clients and servers on their next Puppet runs. If a client host was compromised too, be sure to remove it from your manifests so that it doesn’t receive alice’s new key!

Another way to force replacement of an existing key is:

ssh::auth::key { "alice": force => true }

The force option always forces regeneration of the target key. That’s a good quick fix for a compromised key, but you wouldn’t want to leave it in place, or alice would get a new key every time Puppet ran, i.e. every 30 minutes. The mindate parameter is better because you can leave it in place, and it will also serve to document when you last replaced the key.

c. Rotating keys

We want to replace alice and bob’s keys every 90 days:

ssh::auth::key {[ "alice", "bob" ]: maxdays => 90 }

As soon as either alice or bob’s key becomes older than 90 days, a new key will be created, then distributed to clients and servers on their next Puppet runs.

d. Revoking authorization

alice and her key are fine, but we want to revoke her authorization to use a server. On that node only, we override the ensure parameter for her key:

node sshserver { ssh::auth::server { "alice": ensure => "absent" } }

Again this declaration should stay in place at least until we’re sure that the key has been removed from the server.

Warnings and Limitations

Key synchronization

After you expire and regenerate a key, during the interval between a client and server’s next Puppet runs, the keys will be different on the two hosts and authentication will be denied. The problem will be resolved once both hosts have completed their runs and received the new key, i.e. within 30 minutes on a typical installation. Even so, the user will of course still be unable to authenticate from any host that hasn’t been updated using ssh::auth::client. Be prepared to provide the new key to them in an alternate, secure fashion. You can always find the current set of managed keys in $ssh::auth::keymaster_storage (e.g. /var/lib/keys) on the keymaster.

Denying access

Revoking a user’s ssh keys isn’t generally sufficient to deny her access to a host. She might still log in by password, or she might already have authorized another key for her account. Removing unwanted keys is good housekeeping, but to deny a user access to the host, you need to disable her account.

Control of key files

The previous point illustrates that when a user has write access to his authorized_keys file, you can’t effectively limit which keys can authenticate him, or enforce access Recipes/ModuleSSHAuth on his allowed keys. The reason is that he can simply overwrite authorized_keys with a version more to his liking. You may be tempted to exert more control by making root the owner of authorized_keys, and denying write access to the user. That approach will generally fail, because all the user has to do is mv \~/.ssh/authorized_keys othername — in Unix that operation requires write access only on the containing directory, not on the file itself. Well okay then, suppose you also deny the user write access to \~/.ssh? I’ll leave it as an exercise to show that in order for that to work, you’d then have to also deny the user write access to his own home directory — an undesirable situation.

Other solutions, such as making the key files immutable or keeping them in a root-controlled repository, are possible but also have drawbacks. We won’t discuss those further here, but will just note that for now, ssh::auth doesn’t provide an option to change the ownership of authorized_keys. If you need this functionality, please post a message to the Puppet users mailing list and we can discuss it.

For private key files, restricting write privileges (for example, to enforce password quality rules) is quite obviously useless, since the user can simply create their own copy of the private key, adjust it as they like, and use that one instead.

Note that these concerns don’t really apply to system users, since those aren’t autonomous but are under the direct control of root.

Losing track of keys

ssh::auth tries to keep track of keys and remove them when they’re revoked, but its method isn’t perfect. Here’s how you can fool it:

  • Change the key title. If you change the title in an ssh::auth::key definition, you haven’t changed the title of your existing key. Instead you’ve declared a new key, and if the old one isn’t declared any more, ssh::auth will simply forget about it and it will stay in place and authorized on the clients and servers. To avoid this, don’t change a key’s title: create a whole new definition for the new key, and separately revoke the old key.
  • Remove the key declaration without revoking it. Similarly, if you remove a key’s declaration, you haven’t removed the key. Again, without the declaration ssh::auth will forget about the key and it will stay in place and authorized on the clients and servers. To avoid this, don’t just remove a key’s declaration: revoke the key by leaving its declaration in place with ensure => absent. After enough time has passed that you’re sure the key has been removed from the servers (clients are less important), you can remove its declaration from your manifests.
  • Change the $filename. If you change the $filename of a key, the old private key will be left in place on the clients under its previous filename. This is a bug that should be fixed in future releases. Fortunately it’s really just a housekeeping issue, not a security issue: as long as the key is de-authorized on the server, it doesn’t matter if copies of it are left on the clients (and you can’t guarantee that the user doesn’t still have copies anyway).
  • Change $home or $user. ssh::auth inserts and removes public keys into the user’s $home/.ssh/authorized_keys on the server. If you change $home without first revoking the key, then the key will remain in place and authorized in that location. Again the solution is to first revoke the key, with e.g. ensure => absent, before changing $home. Note that $home is determined by default as /home/$user, so the same warning applies if you change $user while using the default $home.

It’s hoped that these gaps will be reduced or eliminated in future releases of ssh::auth.

Unencrypted private keys

ssh::auth creates private keys unencrypted, i.e. with empty passphrases. Users can add passphrases themselves using ssh-keygen -p, or, if you feel you need the keys to have passphrases, you could probably add a passphrase-creation step before distributing the keys. (If you create such a facility, please consider submitting it for inclusion into ssh::auth.) Note however, that in general there is no way to force users to encrypt their private keys, since even if you force a user to create a passphrase, they can always remove it later with ssh-keygen -p. You could try restricting write access to the private key files, but see Recipes/ModuleSSHAuth . There is also no way for a server to detect whether a key has a passphrase, nevermind a strong one. This is a limitation of ssh.

Key rotation

Rotating keys regularly is probably a good idea, but it only gets you so far and gives diminishing returns as you do it more often. In the extreme you could rotate keys every day, but it wouldn’t really buy you more security. The reason is that in order to distribute the keys automatically, we have to rely on Puppet’s host-based encryption and authentication mechanism. Therefore we really only shift our trust from the ssh keys to Puppet’s SSL keys. Yet if a client host is compromised, the attacker can steal either type of key with equal ease. So rotate your ssh keys at reasonable intervals, but protect your hosts too, and consider rotating Puppet’s keys as well (sorry, ssh::auth can’t do that!).

Detailed Usage


To manage keys with ssh::auth you have to:

  1. include ssh::auth in the global scope of your site manifest (or at least, in an enclosing scope of your keymaster, client, and server node definitions). Typically this would be outside of all of your node and class definitions in manifests/site.pp.
  2. Declare your keys with ssh::auth::key, also in the global scope.
  3. Create the keys by invoking include ssh::auth::keymaster on the keymaster.
  4. Create a $home/.ssh directory for each key user on each client and server.
  5. Distribute the keys by invoking ssh::auth::client and ssh::auth::server in the client and server node definitions.

Every key is uniquely identified by its title, which must be the same in all of the method calls that refer to it. For example, if you create a key with ssh::auth::key { “”: }, then you have to use ssh::auth::client { “”: } and ssh::auth::server { “”: } to distribute that key.

A key title can be anything, as long as it’s unique among ssh::auth::key resources (Puppet enforces this), begins with a letter or digit, and contains only the characters A-Za-z0-9_.:@–. However, there is an advantage to naming the key as either user or user@domain, since ssh::auth uses the part of the title up to the first @ character (if any) as the key’s default user. For example, a key named would be assigned to user bob on both the client and server, unless the user parameter was explicitly overridden.

It would be nice if ssh::auth could create the $home/.ssh directories for you, but unfortunately it has no way to do that while guaranteeing that the directory definitions are unique, as Puppet requires. So you’ll have to create the directories yourself, as #4 above says. Every user’s keys will automatically require that user’s $home/.ssh directory, if it’s declared in your node manifests.


A definition that creates, regenerates, or revokes private/public key pairs, for distribution to servers and clients. Invoke this in the global scope of your site manifest, for each key that you want to manage. Key titles must be unique, begin with a letter or digit, and consist only of the characters A-Za-z0-9_.:@– .

All parameters for ssh::auth::client and ssh::auth::server may also be set in the call to ssh::auth::key. They become the default values for that key but may be overridden in ssh::auth::client and ssh::auth::server. For example,

ssh::auth::key { "alice": ensure => "absent" }
node sshserver { ssh::auth::server { "alice": } }
node sshclient { ssh::auth::client { "alice": } }

ensures that alice’s key is removed from the keymaster, sshserver, and sshclient, since ensure => “absent” becomes the default for that key, and propagates through to the calls to ::server and ::client.


“present” or “absent”, to ensure that the key is present or absent. If “absent”, then all of the other parameters of ssh::auth::key are ignored: the existing key is simply removed from the keymaster. It will be removed from clients and servers too if you call ssh::auth::client and ssh::auth::server there, since “absent” will become the default for this key. Note that removing a user’s key is generally insufficient to deny her access to the servers; see Recipes/ModuleSSHAuth .


The name of the key files, on the client. For example if the filename is “id_rsa”, then the key files are id_rsa and, both in $home/.ssh. The default is “id_rsa” or “id_dsa” if the $keytype is “rsa” or “dsa”, respectively. You shouldn’t normally have to change the filename from the default, unless a user needs multiple keys and you need to avoid file name conflicts. If you do use a non-default filename, the user will have to add it explicitly to any calls to ssh(1) or ssh-add(1). filenames must begin with a letter or digit, and consist of only the characters A-Za-z0-9_.:@– .


true or false. If true, the key is forced to be regenerated. You can use this to force key regeneration one time, but it wouldn’t make sense to leave it set to true, unless you want your users to get new keys every 30 minutes. If false (the default), then the key is regenerated only if the maxdays or mindate conditions aren’t met.


Name of the group that should own the key files, on the clients and servers. Default is “puppet”.


The user’s home directory. Defaults to /home/$user. The key files will be installed into $home/.ssh on the clients and servers.


The ssh key type: “rsa” for RSA keys, or “dsa” for DSA keys. Default is “rsa”.


The key length, in bits. For $keytype=“rsa”, the default is 2048. For $keytype=“dsa”, ssh-keygen(1) requires the length to be 1024 bits, so this parameter is ignored.


The maximum allowed age of the key, in days. If a key of this title exists and is older than this many days, it will be removed and a new one generated and distributed.


Set this to a date string to ensure that the key was created on or after that date. The date may be given in any form accepted by find(1)’s -newermt option. With GNU find(1) and date(1), just about any date format you can think of will work: e.g. “2009-11-30”, “11/30/09”, “30-Nov-2009”, “November 30, 2009”, and so on all work. If a key with this title exists and was created before the given date, it will be removed and a new one generated and distributed.


Public key options, on the server. These are used to control the user’s access, for example by forcing a command to run at login, or allowing only certain ports to be forwarded. See “Authorized_keys file format” in sshd(8) for the complete list of options, but see also Recipes/ModuleSSHAuth for why this probably won’t work as you’d like it to. The default is empty. Multiple options should be given comma-delimited in a single string.


The name of the user who will use this key to authenticate. This user does not have to exist on the keymaster. It does have to exist in the configuration of the servers and clients where the key is to be installed, and will automatically be required there. The user applies by default to both the servers and clients but can be overridden on either. Default is the part of the title up to the first @ character if there is one.


A class that creates a storage area for the managed keys, and does the work of creating, regenerating, and removing the keys. You just have to

include ssh::auth::keymaster

in your keymaster’s node definition. Note that the keymaster must currently be on the same node as the puppetmaster, since the keymaster reads files from the file system, which in Puppet must always be that of the puppetmaster.


Installs or removes keys into user accounts on an ssh server, so that the users can use them to authenticate, i.e. log in. The title must identify a key that has already been declared using Recipes/ModuleSSHAuth .

The public key is installed or updated in or removed from the user’s authorized_keys(5) file on the server. Puppet’s native Type Reference type is used to do this.

ssh::auth::server supports the following parameters:

  • ensure
  • home
  • options
  • user

All of these parameters can also be set in ssh::auth::key, so they’re described there. Parameter values set in ssh::auth::key become the default for that key, but can be overridden for any server in ssh::auth::server.


Installs or removes keys into user accounts on client hosts, so that the users can use them to authenticate to servers. The title must identify a key that has already been declared using Recipes/ModuleSSHAuth .

Key files are installed into $home/.ssh, where $home is the user’s home directory. Before you do this, consider that your users may already have their own ssh keys that could be overwritten. ssh::auth::client does not, at present, have any way to detect this, and will simply overwrite any existing key files having the same name. You can avoid this by setting the filename parameter to a unique value, such as the domain name of your site. Your users will then have to explicitly name the private key file in any invocations of ssh(1) or ssh-add(1).

ssh::auth::client supports the following parameters:

  • ensure
  • filename
  • group
  • home
  • user

All of these parameters can also be set in ssh::auth::key, so they’re described there. Parameter values set in ssh::auth::key become the default for that key, but can be overridden for any client in ssh::auth::client.


ssh::auth::key, ::client, and ::server support all of Puppet’s Metaparameter Reference , so you can add require, before, et al. to any of them in the usual way. Remember that each of them already requires the user of a key, so you don’t need to add that.

Referring to the ssh::auth definitions in metaparameters of other resources is harder, because Puppet doesn’t currently support the necessary syntax. That is, you would normally require e.g. an ssh::auth::key by writing

require => Ssh::auth::key[$title]  # INVALID

but as of version 0.25.1, this is a syntax error because of the colons in the name. TODO: workaround?


The following variables provide defaults that influence ssh::auth’s behavior. You can override them either by changing them in the source file (they’re all set at the top of auth.pp), or by … FIXME


The name of the directory where generated keys are stored on the keymaster. Default is /var/lib/keys.

Error Messages

Could not find class/resource type

Any of the error messages

Could not find class ssh::auth::keymaster
Could not find resource type ssh::auth::server
Could not find resource type ssh::auth::client

means that you didn’t put include ssh::auth in an enclosing scope of your node definitions. Try putting it in manifests/site.pp, before any class or node definitions.

Failed to realize virtual resources

If you get an error message like

Failed to realize virtual resources Ssh_auth_key_client[X] on node Y

on the puppetmaster, it means that you called ssh::auth::client without declaring the key with ssh::auth::key in an enclosing scope. The analogous message applies to ssh::auth::server without ssh::auth::key.

Could not retrieve dependency ‘User[X]’

An error message like

Could not retrieve dependency 'User[X]' of File[/home/X/.ssh/authorized_keys]

on a client or server means that you’re trying to install a key for user X on that node, but you haven’t declared user X in its manifest.

Private/public key file not found on keymaster

You may see a warning message like

Private/Public key file for key X not found on keymaster; skipping ensure => present

on a client or server, on the first run after you’ve declared a new key. This happens if Puppet hasn’t run on the keymaster yet, so that no keys have been created yet. The warning should go away on the next run, after the keymaster has run and created the keys.


Here is how ssh::auth compares to the other ssh-related tools available in Puppet:


Type Reference is a native type in Puppet, that installs servers' public keys into ssh_known_host files. It only manages server public keys. It has nothing to do with user keys.


Type Reference is a native type in Puppet for managing users' public keys. It will install, update, or remove public keys in authorized_keys files on servers, and ssh::auth uses it for that. It puts the key title into the comment field in authorized_keys, and uses that comment to identify the key. It won’t create key pairs or manage the key files, and it has a few quirks.

Authorized keys recipe

The Recipes/Authorized_keys is an example of how to manage ssh keys in Puppet. It was an inspiration for ssh::auth, and has some of the same features. However it has some problems:

  • It’s poorly documented.
  • It doesn’t provide installable module code.
  • It mixes ssh key management together with user account management.
  • It enforces root ownership and control of the key files.


  1. When a key is created or changed on the keymaster, any invocations of ssh::auth::client or ssh::auth:: server on the keymaster won’t find the new key until the next Puppet run.
  2. If you change a key’s filename, ssh::auth will forget about the previous key files and leave them in place on the clients. To avoid this you have to remove the key first (ensure => “absent”) before changing the filename. This problem doesn’t affect servers or the keymaster, which don’t use the filename.
  3. If a user has two keys with the same filename, there’s no telling which one they’ll get. It will probably change back and forth randomly at each run :(

See also Recipes/ModuleSSHAuth . Bug reports and feature requests are welcome on the Puppet users mailing list.


Version Date Comments 0.3.2 2009-12-29 – Fix wrong $home when user is specified separately for a client

or server
  • Clarify that ssh::auth doesn’t manage $home/.ssh

0.3.1 2009-12-28 – Fix parser error in ssh_auth_namecheck

0.3 2009-12-22 – Regenerate key if $keytype or $length changes – Update authorized_keys if $options changes – Use ssh_authorized_keys to manage authorized_keys – Remove comment parameter. The public key comment field is now

used to identify the key, via ssh\_authorized\_keys

0.2 2009-12-09 – Fix inheritance problem with $ssh::auth::keymaster_storage

0.1 2009-12-08 Unstable release


ssh::auth is released under the GNU General Public License.


ssh::auth is written by Andrew E. Schulman <andrex at alumni dot utexas dot net>. Please send discussion of ssh::auth to the Puppet users mailing list. If you put ssh::auth in the subject, I’ll see it.

auth.pp (14.9 KB) christian c, 03/29/2010 08:01 am