How we manipulate passwords using Ansible

January 27, 2022

Ansible is the core of our quick deployment infrastructure, meaning that we use it to run thousands of servers and automated processes. All of that infrastructure also needs password management, but Ansible Vault doesn't quite fulfill our needs at our scale. I'd like to share our solution for group password and permission management on Ansible.

passwords thumb

The problem with Ansible Vault is that it has poor support for group work and doesn't have convenient user, permission, and value management options. We always try to keep our infrastructure 100% IaC (Infrastructure as Code), so splitting passwords or adding manual actions wasn't really an option for us.

We've grown fond of Team Password Manager (TPM) over time, but it was its integration functionality that made it the perfect fit for our solution. We were able to integrate TPM to Ansible, solving our group work, permission, and password organization issues. This also took password management off of DevOP's hands by using encrypted vault files that solve merge requests and search problems in GitLab.

Here are the key changes we made to ensure that our admins and developers could work securely and conveniently:

  • Moved all passwords and sensitive information from Ansible Vault and GitLab Variables to TPM;

  • All application configuration files were reworked to the jinja format;

  • All application configuration files were removed from Ansible and moved to app catalogs managed by our developers.

These changes helped us fully separate the server configuration and application development processes. It also made the deployment process fully automated and developer-controlled.

Here are a few examples of how we use the TPM ansible module in group_vars and deployment playbooks.

=== Group_vars YAML example

1
elasticsearch_users:
2
- { user: "writer", password: "{{ {'id':1517,'auth':'auth:deploy'} | nordsec.team_password_manager.get_password }}", roles: [ "writeonly" ] }
3
- { user: "reader", password: "{{ {'id':1518,'auth':'auth:deploy'} | nordsec.team_password_manager.get_password }}", roles: [ "readonly" ] }

=== Playbook example

Pulling different values for different environments

1
- name: Set tpm passwords
2
set_fact:
3
site_map_ids:
4
production:
5
projects:
6
- 930
7
- 963
8
passwords:
9
- 16383
10
staging:
11
projects:
12
- 947
13
- 1163
14
passwords:
15
- 16382

Prepare to get request config

1
- name: Prepare tpm config
2
set_fact:
3
tpm_config:
4
auth: "auth:deploy"
5
get_passwords_from_projects: "{{ site_map_ids[deploy_environment]['projects']|default([]) }}"
6
get_passwords: "{{ site_map_ids[deploy_environment]['passwords']|default([]) }}"

Warmup process

1
- name: Get passwords from TPM (warmup)
2
tpm_module: "{{ tpm_config | combine({'update_cache': True}) }}"
3
connection: local
4
run_once: True
5
when: inventory_hostname == ansible_play_hosts_all[0]

Getting all values from cache

1
- name: Get passwords from TPM
2
tpm_module: "{{ tpm_config }}"
3
connection: local
4
register: tpm_pass_list

Result with variable names which can be used in application config and easily replaced

1
ok: [server-1] => {
2
"msg": {
3
"changed": false,
4
"failed": false,
5
"tpm": {
6
"IDENTITY_EMAIL_PARAM_AES_KEY": "xxxxxxxxxx",
7
"AUTH_API_CLIENT_ID": "xxxxxxxxxx",
8
"AUTH_API_CLIENT_SECRET": "xxxxxxxxxx",
9
...
10
"RABBITMQ_API_PASSWORD": "xxxxxxxxxx"
11
}
12
}
13
}

You’ll have to complete the implementation based on your own specific needs and technical capabilities, but these are some of the essential building blocks for our implementation. Now, all of our admin and dev teams across all of our infrastructure enjoy easy and dependable password management.