Categories
Technical

Generating passwords in Ansible with complex loops

Ansible is a fantastic open source automation tool which you can use to configure systems, deploy software, and perform any number of other tasks automatically across as many, or as little, systems as you need. It’s like Chef or Puppet, only configuration is a piece of cake and installation is only required on the master host – it uses SSH to connect and manage all of the hosts to be managed, so you don’t need to set anything up anywhere else. The best place to find more information is the Ansible Docs.

The Ansible configuration tries to keep things as simple as possible, while still providing a lot of powerful features. There are plenty of upsides to this, but also some downsides… I’ve been setting up Ansible to configure web applications across a couple of web servers, and the configuration looks something like this:

---
sites:
  - domain: domain.com
    repo: [email protected]:repo.git
    envfile: .env.php
    db:
      name: dbname
      user: username
      pass: password

Ansible tasks handle this config very easily looping with with_items: sites, and everything is easily configured: directory created, git repo checked out, envfile written, database created, etc…

However, I now have my database password in my Ansible configuration. From a security point of view, this is a very bad idea!

How do we go about getting Ansible to automatically generate a new password, and use this to create a database user, and save it into an env file?

If I wasn’t looping through sites, I could simply use something like this to generate a new password, register a new variable, create a db user, and write it all to a file like this:

---
- name: Generate new password
  shell: makepasswd --chars=20
  args:
    creates: /var/www/vhosts/domain.com/.env.php
  register: newpwd

- name: Save new password in env file
  template: src=env/env.php dest=/var/www/vhosts/domain.com/.env.php

- name: Create new Postgresql User
  postgresql_user: db=postgres name=dbname password={{ newpwd }}

However, I’m already looping through sites, so things get a lot more complicated! We need to tell Ansible to loop through each site to generate a new password (which is easy), but then tell it to loop through each site and each generated password and collate them nicely. (See told you it was getting complicated!)

After a bit of digging, I stumbled on the with_together() loop operator, and gave it a shot. It allows you to specify two lists which have related items in the same order, and loop through both side-by-side. After some trial and error, I arrived at a working set of Ansible tasks:

---
- name: Generate site password
  shell: makepasswd --chars=20
  args:
    creates: "{{ vhostdir }}/{{ item.domain }}/{{ item.envfile }}"
  with_items: sites
  register: sitepwd

- name: Save envfile
  template: src=env/{{ item.0.envfile }} dest={{ vhostdir }}/{{ item.0.domain }}/{{ item.0.envfile }}
  with_together:
    - sites
    - sitepwd.results

- name: Create Postgresql User
  postgresql_user: db=postgres name={{ item.0.db.user }} password={{ item.1.stdout }}
  with_together:
    - sites
    - sitepwd.results

Note how there are both item.0 and item.1 as variables. These relate to each of the variables listed at in the with_together.

There we have it – a way to create new passwords in Ansible, save them to an env file, and create a database user, all while looping through a list of sites.

Hopefully that’s been helpful for you, if you’ve been struggling with a similar sort of thing in Ansible. 🙂

If you’ve solved this in other ways, I’d love to hear how you do it!

Leave a Reply

Your email address will not be published. Required fields are marked *