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.  

9 comments:

  1. Hi Richard,

    Why did you use a txt file vs yaml file for your "inventory.txt"? Also couldn't you have
    referenced /etc/ansible/hosts?
    Thanks,
    Brian

    ReplyDelete
  2. Hey Brian,

    I don't commonly use /etc/ansible/hosts because we place our inventory files in source control with our projects so all members have access to the same resources; I guess this post is a reflection of our workflow.

    As for the text file format, I guess it's just out of habit when working with a simple text editor.

    Thanks,

    Richard

    ReplyDelete
  3. Hello Richard,

    I tried to run your playbook but I get this error message :
    ERROR: remote_user is not a legal parameter in an Ansible Playbook

    Any idea ? Thanks

    Kind regards

    ReplyDelete
    Replies
    1. What version of Ansible do you have installed? I'm using the latest version (I think 1.5 at the time of this post). You may be encountering the following issue described in the documentation:

      "The remote_user parameter was formerly called just user. It was renamed in Ansible 1.4 to make it more distinguishable from the user module (used to create users on remote systems)."

      - http://www.ansibleworks.com/docs/playbooks.html#hosts-and-users

      Let me know if that helps.

      Delete
    2. Thanks. You are right : $ ansible --version
      ansible 1.3.4

      I just have an other question... if you use "user=myremoteuser host=hostgroup" where hostgroup is a group of hosts, do you need to type password for each host (because of --ask-pass flag). I wonder how (or if) we can send same password for each host. I wonder if we can't use SSHPASS environnement variable (like sshpass) with ansible

      $ sshpass -help
      Usage: sshpass [-f|-d|-p|-e] [-hV] command parameters
      -f filename Take password to use from file
      -d number Use number as file descriptor for getting password
      -p password Provide password as argument (security unwise)
      -e Password is passed as env-var "SSHPASS"
      With no parameters - password will be taken from stdin

      -h Show help (this screen)
      -V Print version information
      At most one of -f, -d, -p or -e should be used

      Delete
    3. In terms of keypass, I don't know. I do know that you can supply a password in your inventory file as a variable at the group level. This password will be used for every server (removing the need for the --ask-pass flag). When your done installing the key, remove the variable from the inventory file. This may be more ideal if you are provisioning a lot of servers.

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
  5. If you add the contents below to ~/.ssh/config , there will be no authentication error.
    Host *
    UserKnownHostsFile /dev/null
    StrictHostKeyChecking no

    ReplyDelete

Note: Only a member of this blog may post a comment.