def restore(path, password_file=None): """ Retrieves a file from the atk vault and restores it to its original location, re-encrypting it if it has changed. :param path: path to original file """ vault = VaultLib(get_vault_password(password_file)) atk_path = os.path.join(ATK_VAULT, path) # Load stored data with open(os.path.join(atk_path, 'encrypted'), 'rb') as f: old_data = f.read() with open(os.path.join(atk_path, 'hash'), 'rb') as f: old_hash = f.read() # Load new data with open(path, 'rb') as f: new_data = f.read() new_hash = hashlib.sha1(new_data).hexdigest() # Determine whether to re-encrypt if old_hash != new_hash: new_data = vault.encrypt(new_data) else: new_data = old_data # Update file with open(path, 'wb') as f: f.write(new_data) # Clean atk vault os.remove(os.path.join(atk_path, 'encrypted')) os.remove(os.path.join(atk_path, 'hash'))
def backup(path, password_file=None): """ Replaces the contents of a file with its decrypted counterpart, storing the original encrypted version and a hash of the file contents for later retrieval. """ vault = VaultLib(get_vault_password(password_file)) with open(path, 'r') as f: encrypted_data = f.read() # Normally we'd just try and catch the exception, but the # exception raised here is not very specific (just # `AnsibleError`), so this feels safer to avoid suppressing # other things that might go wrong. if vault.is_encrypted(encrypted_data): decrypted_data = vault.decrypt(encrypted_data) # Create atk vault files atk_path = os.path.join(ATK_VAULT, path) mkdir_p(atk_path) # ... encrypted with open(os.path.join(atk_path, 'encrypted'), 'wb') as f: f.write(encrypted_data) # ... hash with open(os.path.join(atk_path, 'hash'), 'wb') as f: f.write(hashlib.sha1(decrypted_data).hexdigest()) # Replace encrypted file with decrypted one with open(path, 'wb') as f: f.write(decrypted_data)
def test_split_header(self): v = VaultLib('ansible') data = "$ANSIBLE_VAULT;9.9;TEST\nansible" rdata = v._split_header(data) lines = rdata.split('\n') assert lines[0] == "ansible" assert v.cipher_name == 'TEST', "cipher name was not set" assert v.version == "9.9"
def test_remove_header(self): v = VaultLib('ansible') data = "$ANSIBLE_VAULT;9.9;TEST\n%s" % hexlify("ansible") rdata = v._split_headers_and_get_unhexified_data(data) lines = rdata.split('\n') assert lines[0] == "ansible" assert v.cipher_name == 'TEST', "cipher name was not set" assert v.version == "9.9"
def test_encyrpt_decrypt(self): if not HAS_AES: raise SkipTest v = VaultLib('ansible') v.cipher_name = 'AES' enc_data = v.encrypt("foobar") dec_data = v.decrypt(enc_data) assert enc_data != "foobar", "encryption failed" assert dec_data == "foobar", "decryption failed"
def test_encrypt_decrypt_aes256(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest v = VaultLib('ansible') v.cipher_name = 'AES256' enc_data = v.encrypt("foobar") dec_data = v.decrypt(enc_data) assert enc_data != "foobar", "encryption failed" assert dec_data == "foobar", "decryption failed"
def test_decrypt_decrypted(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest v = VaultLib('ansible') data = "ansible" error_hit = False try: dec_data = v.decrypt(data) except errors.AnsibleError, e: error_hit = True
def test_cipher_not_set(self): if not HAS_AES: raise SkipTest v = VaultLib('ansible') data = "ansible" error_hit = False try: enc_data = v.encrypt(data) except errors.AnsibleError, e: error_hit = True
def test_encrypt_decrypt_aes(self): if self._is_fips(): raise SkipTest('MD5 not available on FIPS enabled systems') if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest v = VaultLib('ansible') v.cipher_name = 'AES' enc_data = v.encrypt("foobar") dec_data = v.decrypt(enc_data) assert enc_data != "foobar", "encryption failed" assert dec_data == "foobar", "decryption failed"
def test_cipher_not_set(self): # not setting the cipher should default to AES256 if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest v = VaultLib('ansible') data = "ansible" error_hit = False try: enc_data = v.encrypt(data) except errors.AnsibleError, e: error_hit = True
def test_encrypt_encrypted(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest v = VaultLib('ansible') v.cipher_name = 'AES' data = "$ANSIBLE_VAULT;9.9;TEST\n%s" % hexlify("ansible") error_hit = False try: enc_data = v.encrypt(data) except errors.AnsibleError, e: error_hit = True
def test_add_header(self): v = VaultLib('ansible') v.cipher_name = "TEST" sensitive_data = "ansible" data = v._add_header(sensitive_data) lines = data.split('\n') assert len(lines) > 1, "failed to properly add header" header = lines[0] assert header.endswith(';TEST'), "header does end with cipher name" header_parts = header.split(';') assert len(header_parts) == 3, "header has the wrong number of parts" assert header_parts[0] == '$ANSIBLE_VAULT', "header does not start with $ANSIBLE_VAULT" assert header_parts[1] == v.version, "header version is incorrect" assert header_parts[2] == 'TEST', "header does end with cipher name"
def test_methods_exist(self): v = VaultLib('ansible') slots = ['is_encrypted', 'encrypt', 'decrypt', '_add_header', '_split_header',] for slot in slots: assert hasattr(v, slot), "VaultLib is missing the %s method" % slot
def test_add_header(self): v = VaultLib('ansible') v.cipher_name = "TEST" sensitive_data = "ansible" sensitive_hex = hexlify(sensitive_data) data = v._add_headers_and_hexify_encrypted_data(sensitive_data) open("/tmp/awx.log", "a").write("data: %s\n" % data) lines = data.split('\n') assert len(lines) > 1, "failed to properly add header" header = lines[0] assert header.endswith(';TEST'), "header does end with cipher name" header_parts = header.split(';') assert len(header_parts) == 3, "header has the wrong number of parts" assert header_parts[ 0] == '$ANSIBLE_VAULT', "header does not start with $ANSIBLE_VAULT" assert header_parts[1] == v.version, "header version is incorrect" assert header_parts[2] == 'TEST', "header does end with cipher name" assert lines[1] == sensitive_hex
class Vault(object): '''R/W an ansible-vault yaml file''' def __init__(self, password): self.password = password self.vault = VaultLib(password) def load(self, stream): '''read vault steam and return python object''' return yaml.load(self.vault.decrypt(stream)) def dump(self, data, stream=None): '''encrypt data and print stdout or write to stream''' yaml_text = yaml.dump(data, default_flow_style=False, allow_unicode=True) encrypted = self.vault.encrypt(yaml_text) if stream: stream.write(encrypted) else: return encrypted
class Vault(object): '''R/W an ansible-vault yaml file''' def __init__(self, password): self.password = password self.vault = VaultLib(password) def load(self, stream): '''read vault steam and return python object''' return yaml.load(self.vault.decrypt(stream)) def dump(self, data, stream=None): '''encrypt data and print stdout or write to stream''' yaml_text = yaml.dump( data, default_flow_style=False, allow_unicode=True) encrypted = self.vault.encrypt(yaml_text) if stream: stream.write(encrypted) else: return encrypted
def decrypt_diff(diff_part, password_file=None): """ Diff part is a string in the format: diff --git a/group_vars/foo b/group_vars/foo index c09080b..0d803bb 100644 --- a/group_vars/foo +++ b/group_vars/foo @@ -1,32 +1,33 @@ $ANSIBLE_VAULT;1.1;AES256 -61316662363730313230626432303662316330323064373866616436623565613033396539366263 -383632656663356364656531653039333965 +30393563383639396563623339383936613866326332383162306532653239636166633162323236 +62376161626137626133 Returns a tuple of decrypted old contents and decrypted new contents. """ vault = VaultLib(get_vault_password(password_file)) old_contents, new_contents = get_contents(diff_part) if vault.is_encrypted(old_contents): old_contents = vault.decrypt(old_contents) if vault.is_encrypted(new_contents): new_contents = vault.decrypt(new_contents) return old_contents, new_contents
# A register for if we executed a module. # Used to cut down on command calls when not recursive. module_executed = False # Tell _execute_module to delete the file if there is one file. delete_remote_tmp = (len(source_files) == 1) # If this is a recursive action create a tmp_path that we can share as the _exec_module create is too late. if not delete_remote_tmp: if "-tmp-" not in tmp_path: tmp_path = self.runner._make_tmp_path(conn) # expand any user home dir specifier dest = self.runner._remote_expand_user(conn, dest, tmp_path) vault = VaultLib(password=self.runner.vault_pass) for source_full, source_rel in source_files: vault_temp_file = None data = None try: data = open(source_full).read() except IOError: raise errors.AnsibleError("file could not read: %s" % source_full) if vault.is_encrypted(data): # if the file is encrypted and no password was specified, # the decrypt call would throw an error, but we check first # since the decrypt function doesn't know the file name
def __init__(self, password): self.password = password self.vault = VaultLib(password)
def test_is_encrypted(self): v = VaultLib(None) assert not v.is_encrypted("foobar"), "encryption check on plaintext failed" data = "$ANSIBLE_VAULT;9.9;TEST\n%s" % hexlify("ansible") assert v.is_encrypted(data), "encryption check on headered text failed"
#!/usr/bin/python from ansible.utils.vault import VaultLib import fileinput from sys import argv data = open(argv[2], 'rb').read() vl = VaultLib(argv[1]) decrypted_data = vl.decrypt(data) print(decrypted_data)
#!/usr/bin/env python from __future__ import print_function import os from kazoo.client import KazooClient try: from ansible.utils.vault import VaultLib except ImportError: # Ansible 2.0 has changed the vault location from ansible.parsing.vault import VaultLib os.system("docker-compose up -d zk") vault = VaultLib(open(os.path.expanduser("~/.vault-password.nestor")).read().strip()) DEFAULT_HOSTS = os.environ.get("ZK_HOST", "localhost:2181") zk = KazooClient(hosts=DEFAULT_HOSTS) zk.start() dir = 'config' path = '/nestor/config' for root, dirs, files in os.walk(dir): outroot = root.replace(dir, "").strip("/") outroot = os.path.join(path, outroot) for name in files: inpath = os.path.join(root, name) outpath = os.path.join(outroot, name) zk.ensure_path(outpath) with open(inpath) as f: data = f.read() if data.startswith("$ANSIBLE_VAULT"):
# make sure the password functions for the cipher error_hit = False try: ve.rekey_file('ansible2') except errors.AnsibleError, e: error_hit = True # verify decrypted content f = open(filename, "rb") fdata = f.read() f.close() shutil.rmtree(dirpath) assert error_hit == False, "error rekeying 1.0 file to 1.1" # ensure filedata can be decrypted, is 1.1 and is AES256 vl = VaultLib("ansible2") dec_data = None error_hit = False try: dec_data = vl.decrypt(fdata) except errors.AnsibleError, e: error_hit = True assert vl.cipher_name == "AES256", "wrong cipher name set after rekey: %s" % vl.cipher_name assert error_hit == False, "error decrypting migrated 1.0 file" assert dec_data.strip() == "foo", "incorrect decryption of rekeyed/migrated file: %s" % dec_data