Personal Infrastructure Part 2: Setting up Secret Storage for Ansible
Published on September 14, 2024 | Last updated on September 19, 2024
In this post, I'm going to explain one way to store secrets when using Ansible.
Ansible has the ability to encrypt and decrypt data, using what it calls the Ansible Vault.
Introduction
Many services require passwords, keys and other secrets. Some are used to access systems and services outside of the ansible deployment, and many are often randomly generated during the initial setup for use within the deployment.
In both cases, I like encrypting these using Ansible Vault. To make it a bit smoother, I take advantage of a few Ansible features. After digging around, and doing this a few times, I've settled on the following technique:
- I use a Python script to retrieve the key used by Ansible Vault to encrypt and decrypt.
- Edit
ansible.cfg
to use this script - Make another python script and Ansible Playbook that create new random secrets for usage within playbooks.
Pieces
1. Store Vault Password
I made a simple python script that for storing a password in the system's protected storage. This should work on Windows, MacOS and Linux (in Desktop mode), though I haven't tested on anything except MacOS:
#!/usr/bin/env python
import os
import sys
import keyring
import getpass
import argparse
import secrets
import string
APP_ENV = os.getenv("APP_ENV","development")
SERVICE_NAME = "AnsibleVault"
ACCOUNT_NAME = f"ansible_vault_password_rev_{APP_ENV}"
def get_vault_password():
password = keyring.get_password(SERVICE_NAME, ACCOUNT_NAME)
return password
def set_vault_password(generate=False):
if generate:
password = ''.join(secrets.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(32))
print("Generated a new secure password.")
else:
password = getpass.getpass("Enter New Ansible Vault password: ")
keyring.set_password(SERVICE_NAME, ACCOUNT_NAME, password)
return password
def clear_vault_password():
keyring.delete_password(SERVICE_NAME, ACCOUNT_NAME)
print("Ansible Vault password has been cleared.")
if __name__ == "__main__":
if len(sys.argv) == 1:
password = get_vault_password()
if not password:
sys.stderr.write("No Ansible Vault password found. Please set or generate one.")
sys.exit(1)
print(password)
else:
parser = argparse.ArgumentParser(description="Manage Ansible Vault password")
parser.add_argument("action", choices=["set", "generate", "clear"], help="Action to perform")
args = parser.parse_args()
if args.action == "set":
set_vault_password()
print("Ansible Vault password has been set.")
elif args.action == "generate":
stored_password = keyring.get_password(SERVICE_NAME, ACCOUNT_NAME)
if stored_password:
print("Ansible Vault password already exists. Use set to set it, or clear to clear it.")
else:
set_vault_password(generate=True)
print("Ansible Vault password has been set.")
elif args.action == "clear":
clear_vault_password()
Configure Ansible to use Python Script
I edited ansible.cfg
to use the new python script:
cat ansible.cfg master ⬆ ✖ ◼
[defaults]
inventory = inventory/hosts
remote_user = ansible_user
private_key_file = ~/.ssh/id_ed25519_aslan_ansible
host_key_checking = False
+++ vault_password_file = get_vault_pass.py
interpreter_python = auto_silent
Important: Be sure to make sure get_vault_pass.py
is executable and has an appropriate #!/usr/bin/env python
or similar.
Create initial vault password
APP_ENV=staging python3 physical-server-ansible-playbook/get_vault_pass.py generate
APP_ENV=development python3 physical-server-ansible-playbook/get_vault_pass.py generate
APP_ENV=production python3 physical-server-ansible-playbook/get_vault_pass.py generate