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

    "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



       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


     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

    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_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

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/

CFEngine created authorized_keys files:

# ls /home/user?/.ssh/auth*keys

CFEngine installed the user's keys:

# more /home/user?/.ssh/auth*keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCjtmJf9QfME2KIV19C96EyRg1dizxKMTjLRsPtwsmC2fRyA3fRFvpUVKApigDTNxF5nDqfgGtY9
vpE11CBigl7zTlB0M7nQYzpjaf7qS3AvOXw5CLUPD user1@examplehost
C/hNB84Loy+2nMU8QpKJ7Ha6UyBtU2YrzDxL3YPgJ user2@examplehost