Distribute ssh keys

Let's say we have a list of users that are trusted to login to a set of servers managed by CFEngine.

We are going to implement this trust relationship by ensuring the users' accounts on managed servers have a .ssh/authorized_keys file with each user's public key.

Let's assume we collected all users' public keys into a single directory on the server and that users exist on the clients (and have corresponding home directory). The following CFEngine policy distributes the keys from /var/cfengine/masterfiles/ssh_keys on the policy server to /var/cfengine/inputs/ssh_keys on the managed servers and from there each user's key will go to each user's .ssh/authorized_keys file.

Note: special variable $(sys.policy_hub) contains the hostname of the policy server.

You have to adapt this policy in the mentioned places for it to work in your environment.

body common control {
bundlesequence => { "distribute_ssh_keys" };
inputs => { "libraries/cfengine_stdlib.cf" };
}

bundle agent distribute_ssh_keys
{
vars:

    "users"             slist => { "user1", "user2" };   # List of users to be included in key distribution.
                                                         # Modify to include actual users.
    "source_server"    string => "$(sys.policy_hub)";         # Server where keys are stored
    "source_directory" string => "/var/cfengine/masterfiles/ssh_keys"; # Source directory of key files
    "local_cache"      string => "/var/cfengine/inputs/ssh_keys";      # Local cache of key files

files:

   "$(local_cache)/$(users).pub"

       comment => "Copy public keys from an authorized source into a cache on localhost",
         perms => mo("600","root"),
     copy_from => remote_cp("$(source_directory)/$(users).pub","$(source_server)"),
        action => if_elapsed("60");  # wait 60 min before checking this promise again

  # Ensure that authorized_keys file exists and has permissions 600 and call a file editing promise

   "/home/$(users)/.ssh/authorized_keys"

     comment => "Edit the authorized keys into the user's personal keyring",
      create => "true",
       perms => m("600"),
   edit_line => insert_file_if_no_line_matching("$(users)","$(local_cache)/$(users).pub"),
      action => if_elapsed("60"); 
}

#####

bundle edit_line insert_file_if_no_line_matching(user,file)
{

# Check if user exists in the authorized_keys file
classes:

  "have_user"
    expression => regline("$(user).*","$(this.promiser)");

# Insert the content of the key file into authorized_keys if the user's key is not already there
insert_lines:

  !have_user::

    "$(file)"
      insert_type => "file";
}

Example run:

First, let's setup for the run. Put users' SSH keys into the key distribution point on the policy hub:

policy_hub# ls /var/cfengine/masterfiles/ssh_keys/*pub 
/var/cfengine/masterfiles/ssh_keys/user1.pub  /var/cfengine/masterfiles/ssh_keys/user2.pub
policy_hub# 

There are no authorized_keys files on the managed servers, but the home (and .ssh) directories exist:

# ls -d /home/user*/.ssh
/home/user1/.ssh  /home/user2/.ssh
# ls /home/user?/.ssh/authorized_keys  
ls: cannot access /home/user?/.ssh/authorized_keys: No such file or directory
#

Run CFEngine on one of the managed servers to create and populate /var/cfengine/inputs/ssh_keys from source (policy_hub:/var/cfengine/masterfiles/ssh_keys) and then install each user's key into that user's authorized_keys file:

# cf-agent -f ssh.cf
2013-06-08T15:49:29-0700    error: Failed to chdir into '/var/cfengine/inputs/ssh_keys'
#

Note: the above error only happens on the first run. Then /var/cfengine/inputs/ssh_keys is created and this error does not recur.

The local cache now contains the users' public keys:

ls /var/cfengine/inputs/ssh_keys/
user1.pub
user2.pub
#

CFEngine created authorized_keys files:

# ls /home/user?/.ssh/auth*keys
/home/user1/.ssh/authorized_keys
/home/user2/.ssh/authorized_keys
#

CFEngine installed the user's keys:

# more /home/user?/.ssh/auth*keys
::::::::::::::
/home/user1/.ssh/authorized_keys
::::::::::::::
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjtmJf9QfME2KIV19C96EyRg1dizxKMTjLRsPtwsmC2fRyA3fRFvpUVKApigDTNxF5nDqfgGtY9
0KhnuqjhOgYWnpm4dmiTdFXJ5XHuNPCc4JpsXBeyMy2f8e1aobb/dN5UhSSZmYb84FkYwbI/EkxJ46CmmOpOi6C5AjYfqwzshIGNgJS39hbtsUimc
qBAOYTHzVpm5+KfHbNryZ9ORWEVcPvnchKtEfNu8iuDdecOxmWWUPhEyhUz7/SfZ4cPs7692JcIX2XQCsvsGWS5JPiVXGDPCcLz7WNI2A7rohoC9f
vpE11CBigl7zTlB0M7nQYzpjaf7qS3AvOXw5CLUPD user1@examplehost
::::::::::::::
/home/user2/.ssh/authorized_keys
::::::::::::::
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDhaBkZg7t63kNXqduU1LzLH+8DEkTGAhOjharGf6TMWL9fkWXS+Xjj2iD7KZgT2VBC9Hf8o+HhL
al5kyHYH8qRxtPXMm5UVhIHnq8hxDQQPo/jW62wwxB0N2pF8oU4sMzMzCANJYE3C6H0rjIzgloiCIkBwL21WoFhxZ145z7VoKTEf0ICRk2+xmCc2W
hX1pQVJzs5GlKlWEsJUp8Skqt+OuJTtIS4R3nJALvo7zindvum12DcbWfsrV5oW3gl89GkyDAdi1mWaqBmGX5qF5b19KaP4qdth61foUTR7NyHuCs
C/hNB84Loy+2nMU8QpKJ7Ha6UyBtU2YrzDxL3YPgJ user2@examplehost
#