In my previous article about Secrets Managers, I mentioned Ansible Vault as one solution, and if you are already using Ansible for deployment, it may be an obvious choice.
I’m guessing that your Ansible code has sensitive information that is scattered all over:
- Passwords
- Database connection strings
- API Keys
- Private keys for server access
- Private keys for SSL certificates
You don’t want to keep these secrets in your source code, but you also need to keep the code manageable, especially when you have multiple target environments with different sets of credentials.
Ansible has a solution (up to a certain scale) that solves the problem of secrets management. The best part is that it doesn’t change a whole lot about how you do things already.
Compared to other Secrets Managers, the Ansible Vault is simple to implement and maintain. It supports multi/hybrid cloud environments and helps you avoid provider lock-in.
Ansible Vault
What the Ansible Vault does is simple: It allows you to encrypt entire files or strings, which you can then safely commit to source control. Later on, when you want to run the Playbook, Ansible decrypts the files in runtime and continues as usual.
To better illustrate how Ansible Vault works, I’m using a Spring Boot application as an example. The same applies to any other project type.
Ansible Vault for securing Spring Boot application.properties
I created a small Ansible playbook that copies a Java Spring Boot application.properties file to a server. The file contains the setting for the app, and includes the database connection string, and password:
# database init, supports mysql too
database=hsqldb
spring.datasource.schema=classpath*:db/${database}/schema.sql
spring.datasource.data=classpath*:db/${database}/data.sql
spring.datasource.password=mypassword
# Web
spring.thymeleaf.mode=HTML
# JPA
spring.jpa.hibernate.ddl-auto=none
# Internationalization
spring.messages.basename=messages/messages
# Actuator / Management
management.endpoints.web.base-path=/manage
management.endpoints.web.exposure.include=*
# Logging
logging.level.org.springframework=INFO
# logging.level.org.springframework.web=DEBUG
# logging.level.org.springframework.context.annotation=TRACE
# Maximum time static resources should be cached
spring.resources.cache.cachecontrol.max-age=12h
As you can see, the problem is that the password is right there in clear-text.
Creating an Ansible Jinja2 template
Because we only want to encrypt the database password and not the entire file, we can take advantage of Ansible templates and use Jinja2.
Using templates also allows us to support multiple deployment environments without keeping multiple versions of the same file.
I created an application.properties.j2 file, which now looks like this:
# database init, supports mysql too
database=hsqldb
spring.datasource.schema=classpath*:db/${database}/schema.sql
spring.datasource.data=classpath*:db/${database}/data.sql
**spring.datasource.password={{datasource_password}}**
...
You can see that we now have an ansible variable as the value of the spring.datasource.password.
A simple Ansible playbook before using the Ansible Vault
Below is a very rudimentary Ansible playbook that copies the rendered template to our server:
---
- hosts: webservers
vars:
datasource_password: mypassword
tasks:
- name: Copy the application.properties file to the server
template:
src: env/application.properties.j2
dest: /opt/myapp/application.properties
But then it seems like we just moved the problem around. We still have a clear text password in the deployment code.
Using the Ansible Vault to encrypt the database password
Because we are encrypting a string, as opposed to the entire file, we should use the encrypt_string flag in the ansible-vault command.
I also passed the –ask-vault-pass flag so that it will prompt me for the vault password, but you can use a password file instead.
➜ spring-petclinic git:(master) ✗ ansible-vault encrypt_string --ask-vault-pass "mypassword" --name "datasource_password"
New Vault password:
Confirm New Vault password:
datasource_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
34366530376565353931396336623365393432636466363766633333643066316362336539373532
3930313363643064623464393233353034623735363731640a363962656262646339663633633930
61663931346431393862643663303532326236643430333630653631316137306165653262396430
6335613237633530360a633139396432336165333062353131356563313362313865633639666262
6434
Encryption successful
The result is a key/value pair, where the value is your encrypted secret that you can safely keep in the code.
The playbook that now contains the encrypted secret
Paste in the key/value pair into the playbook and replace the clear-text password:
---
- hosts: webservers
vars:
datasource_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
34366530376565353931396336623365393432636466363766633333643066316362336539373532
3930313363643064623464393233353034623735363731640a363962656262646339663633633930
61663931346431393862643663303532326236643430333630653631316137306165653262396430
6335613237633530360a633139396432336165333062353131356563313362313865633639666262
6434
tasks:
- name: Copy the application.properties file to env
template:
src: env/application.properties.j2
dest: /opt/myapp/application.properties
Running the Playbook
The ansible-playbook command needs a way to decrypt the values, so you have to pass on the –ask-vault-pass flag, or if using a password file, the –vault-password-file flag followed by the path for the file.
ansible-playbook --ask-vault-pass -i inventory playbook.yaml
That’s it. Ansible will decrypt the file, renders the template, populating it with the real password, and then copies the file to the server.
That’s it. Ansible decrypts the file, render the template, populating it with the real password, and then copy the file to our server.
When to encrypt entire files
While, in some cases, I wouldn’t recommend encrypting entire files, as it makes life difficult in development, there are a few cases where you should use this feature.
Private keys are an excellent example of such files. You will probably not going to make too many changes or reference them during your development lifecycle.
What’s next?
Ansible Vault has a few more options that I didn’t cover in the post, mainly the use of Vault Labels, or Ids, which allow you to use different vault passwords for different environments.
Using multiple “vaults” comes in handy in CI / CD setups, and I recommend experimenting with it if you are going to end up using Ansible Vault as your Secrets Manager.