Wednesday, September 25, 2013

Register an RSA Public Key using an Ansible Playbook

In the previous post, I demonstrated a script that automates the adding of an RSA Public Key to a remote host.  By doing this, we get the ability to perform "password-less" logins.

My original purpose for doing this was so I could remotely manage those hosts via Ansible (http://www.ansibleworks.com/).  It turns out, Ansible has a much more effective way of performing this task.

Ansible has the notion of "playbooks" which are essentially scripts/configuration similar to a Chef Recipe or a Puppet script.

This is the Ansible playbook for adding an RSA Public Key, located at ~/.ssh/id_rsa.pub on my local machine, to a remote host (call this file register-key.yml):

- hosts: '{{ host }}'
  remote_user: '{{ user }}'

  tasks:
  - name: Add RSA key to the remote host
    authorized_key: user='{{ user }}' key="{{ lookup('file', '~/.ssh/id_rsa.pub') }}"

The script basically says, for the following hosts, as the remote user (user), add the contents of the ~/.ssh/id_rsa.pub on my local system to the authorized_key file for the remote user (user).

Note that user and host are both variables we will pass in.  The lookup keyword is literally a function to pull the contents of the 'file' specified as the second argument.

The next thing you need is an inventory file. This inventory file lets Ansible know the available resources for it to command. Here is a really simple inventory file called inventory.txt:

my.remote.host.com

Your Ansible inventory file can be a full list of servers, referenced by hostname or IP Address.  The computers can be grouped into categories, and have special configuration applied individually.

We execute the Ansible playbook like this:

# Register key with the remote host
ansible-playbook register-key.yml -i inventory.txt \
--ask-pass --extra-vars "user=myremoteuser host=my.remote.host.com" 

The previous command basically says:
  1. Execute the  register-key.yml playbook.
  2. Use the inventory described in inventory.txt
  3. Ask me for the remote user's password.  We don't have the key installed yet, so we will need to log in as the remote user.
  4. Use the following values for variables defined in the playbook:
    1. user - myremoteuser
    2. host - my.remote.host.com (this was in the inventory, remember?)
Your output should look something like this:

Yes, it really is that simple.


Update (9/26/13): I discovered an interesting caveat when using this script on a brand-new VM.  If you have never logged into that VM via SSH before, the script will fail due to an authentication error.  At first, I thought the remote server (the VM) was throwing the error.  After some investigation, I realized that it was actually the local machine.  The reason is that Ansible does not automatically accept the remote machine's certificate when the very first connection is made.  Remember the preamble you get when you connect to a remote machine for the first time in SSH?  Your machine asks whether you accept the remote machines certificate.  The same thing is happening here, it's just, Ansible is sensible and doesn't violate your trust by automagically accepting the certificate.

So, in short, you will need to log into the remote machine via SSH just once, if only to accept that machines SSH certificate.  

BASH Script to Register RSA Pulic Key with Remote Host

I wanted to be able to script the installation of an RSA public key on a remote host so I could have "password-less" access to the host via SSH.  This is what I came up with.

Note:  I am a developer, not a BASH ninja.  If you have recommendations to improve this script, let me know.

Before I show the script, this is how you use it:

# The User and Host are together.  
# I decided to not make it any more complicated than it needed to be.
sh register_pub_key.sh user@remotehost


You will be prompted for passwords as necessary.

Here's a look at the console output of the script:



And here's the BASH script (named register_pub_key.sh):

#!/bin/bash

# Variables
USER_AND_HOST=$1

line="---------------------------------------------"

echo "REGISTERING RSA PUBLIC KEY WITH HOST: $REMOTE_HOST"

echo $line

echo "This script will prompt during the process since"
echo "you have not yet installed the RSA key with the"
echo "server.  Please be patient; this should be done"
echo "shortly."

echo $line

# Ensure the RSA Public Key exists.
if [ ! -e ~/.ssh/id_rsa.pub  ]
then

    echo "No public SSH key found..."

    echo "Generating SSH key.  Follow the prompts:"

    echo $line

    ssh-keygen -t rsa

    echo $line

    if [ ! $? ]
    then

        echo "Key Generation was not successful.  Exiting."

        exit 1
    fi
fi

echo $line

echo "Ensuring remote .ssh directory exists."
echo "You will need to enter the remote host's password."

ssh $USER_AND_HOST "mkdir -p ~/.ssh/"

echo $line

echo "Copying key to remote host."
echo "You will need to enter the remote host's password."

echo $line

scp ~/.ssh/id_rsa.pub "$USER_AND_HOST:~"

echo $line

echo "Adding the key to the set of 'authorized_keys'..."
echo "You will need to enter the remote host's password."

ssh $USER_AND_HOST "cat id_rsa.pub >> .ssh/authorized_keys"

echo $line

echo "Cleaning up key and testing SSH access..."
echo "IF YOU HAVE TO ENTER A PASSWORD, IT'S FAILED!!!!"

ssh $USER_AND_HOST "rm ~/id_rsa.pub"

RESULT=$?

echo $line

if [ RESULT ]
then
   echo "Your public key was successfully added to the host."
else
   echo "Epic fail mate!  Your key was NOT added to the host."
fi

echo $line

echo "Ensure you can access the server using the following command:"
echo "ssh $USER_AND_HOST \"ls -lah ~/.ssh\""

In the next post, I'm going to demonstrate how to reduce the number of operations considerably by performing this task using Ansible.