Most exported resource documentation starts with an SSH key example. sshkey
is a Puppet type that creates or destroys entries in the ssh_known_hosts
file used by SSH to verify the validity of remote servers. The sshkey
example is a great use of exported resources, but since most examples put the declaration and collecting phases in the same class, it may be a confusing example for those starting out learning exported resources. It's important to remember that exporting and collecting are different operations.
We'll outline an enterprise application of the sshkey
example and define a class for login servers—any server that allows users to log in directly. Using that class to define exported resources for ssh_host_keys
, we'll then create an ssh_client
class that collects all the login server ssh_keys
. In this way, we can apply the ssh_client
class to any laptops that might connect and have them get updated SSH host keys. To make this an interesting example, we'll run Puppet as non-root on the laptop and have Puppet update the user's known_hosts
file ~/.ssh/known_hosts
instead of the system file. This is a slightly novel approach to running Puppet without root privileges.
We'll begin by defining an example::login_server
class that exports the RSA and DSA SSH host keys. RSA and DSA are the two types of encryption keys that can be used by the SSH daemon; the name refers to the encryption algorithm used by each key type. We will need to check if a key of each type is defined as it is only a requirement that one type of key be defined for the SSH server to function, as shown in the following code:
class example::login_server { if ( $::sshrsakey != undef ) { @@sshkey {"$::fqdn-rsa": host_aliases => ["$::hostname","$::ipaddress"], key => "$::sshrsakey", type => 'rsa', tag => 'example::login_server', } } if ( $::sshdsakey != undef ) { @@sshkey {"$::fqdn-dsa": host_aliases => ["$::hostname","$::ipaddress"], key => "$::sshdsakey", type => 'dsa', tag => 'example::login_server', } } }
This class will export two SSH key entries, one for the rsa
key and another for the dsa
key. It's important to populate the host_aliases
array as we have done so that both the IP address and short hostname are verified with the key when using SSH.
Now we could define an example::laptop
class that simply collects the keys and applies them to the system-wide ssh_known_hosts
file. Instead, we will define a new fact, homedir
in base/lib/facter/homedir.rb
, to determine if Puppet is being run by a non-root user, as follows:
Facter.add(:homedir) do if Process.uid != 0 and ENV['HOME'] != nil setcode do begin ENV['HOME'] rescue LoadError nil end end end end
This simple fact checks the UID of the running Puppet process; if it is not 0 (root), it looks for the environment variable HOME
and sets the fact homedir
equal to the value of that environment variable.
Now we can key off this fact as a top scope variable in our definition of the example::laptop
class as follows:
class example::laptop { # collect all the ssh keys if $::homedir != undef { Sshkey<<| tag == 'login_server' |>> { target => "$::homedir/.ssh/known_hosts" } } else { Sshkey<<| tag == 'login_server' |>> } }
Depending on the value of the $::homedir
fact, we either define system-wide SSH keys or userdir keys. The SSH key collector (Sshkey<<| tag == 'login_server' |>>
) uses the tag login_server
to restrict the SSH key resources to those defined by our example::login_server
class.
To test this module, we apply the example::login_server
class to two servers, ssh1
and ssh2
, thereby creating the exported resources. Now on our laptop, we run Puppet as ourselves and sign the key on Puppet master.
We add the example::laptop
class to our laptop machine and examine the output of our Puppet run.
Our laptop is likely not a normal client of our Puppet master, so when calling Puppet agent, we define the puppetserver
and environment as follows:
t@mylaptop ~ $ puppet agent -t --environment production --server puppet.example.com --waitforcert 60 Info: Creating a new SSL key for mylaptop.example.com Info: Caching certificate for ca Info: csr_attributes file loading from /home/thomas/.puppetlabs/etc/puppet/csr_attributes.yaml Info: Creating a new SSLcertificate request for mylaptop.example.com Info: Certificate Request fingerprint (SHA256): 97:86:BF:BD:79:FB:B2:AC:0C:8E:80:D0:5E:D0:18:F9:42:BD:25:CC:A9:25:44:7B:30:7B:F9:C6:A2:11:6E:61 Info: Caching certificate for ca Info: Caching certificate for mylaptop.example.com Info: Caching certificate_revocation_list for ca Info: Retrieving pluginfacts ... Info: Loading facts Info: Caching catalog for mylaptop.example.com Info: Applying configuration version '1449337295' Notice: /Stage[main]/Example::Laptop/Sshkey[ssh1.example.com-rsa]/ensure: created Info: Computing checksum on file /home/thomas/.ssh/known_hosts Notice: /Stage[main]/Example::Laptop/Sshkey[ssh2.example.com-rsa]/ensure: created Info: Stage[main]: Unscheduling all events on Stage[main] Notice: Applied catalog in 0.12 seconds
Since we ran the agent as non-root, the system-wide SSH keys in ssh_known_hosts
cannot have been modified. Looking at ~/.ssh/known_hosts
, we see the new entries at the bottom of the file as follows:
ssh1.example.com-rsa,ssh1,10.0.2.15ssh-rsaAAAAB3NzaC1yc2... ssh2.example.com-rsa,ssh2,10.0.2.15ssh-rsaAAAAbd3dz56c2E...