There are a few solutions to generate ssh keys on a puppet master/server or copy them from hiera to a box. I have got several boxes and every box needs to have ssh access to every other box. I don’t care which key it is in particular, it just have to work. I don’t want to copy the keys from somewhere, transferring private data is a unnecessary security risk so I want to create them on the node. My idea was to have a solution with four parts:
- defined resource which can create ssh pub/priv keys for me
- a generic fact that exports public keys
- exported resource to export the public key as
ssh_authorized_key
resource - collect every exported pubkey except for the own one
Here is my defined resource (which is a 99% copy, I just added a top scope to it):
# this is based on https://github.com/maestrodev/puppet-ssh_keygen/blob/master/manifests/init.pp define base::ssh_keygen ( $user = undef, $type = undef, $bits = undef, $home = undef, $filename = undef, $comment = undef, $options = undef, ) { Exec { path => '/bin:/usr/bin' } $user_real = $user ? { undef => $name, default => $user, } $type_real = $type ? { undef => 'rsa', default => $type, } $home_real = $home ? { undef => $user_real ? { 'root' => "/${user_real}", default => "/home/${user_real}", }, default => $home, } $filename_real = $filename ? { undef => "${home_real}/.ssh/id_${type_real}", default => $filename, } $type_opt = " -t ${type_real}" if $bits { $bits_opt = " -b ${bits}" } $filename_opt = " -f '${filename_real}'" $n_passphrase_opt = " -N ''" if $comment { $comment_opt = " -C '${comment}'" } $options_opt = $options ? { undef => undef, default => " ${options}", } exec { "ssh_keygen-${name}": command => "ssh-keygen${type_opt}${bits_opt}${filename_opt}${n_passphrase_opt}${comment_opt}${options_opt}", user => $user_real, creates => $filename_real, } }
And here is my custom fact to scan root + postgres user + all normal users for pubkeys and creates facts with comment + the key itself:
Dir.glob(["/root/.ssh/id_*.pub", "/home/*/.ssh/id_*.pub"]).each do |glob| # maybe our regex fails, so jump ahead if so user = /\w+(?=\/\.ssh)/.match(glob).to_s next if user.empty? file = File.open(glob) line = file.gets.chomp type = line.split[0].split('-')[1] pubkey = line.split[1] comment = line.split[2] Facter.add("#{user}_#{type}_pubkey") do setcode do pubkey end end Facter.add("#{user}_#{type}_comment") do setcode do comment end end end Dir.glob("/var/lib/pgsql/.ssh/id_*.pub").each do |glob| file = File.open(glob) line = file.gets.chomp type = line.split[0].split('-')[1] pubkey = line.split[1] comment = line.split[2] Facter.add("postgres_#{type}_pubkey") do setcode do pubkey end end Facter.add("postgres_#{type}_comment") do setcode do comment end end end
Now this allows us to use the following puppet profile:
class profiles::myawesomesshkeyexchange { ## create ssh key for root base::ssh_keygen{root: type => 'ed25519', } ## export it if $::root_ed25519_comment and $::root_ed25519_pubkey { @@ssh_authorized_key{$::root_ed25519_comment: ensure => 'present', type => ed25519, options => ['no-port-forwarding', 'no-X11-forwarding', 'no-agent-forwarding' ], user => $sshuser, key => $::root_ed25519_pubkey, tag => 'bla', } } # collect it Ssh_Authorized_Key <<| tag == 'bla' and title != $comment |>> }
this works for the root user, but we have to accept the fingerprint on the first connect because the key isn’t present in the known_hosts file. Also we maybe want to do this for multiple users on the system:
class profiles::myevenmoreawesomesshkeyexchange { # we need ssh key exchange for two users $type = 'ed25519' $myhash = {root => '/root', postgres => '/var/lib/pgsql'} $myhash.each |$sshuser, $homepath| { ## create ssh key for $sshuser base::ssh_keygen{$sshuser: type => $type, home => $homepath, } ## export it $pubkey = getvar("::${sshuser}_${type}_pubkey") $comment = getvar("::${sshuser}_${type}_comment") if $pubkey and $comment { @@ssh_authorized_key{$comment: ensure => 'present', type => $type, options => ['no-port-forwarding', 'no-X11-forwarding', 'no-agent-forwarding' ], user => $sshuser, key => $pubkey, tag => 'bla', } } # collect it Ssh_Authorized_Key <<| tag == 'bla' and title != $comment |>> } ## export host key if $::sshecdsakey { @@sshkey{$::fqdn: host_aliases => $::ipaddress, type => 'ecdsa-sha2-nistp256', key => $::sshecdsakey, tag => 'bla', } } ## import host key Sshkey <<| tag == 'bla' and title != $::fqdn |>> }
Now we’ve a setup that can automatically create, export and exchange ssh keys for multiple users on multiple servers without any manual interaction. Thanks to exported resources this works even when new nodes join the setup, when somebody deletes a key or accident or if boxes get removed. If you manage all entries in the authorized_keys file you should ensure that puppet removes all unknown keys:
user {'root': purge_ssh_keys => true, }
Pingback: Create a simple streaming replication for postgres with puppet | the world needs more puppet!