Ansible-Vault password management made easy with Lastpass-CLI and Rake

With a fresh new year comes a fresh new set of VPS hosts, and as a resolution i'm planning to fully deploy these through ansible, along with migrating a lot of my existing machines to ansible as well.

Now, as we all know, it's generally considered a bad idea to store plaintext passwords for the world to see in a git repository, which is why ansible-vault exists. However, for ansible vault to work, we need to provide a password on the command line, and while obviously i could use the same password for all the vault files, i considered it a challenge to properly automate this and use a randomly generated password for each of my hosts and groups. For this, we're going to use lastpass-cli. If you're not aware of this tool, it is amazing and i use it to store literally everything in there.

What i've done is store all my credentials sites in the following format:

ansible.vault.{{environment}}.type.{{varname}}: For example, a production host called vps01.transip.vxsan.com would have a lastpass entry called ansible.vault.production.host.vps01.transip.vxsan.com, while a group of webservers in development would have a vault entry called ansible.vault.development.group.webservers. Note that the entries are all empty except for the password and the name, as can be seen below:

Screen-Shot-2018-02-07-at-11.50.28

Also note that you can create these entries through the lastpass-cli as well, so you could very easily make this an automated process through rake or various other deployment tools, even going so far as to generate a random vault password every time you add a new host or a new group.

My directory structure for ansible playbooks is as follows:

- playbook
   - extensions (contains various scripts)
   - host_vars
       - host1.tld
           - app1
           - app2
           - appn
       - host2.tld
       - hostn.tld
   - group_vars
       - group1
           - app1
           - app2
           - appn
       - group2
   - plays (contains all playbooks)
   - roles (contains all roles)

As you can see, because of the way host_vars and group_vars are structured, this setup allows for simplified automation when it comes to generating data either per host or per group.

Now onto how we are using this:

To automate the ansible process we are using rake. An excerpt from my ansible rakefile using lastpass has been shown below:

namespace :ansible do

  def checklogin
    system("lpass status -q")
  end


  ansible_cli = "ansible-playbook "


  task :login, [:username] do |t, args|
    user = "#{args.username}"
    user.empty? and abort("user not defined")
    checklogin or system("lpass login #{user}")
  end

  task :run, :env, :play, :tag do |t, args|
    env = "#{args.env}"
    env.empty? and abort("environment not defined")
    play = "#{args.play}"
    play.empty? and abort("play not defined")
    tag = "#{args.tag}"
    checklogin or abort("user not logged in")
    system("lpass sync")

    Dir.foreach('./group_vars') do |item|
      next if item == '.' or item == '..'
      pwid = "vault.#{env}.#{item}"
      password=`lpass show -F #{pwid} --password 2> /dev/null`
      password.empty? or ansible_cli = ansible_cli + " --vault-id ../vaultid.#{item}"
      password.empty? or File.write("vaultid.#{item}", "#{password}")
      password.empty? or File.chmod(0600,"vaultid.#{item}")
    end

    Dir.foreach('./host_vars') do |item|
      next if item == '.' or item == '..'
      pwid = "vault.#{env}.#{item}"
      password=`lpass show -F #{pwid} --password 2> /dev/null`
      password.empty? or ansible_cli = ansible_cli + " --vault-id ../vaultid.#{item}"
      password.empty? or File.write("vaultid.#{item}", "#{password}")
      password.empty? or File.chmod(0600,"vaultid.#{item}")
    end
    tag.empty? or ansible_cli = ansible_cli + " --tag #{tag}"
    ansible_cli = ansible_cli + " -i ../#{env}.ini #{play} "

    Dir.chdir('plays')
      system("#{ansible_cli}")
      Dir.glob('../vaultid.*').each { |item| File.delete("#{item}") }
  end
end

Note that this temporarily writes the passwords to the cwd so in a shared environment so this could be considered insecure, even though permissions are set to 0600. However, as i am the only user on my VPS for my current setup it works well.

At the start, rake will check the user login status and request a login if the user isn't already. Afterwards, for every host_vars and group_vars it will try to look up the corresponding password item from lastpass, write the password to a temporary file and use the file on the ansible run as a parameter. After the ansible run completes, the file will be removed again.

Obviously, this example is not limited to ansible (or Rake for that matter), and it will allow you to automate credential usage in your personal systems, lab environments and whatever you can think of. Personally, i already don't know the majority of the passwords i use in my browser, and using lastpass on the CLI as well will reduce the amount of password reuse to a minimum there as well.