Hasicorp Vault is a more and more popular choice for securing credentials / keys / certificates. The API is clear and easy to use and rolling these calls into Ansible couldn’t be simpler.
https://www.vaultproject.io/api-docs/secret/kv/kv-v1
I hit the use case where, as part of my VM deployment pipelines, I needed to generate new default credentials, add these to Vault, and update the virtual machine with these newly created credentials.
In this blog I will illustrate how to complete these tasks, intially via a curl command, and then with a corresponding Ansible play.
In order to complete generate credentials, a non standard Vault plugin is required. The Vault Password Generator is a Vault secrets plugin for generating cryptographically secure passwords and passphrases.
https://github.com/sethvargo/vault-secrets-gen
Step 1 – Obtain Login Token
With Curl…
In these examples, an Active Directory account is passed to Vault in order to obtain a login token.
Command: curl -X POST https://1.2.3.4:8200/v1/auth/ldap/login/test.user -d '{"password":"Iamapassword"}' -k Response: {"request_id":"7f2db555-ff1d-e665-e405-d788206273b0","lease_id":"", "renewable":false,"lease_duration":0,"data":{},"wrap_info":null, "warnings":null,"auth":{"client_token":"s.s34IWgJBEu9w9rmD0Falq4MX" ,"accessor":"ImrKPIdWooILuWAWLtKAsDtI","policies":["default", "vault_secrets"],"token_policies":["default","vault_secrets"], "metadata":{"username":"test.user"},"lease_duration":3600, "renewable":true,"entity_id": "46b948be-ec37-166a-c40b-ed2234435529","token_type":"service", "orphan":true}}
As an Ansible play…
- name: Login to Vault as Service Account uri: url: "{{ vault_url }}/v1/auth/ldap/login/{{ vault_username }}" body_format: json method: POST body: {"password":"{{ vault_password }}"} return_content: yes status_code: 200 register: data retries: 6 until: data is succeeded delay: 10 no_log: true
Step 2 – Obtain a new password from Vault
Vault provides the ability to generate a new password to your particular specification. This specification is defined within a JSON payload that you should pass in the request body.
With Curl…
As you can see in this request, we request a password of 15 characters, comprising of 3 digits, 3 symbols and upper characters. The new password is passed back in the response within the data key.
curl -X POST https://1.2.3.4:8200/v1/gen/password -d '{"length":"15", "digits":"3", "symbols":"3", "allow_uppercase": "true"}' -k -H 'X-Vault-Token:s.s34IWgJBEu9w9rmD0Falq4MX' Response: {"request_id":"785d5bfb-a05e-08b7-6ed7-44dd8cc9c078","lease_id":"", "renewable":false,"lease_duration":0, "data":{"value":"yW1\"x5y*ZQL!dP9"},"wrap_info":null, "warnings":null,"auth":null}
As an Ansible play…
Note that in the previous task the returned json object was registered as a variable named ‘data’. Within the following task, this variable is walked to obtain the client_token value, which can be used to authenticate all following requests.
The output of this task is registered as a variable named new_pass. This variable will also need walking in any future tasks to extract the specific key/value needed.
- name: Fetch a new password from Vault uri: url: "{{ vault_url }}/v1/gen/password" body_format: json method: POST headers: X-Vault-Token: "{{ data.json.auth.client_token }}" body: {"length":"15","symbols":"3","digits":"3"} return_content: yes status_code: 200 register: new_pass no_log: true
Step 3 – Create a new Vault record
In this example, we save credentials to the Vault K/V store, where the URL path defines the record name, and the password itself, along with any additional options are passed within the body.
With Curl….
curl -X POST https://1.2.3.4:8200/v1/tg-zone/data/machines/ rhel/tg-test/test-cred -d '{ "options": { "max_versions": "12" }, "data": { "password":"yW1\"x5y*ZQL!dP9"}}' -k -H 'X-Vault-Token:s.s34IWgJBEu9w9rmD0Falq4MX' Response: {"request_id":"8cfcd531-bce2-126f-1ccb-95aa33069c4d","lease_id":"", "renewable":false,"lease_duration":0,"data": {"created_time":"2020-09-20T07:41:59.185148621Z","deletion_time":"" ,"destroyed":false,"version":1},"wrap_info":null,"warnings":null, "auth":null}
As an Ansible Play…
It should be noted that the Ansible task below has a couple of additional variables defined within the paths, as would be expected when code needs to be repeatedly re-used.
- name: Post new server record to Vault uri: url: "{{ vault_url }}/v1/vtg-zone/data/machines/rhel/{{ ansible_hostname }}/{{ item }}_creds" body_format: json method: POST headers: X-Vault-Token: "{{ data.json.auth.client_token }}" body: { "options": { "max_versions": "12" }, "data": { "password":"{{ new_pass.json.data.value }}" }} return_content: yes status_code: 200 no_log: true
Step 4 – User update
At this stage, now that the password has been created and added to a relevant vault record, you finally will need to update the user account on the server with the new password. The below play is an example for Linux, however an equivalent windows module also exists if required.
https://docs.ansible.com/ansible/2.4/win_user_module.html
- name: update password user: name: "{{ item }}" password: "{{ new_pass.json.data.value | password_hash('sha512') }}" become: yes no_log: true
I hope this helps!