Skip to content

Instantly share code, notes, and snippets.

@filipenf
Last active March 28, 2023 20:39
Show Gist options
  • Save filipenf/2cc72af47e3570afaa9d3bf2e71658c3 to your computer and use it in GitHub Desktop.
Save filipenf/2cc72af47e3570afaa9d3bf2e71658c3 to your computer and use it in GitHub Desktop.
Script to convert an ansible vault into a yaml file with encrypted strings
#!/bin/bash
#Vault password is 1
echo "Converting vault to yaml format:"
ansible-vault decrypt --output - vault | python ./convert_vault.py > new-vault.yml
echo "Decrypting a variable from the converted vault"
ansible localhost -i localhost, -e @new-vault.yml -m debug -a 'var=secret' --ask-vault-pas
import sys
import yaml
import argparse
from ansible.parsing.vault import VaultLib
from ansible.cli import CLI
from ansible import constants as C
from ansible.parsing.dataloader import DataLoader
from ansible.parsing.yaml.dumper import AnsibleDumper
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
"""
This script reads a yaml file and dumps it back while encrypting
the values but keeping the keys plaintext. To convert an ansible
vault file format into yaml you can do:
ansible-vault decrypt --output - vault | \
python ./convert_vault.py > new-vault
"""
def encrypt_string(decrypted_secret, vault_id=None):
"""
Encrypts string
"""
loader = DataLoader()
vault_secret = CLI.setup_vault_secrets(
loader=loader,
vault_ids=C.DEFAULT_VAULT_IDENTITY_LIST
)
vault = VaultLib(vault_secret)
return AnsibleVaultEncryptedUnicode(
vault.encrypt(decrypted_secret,
vault_id=vault_id))
def encrypt_dict(d, vault_id=None):
for key in d:
value = d[key]
if isinstance(value, str):
d[key] = encrypt_string(value, vault_id)
elif isinstance(value, list):
for item in value:
encrypt_dict(item)
elif isinstance(value, dict):
encrypt_dict(value)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--input-file',
help='File to read from',
default='-')
parser.add_argument('--vault-id',
help='Vault id used for the encryption')
args = parser.parse_args()
in_file = sys.stdin if args.input_file == '-' else open(args.input_file)
data = yaml.load(in_file, Loader=AnsibleLoader)
encrypt_dict(data, vault_id=args.vault_id)
print(yaml.dump(data, Dumper=AnsibleDumper))
if __name__ == "__main__":
main()
$ANSIBLE_VAULT;1.1;AES256
32656465316437646133376138393234386439303536343631343763396661386339366431346263
3036313832303437623834633363396333343338643930640a343266336434383434646530386664
61623665363935373738366634613363626132613861666432396630396436306534303265303430
3637306164666265380a386431363364636666626263653864613866323235366638386261353433
30643765623033353435313230663933353931616530663735303437393138663738
@hs-amartin
Copy link

Also note ansible/ansible#72703, this does not lookup the vault provided, just uses the default, and labels it with the vault-id provided as the label. Something very similar to the below is what I ended up using.

#Adapted from https://gist.github.com/filipenf/2cc72af47e3570afaa9d3bf2e71658c3

import sys
import yaml
import argparse
from ansible.parsing.vault import VaultLib
from ansible.cli import CLI
from ansible import constants as C
from ansible.parsing.dataloader import DataLoader
from ansible.parsing.yaml.dumper import AnsibleDumper
from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode


class VaultHelper():
    def __init__(self, vault_id):
        loader = DataLoader()
        vaults = [v for v in C.DEFAULT_VAULT_IDENTITY_LIST if v.startswith('{0}@'.format(vault_id))]
        if len(vaults) != 1:
            raise ValueError("'{0}' does not exist in ansible.cfg '{1}'".format(vault_id, C.DEFAULT_VAULT_IDENTITY_LIST))

        self.vault_id = vault_id
        vault_secret = CLI.setup_vault_secrets(
            loader=loader,
            vault_ids=vaults
        )
        self.vault = VaultLib(vault_secret)


    def convert_vault_to_strings(self, vault_data):
        decrypted = self.vault.decrypt(vault_data)
        d = yaml.load(decrypted, Loader=AnsibleLoader)
        self._encrypt_dict(d)
        return d


    def _encrypt_dict(self, d):
        for key in d:
            value = d[key]
            if isinstance(value, str):
                d[key] = AnsibleVaultEncryptedUnicode(
                    self.vault.encrypt(plaintext=value, vault_id=self.vault_id))
            elif isinstance(value, list):
                for item in value:
                    self._encrypt_dict(item)
            elif isinstance(value, dict):
                self._encrypt_dict(value)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--input-file',
                        help='File to read from',
                        required=True)
    parser.add_argument('--output-file',
                        help='File to read from',
                        required=True)
    parser.add_argument('--vault-id',
                        help='Vault id used for the encryption',
                        required=True)
    args = parser.parse_args()
    original_secrets = open(args.input_file).read()
    vault = VaultHelper(args.vault_id)
    converted_secrets = vault.convert_vault_to_strings(original_secrets)

    with open(args.output_file, 'w+') as f:
        yaml.dump(converted_secrets, Dumper=AnsibleDumper, stream=f)


if __name__ == "__main__":
    main()

@srgvg
Copy link

srgvg commented Feb 23, 2021

Good stuff! Just what I was looking for.

@monicaruttle
Copy link

@filipenf This is great, and saved me a ton of time! I'm not sure list handling is correct, so I used the following slightly modified version of encrypt_dict.

def encrypt_dict(d, vault_id=None):
    for key in d:
        value = d[key]
        if isinstance(value, str):
            d[key] = encrypt_string(value, vault_id)
        elif isinstance(value, list):
            encrypted_list = []
            for item in value:
                encrypted_list.append(encrypt_string(item))
            d[key] = encrypted_list
        elif isinstance(value, dict):
            encrypt_dict(value)

@gthieleb
Copy link

Thanks for this contribution. Really elegant and saved me some hours. I tweaked it a bit to support partial encryption, so I can keep the username plain but the password is still encrypted.

@jrgoldfinemiddleton
Copy link

This was such a godsend. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment