Creating Hashicorp Vault Records with Ansible

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!

Leave a Reply