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_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(six.b("ansible")) error_hit = False try: enc_data = v.encrypt(data) except errors.AnsibleError as e: error_hit = True assert error_hit, "No error was thrown when trying to encrypt data with a header"
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 as e: error_hit = True assert not error_hit, "An error was thrown when trying to encrypt data without the cipher set" assert v.cipher_name == "AES256", "cipher name is not set to AES256: %s" % v.cipher_name
def vault_encrypt(plaintext, secret): ''' Vault encrypt a piece of data. ''' try: vault = VaultLib() secret_file = get_file_vault_secret(filename=secret, loader=DataLoader()) secret_file.load() vault.secrets = [('default', secret_file)] return vault.encrypt(plaintext) except AnsibleError as e: logger.critical(f"Cannot encrypt string: {e}") sys.exit(1)
def vault_encrypt(plaintext, secret): """ Vault encrypt a piece of data. """ try: vault = VaultLib() secret_file = get_file_vault_secret(filename=secret, loader=DataLoader()) secret_file.load() vault.secrets = [("default", secret_file)] return vault.encrypt(plaintext) except AnsibleError as e: logger.critical("Cannot encrypt string: {}".format(e)) sys.exit(1)
def vault_encrypt(plaintext, secret): """ Vault encrypt a piece of data. """ try: vault = VaultLib() secret_file = get_file_vault_secret(filename=secret, loader=DataLoader()) secret_file.load() vault.secrets = [('default', secret_file)] return vault.encrypt(plaintext) except AnsibleError as exc: LOGGER.critical('Cannot encrypt string: %s', exc) sys.exit(1)
class Vault(object): '''R/W an ansible-vault yaml file''' def __init__(self, password): self._ansible_ver = _ansible_ver self.secret = password.encode('utf-8') self.vault = VaultLib(self._make_secrets(self.secret)) def _make_secrets(self, secret): if self._ansible_ver < 2.4: return secret from ansible.constants import DEFAULT_VAULT_ID_MATCH from ansible.parsing.vault import VaultSecret return [(DEFAULT_VAULT_ID_MATCH, VaultSecret(secret))] def load_raw(self, stream): """Read vault stream and return raw data.""" return self.vault.decrypt(stream) def dump_raw(self, text, stream=None): """Encrypt raw data and write to stream.""" encrypted = self.vault.encrypt(text) if stream: stream.write(encrypted) else: return encrypted def load(self, stream): """Read vault steam and return python object.""" return yaml.safe_load(self.load_raw(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) return self.dump_raw(yaml_text, stream=stream)
def encrypt_string(passwordfile, var_name, plain_text): loader = DataLoader() secret = FileVaultSecret(filename=passwordfile, encoding='utf8', loader=loader) secret.load() encrypt_vault_id = 'default' encrypt_secret = secret vault_secrets = [(encrypt_vault_id, encrypt_secret)] vault = VaultLib(vault_secrets) b_vaulttext = vault.encrypt(plain_text, secret=secret, vault_id=encrypt_vault_id) code = b_vaulttext.decode() code = code.rstrip() code = code.replace("\n", "\n ") return f"{var_name}: !vault |\n {code}"
def encrypt_file(path, password_file, newpath=None, secrets=None): """Encrypts an Ansible Vault file. Returns encrypted data. Set newpath to write the result somewhere. Set secrets to specify inline secret addresses.""" # log.debug('Reading decrypted data from {}...'.format(path)) with open(path, 'rb') as f: data = f.read() if not data: raise ValueError('Unable to parse/read file {}'.format(path)) else: log.debug('Got vars/file: {}'.format(data)) with open(password_file) as f: p = f.read().strip() log.debug('Read pass from {}: {}'.format(password_file, p)) vault = VaultLib([(DEFAULT_VAULT_ID_MATCH, VaultSecret(p.encode('utf-8'))) ]) encrypted = vault.encrypt(data) with open(newpath, 'wb') as f: f.write(encrypted) return encrypted
class Vault(object): '''R/W an ansible-vault yaml file''' def __init__(self, password): self._ansible_ver = _ansible_ver self.secret = password.encode('utf-8') self.vault = VaultLib(self._make_secrets(self.secret)) def _make_secrets(self, secret): if self._ansible_ver < 2.4: return secret from ansible.constants import DEFAULT_VAULT_ID_MATCH from ansible.parsing.vault import VaultSecret return [(DEFAULT_VAULT_ID_MATCH, VaultSecret(secret))] def load_raw(self, stream): """Read vault stream and return raw data.""" return self.vault.decrypt(stream) def dump_raw(self, text, stream=None): """Encrypt raw data and write to stream.""" encrypted = self.vault.encrypt(text) if stream: stream.write(encrypted) else: return encrypted def load(self, stream): """Read vault steam and return python object.""" return yaml.safe_load(self.load_raw(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) return self.dump_raw(yaml_text, stream=stream)
class Convert: def __init__(self, password): self.text = b'' self.err = '' self.password = password if password: self.vault = VaultLib([(DEFAULT_VAULT_ID_MATCH, VaultSecret(password.encode("utf-8")))]) def convert(self, source): try: if self.vault and source: self.decrypt(source) if source.startswith( "$ANSIBLE_VAULT") else self.encrypt(source) finally: return { 'password': self.password, 'source': source, 'result': self.text.decode('utf-8'), 'error': self.err } def encrypt(self, source): logging.debug("Request encrypt") try: self.text = self.vault.encrypt(source) except: self.err = "Impossible de chiffrer ces informations" logging.error("Problem: %s", sys.exc_info()[0]) def decrypt(self, source): logging.debug("Request decrypt") try: self.text = self.vault.decrypt(source) except: self.err = "La clé ne correspond pas" logging.error("Problem: %s", sys.exc_info()[0])
class Vault(object): """ R/W an ansible-vault file """ def __init__(self, password): self.password = password try: from ansible.parsing.vault import VaultSecret from ansible.module_utils._text import to_bytes pass_bytes = to_bytes(password, encoding='utf-8', errors='strict') secrets = [('password', VaultSecret(_bytes=pass_bytes))] self.vault = VaultLib(secrets=secrets) except ImportError: self.vault = VaultLib(password) def load(self, stream): """ Read vault steam and return python object :param stream: The stream to read data from :returns: The decrypted data """ try: return self.vault.decrypt(stream) except AnsibleError: print(t("Unable to decrypt using given password")) sys.exit(1) def load_secure_file(self, secure_file): """ Read vault secured file and return python object :param secure_file: The file to read data from :returns: The decrpted data """ return self.load(open(secure_file).read()) def load_as_json(self, secure_file): """ Read vault secured file and return json decoded object :param secure_file: The file to read data from as json :returns: The JSON data """ return json.loads(self.load_secure_file(secure_file).decode('UTF-8')) def dump(self, data, stream=None): """ Encrypt data and print stdout or write to stream :param data: The information to be encrypted :param stream: If not None the location to write the encrypted data to. :returns: If stream is None then the encrypted bytes otherwise None. """ encrypted = self.vault.encrypt(data) if stream: stream.write(encrypted) else: return encrypted def dump_as_json(self, obj, stream=None): """ Convert object to json and encrypt the data. :param obj: Python object to convert to json :param stream: If not None the location to write the encrypted data to. If this is a file in Python 3, it must be open in binary mode. :returns: If stream is None then the encrypted bytes otherwise None. """ data = json.dumps(obj, separators=(',', ': ')) return self.dump(data, stream) def dump_as_json_to_file(self, obj, file_path): """ Convert object to json and encrypt the data. :param obj: Python object to convert to json :param file_path: The file to write data to via temp file """ with tempfile.NamedTemporaryFile(delete=False) as data_temp: self.dump_as_json(obj, data_temp) data_temp.close() move(data_temp.name, os.path.abspath(file_path)) def dump_as_yaml(self, obj, stream=None): """ Convert object to yaml and encrypt the data. :param obj: Python object to convert to yaml :param stream: If not None the location to write the encrypted data to. :returns: If stream is None then the encrypted bytes otherwise None. """ data = yaml.dump(obj, default_flow_style=False) return self.dump(data, stream) def dump_as_yaml_to_file(self, obj, file_path): """ Convert object to yaml and encrypt the data. :param obj: Python object to convert to yaml :param file_path: The file to write data to via temp file """ with tempfile.NamedTemporaryFile(delete=False) as data_temp: self.dump_as_yaml(obj, data_temp) data_temp.close() move(data_temp.name, os.path.abspath(file_path))
class Vault(object): """ R/W an ansible-vault file """ def __init__(self, password): self.password = password self.vault = VaultLib(password) def load(self, stream): """ Read vault steam and return python object :param stream: The stream to read data from :returns: The decrypted data """ return self.vault.decrypt(stream) def load_secure_file(self, secure_file): """ Read vault secured file and return python object :param secure_file: The file to read data from :returns: The decrpted data """ return self.load(open(secure_file).read()) def load_as_json(self, secure_file): """ Read vault secured file and return json decoded object :param secure_file: The file to read data from as json :returns: The JSON data """ return json.loads(self.load_secure_file(secure_file).decode('UTF-8')) def dump(self, data, stream=None): """ Encrypt data and print stdout or write to stream :param data: The information to be encrypted :param stream: If not None the location to write the encrypted data to. :returns: If stream is None then the encrypted bytes otherwise None. """ encrypted = self.vault.encrypt(data) if stream: stream.write(encrypted) else: return encrypted def dump_as_json(self, obj, stream=None): """ Convert object to json and encrypt the data. :param obj: Python object to convert to json :param stream: If not None the location to write the encrypted data to. :returns: If stream is None then the encrypted bytes otherwise None. """ data = json.dumps(obj, separators=(',', ': ')) return self.dump(data, stream) def dump_as_json_to_file(self, obj, file_path): """ Convert object to json and encrypt the data. :param obj: Python object to convert to json :param file_path: The file to write data to via temp file """ tempdir = gettempdir() tempfilename = 'tmp_' + str(uuid.uuid4()) temppath = os.path.join(tempdir, tempfilename) with open(temppath, 'wb') as data_temp: self.dump_as_json(obj, data_temp) data_temp.close() move(temppath, os.path.abspath(file_path)) def dump_as_yaml(self, obj, stream=None): """ Convert object to yaml and encrypt the data. :param obj: Python object to convert to yaml :param stream: If not None the location to write the encrypted data to. :returns: If stream is None then the encrypted bytes otherwise None. """ data = yaml.dump(obj, default_flow_style=False) return self.dump(data, stream) def dump_as_yaml_to_file(self, obj, file_path): """ Convert object to yaml and encrypt the data. :param obj: Python object to convert to yaml :param file_path: The file to write data to via temp file """ tempdir = gettempdir() tempfilename = 'tmp_' + str(uuid.uuid4()) temppath = os.path.join(tempdir, tempfilename) with open(temppath, 'wb') as data_temp: self.dump_as_yaml(obj, data_temp) data_temp.close() move(temppath, os.path.abspath(file_path))
class VaultpwCLI(CLI): ''' ''' VALID_ACTIONS = frozenset(("show", "store")) def __init__(self, args): super().__init__(args) self.encrypt_secret = None self.encrypt_vault_id = None def set_action(self): super().set_action() if self.action == "show": self.parser.set_usage( "usage: %prog show /path/to/example_password.yml") elif self.action == "store": self.parser.set_usage( "usage: %prog store /path/to/example_password.yml [-c command]" ) self.parser.add_option('-c', '--command', dest='password_command', action='store', type='string', help="command to run to obtain a password") self.parser.add_option( '--encrypt-vault-id', default=[], dest='encrypt_vault_id', action='store', type='string', help= 'the vault id used to encrypt (required if more than one vault-id is provided)' ) def init_parser(self): super().init_parser( usage="usage: %%prog [%s] [options] /path/to/example_password.yml" % "|".join(sorted(self.VALID_ACTIONS)), desc= "utility to store or fetch vault-encrypted passwords in YAML inventory files", epilog= "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])) opt_help.add_vault_options(self.parser) self.set_action() def post_process_args(self, options, args): options, args = super().post_process_args(options, args) self.validate_conflicts(options, vault_opts=True, vault_rekey_opts=False) display.verbosity = options.verbosity if options.vault_ids: for vault_id in options.vault_ids: if u';' in vault_id: raise AnsibleOptionsError( "Invalid character ';' found in vault id: %s" % vault_id) return options, args def run(self): super().run() self.loader = DataLoader() vault_ids = C.DEFAULT_VAULT_IDENTITY_LIST + list( context.CLIARGS['vault_ids']) vault_secrets = self.setup_vault_secrets( self.loader, vault_ids=vault_ids, vault_password_files=list(context.CLIARGS['vault_password_files']), ask_vault_pass=context.CLIARGS['ask_vault_pass']) if not vault_secrets: raise AnsibleOptionsError( "A vault password is required to use ansible-vault") encrypt_vault_id = context.CLIARGS.get( 'encrypt_vault_id') or C.DEFAULT_VAULT_ENCRYPT_IDENTITY if len(vault_secrets) > 1 and not encrypt_vault_id: raise AnsibleOptionsError( "Use '--encrypt-vault-id id' to choose one of the following vault ids to use for encryption: %s" % ','.join([x[0] for x in vault_secrets])) encrypt_secret = match_encrypt_secret( vault_secrets, encrypt_vault_id=encrypt_vault_id) self.encrypt_vault_id = encrypt_secret[0] self.encrypt_secret = encrypt_secret[1] self.loader.set_vault_secrets(vault_secrets) self.vault = VaultLib(vault_secrets) if len(context.CLIARGS['args']) != 1: raise AnsibleOptionsError( "Exactly one inventory file must be specified") self.file = os.path.expanduser(context.CLIARGS['args'][0]) old_umask = os.umask(0o077) self.execute() os.umask(old_umask) def execute_store(self): ''' Takes the path to an inventory file such as inventory/group_vars/tag_Cluster_xxx/secrets/example_password.yml and overwrites the file with an assignment of "example_password: password" in vault-encrypted YAML format. The password is obtained by prompting the user or, if a command is specified, by running the command and reading stdout. ''' b_plaintext = b'' command = context.CLIARGS['password_command'] if command: try: pw = subprocess.run(command, capture_output=True) if pw.returncode != 0: raise Exception('non-zero exit code: %s' % pw.returncode) b_plaintext = pw.stdout.strip() except Exception as e: print("ERROR: password command failed: %s" % str(e), file=sys.stderr) sys.exit(-1) else: b_plaintext = to_bytes(display.prompt("Password: "******"ERROR: cannot encrypt password: %s" % str(e), file=sys.stderr) sys.exit(-1) name = os.path.basename(self.file).replace('.yml', '') lines = [] lines.append("%s: !vault |\n" % name) for l in to_text(b_ciphertext).splitlines(): lines.append(" %s\n" % l) try: fh = open(self.file, 'wb') fh.write(to_bytes(''.join(lines))) fh.close() except Exception as e: print("ERROR: cannot write output to %s: %s" % (self.file, str(e)), file=sys.stderr) sys.exit(-1) def execute_show(self): ''' Takes the path to an inventory file such as inventory/group_vars/tag_Cluster_xxx/secrets/example_password.yml and prints the password defined therein. The file must contain a variable assignment of the form "example_password: password"; either the whole file is vault-encrypted, or only the password is. ''' if not os.path.exists(self.file): print("ERROR: inventory file does not exist: %s" % self.file, file=sys.stderr) sys.exit(-1) try: name = os.path.basename(self.file).replace('.yml', '') y = self.loader.load_from_file(self.file) print(y[name]) except Exception as e: print("ERROR: cannot show password from %s: %s" % (self.file, str(e)), file=sys.stderr) sys.exit(-1)
def test_encrypt(self): v = VaultLib(password='******') plaintext = u'Some text to encrypt.' ciphertext = v.encrypt(plaintext) self.assertIsInstance(ciphertext, (bytes, str))
def create(self): try: print('') new_file = self.args.file if new_file is None: new_file = input('File to create: ') if os.path.exists(os.path.join(self.args.vault_path, new_file)): eprint('This file already exists') sys.exit(2) plugin_name = self.args.plugin if plugin_name is None: plugin_name = input('Keyring plugin name to use [' + ', '.join(list_plugins()) + ']: ') plugin = self.get_plugin_instance(plugin_name) id = plugin.generate_id(self.args.plugin_vars) print('New ID to use: ' + id) print('') stdin_pass = self.args.stdin_pass if not stdin_pass and sys.stdin.isatty(): password = getpass.getpass('New password: '******'Confirm password: '******'Passwords missmatch') sys.exit(2) else: password = sys.stdin.read() password = password.strip() if password == '': print('Your password is empty !') sys.exit(2) except KeyboardInterrupt: print('') sys.exit(0) try: new_version = plugin.set_password(id, password) id = plugin.append_id_version(new_version) vault_metadata = get_metadata(self.args.vault_path) vault_metadata['vault_ids'].append({ METADATA_ID_KEY: id, METADATA_PLUGIN_KEY: plugin_name, METADATA_VAULT_FILES: [new_file] }) write_metadata(vault_metadata, self.args.vault_path) VaultLib = get_vault_lib() vault_api = VaultLib(_make_secrets(password)) with open(os.path.join(self.args.vault_path, new_file), 'w') as stream: encrypted = str(vault_api.encrypt('---'), 'utf-8') stream.write(encrypted) except Exception as e: eprint(e) sys.exit(2) if (self.args.verbose): import traceback traceback.print_exc()
class TestVaultLib(unittest.TestCase): def setUp(self): self.v = VaultLib('test-vault-password') def test_encrypt(self): plaintext = u'Some text to encrypt in a café' b_vaulttext = self.v.encrypt(plaintext) self.assertIsInstance(b_vaulttext, six.binary_type) b_header = b'$ANSIBLE_VAULT;1.1;AES256\n' self.assertEqual(b_vaulttext[:len(b_header)], b_header) def test_encrypt_bytes(self): plaintext = to_bytes(u'Some text to encrypt in a café') b_vaulttext = self.v.encrypt(plaintext) self.assertIsInstance(b_vaulttext, six.binary_type) b_header = b'$ANSIBLE_VAULT;1.1;AES256\n' self.assertEqual(b_vaulttext[:len(b_header)], b_header) def test_is_encrypted(self): self.assertFalse( self.v.is_encrypted(b"foobar"), msg="encryption check on plaintext yielded false positive") b_data = b"$ANSIBLE_VAULT;9.9;TEST\n%s" % hexlify(b"ansible") self.assertTrue(self.v.is_encrypted(b_data), msg="encryption check on headered text failed") def test_format_output(self): self.v.cipher_name = "TEST" b_ciphertext = b"ansible" b_vaulttext = self.v._format_output(b_ciphertext) b_lines = b_vaulttext.split(b'\n') self.assertGreater(len(b_lines), 1, msg="failed to properly add header") b_header = b_lines[0] self.assertTrue(b_header.endswith(b';TEST'), msg="header does not end with cipher name") b_header_parts = b_header.split(b';') self.assertEqual(len(b_header_parts), 3, msg="header has the wrong number of parts") self.assertEqual(b_header_parts[0], b'$ANSIBLE_VAULT', msg="header does not start with $ANSIBLE_VAULT") self.assertEqual(b_header_parts[1], self.v.b_version, msg="header version is incorrect") self.assertEqual(b_header_parts[2], b'TEST', msg="header does not end with cipher name") def test_split_header(self): b_vaulttext = b"$ANSIBLE_VAULT;9.9;TEST\nansible" b_ciphertext = self.v._split_header(b_vaulttext) b_lines = b_ciphertext.split(b'\n') self.assertEqual(b_lines[0], b"ansible", msg="Payload was not properly split from the header") self.assertEqual(self.v.cipher_name, u'TEST', msg="cipher name was not properly set") self.assertEqual(self.v.b_version, b"9.9", msg="version was not properly set") def test_encrypt_decrypt_aes(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = u'AES' self.v.b_password = b'ansible' # AES encryption code has been removed, so this is old output for # AES-encrypted 'foobar' with password 'ansible'. b_vaulttext = b'''$ANSIBLE_VAULT;1.1;AES 53616c7465645f5fc107ce1ef4d7b455e038a13b053225776458052f8f8f332d554809d3f150bfa3 fe3db930508b65e0ff5947e4386b79af8ab094017629590ef6ba486814cf70f8e4ab0ed0c7d2587e 786a5a15efeb787e1958cbdd480d076c ''' b_plaintext = self.v.decrypt(b_vaulttext) self.assertEqual(b_plaintext, b"foobar", msg="decryption failed") def test_encrypt_decrypt_aes256(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = u'AES256' plaintext = u"foobar" b_vaulttext = self.v.encrypt(plaintext) b_plaintext = self.v.decrypt(b_vaulttext) self.assertNotEqual(b_vaulttext, b"foobar", msg="encryption failed") self.assertEqual(b_plaintext, b"foobar", msg="decryption failed") def test_encrypt_decrypt_aes256_existing_vault(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = u'AES256' b_orig_plaintext = b"Setec Astronomy" vaulttext = u'''$ANSIBLE_VAULT;1.1;AES256 33363965326261303234626463623963633531343539616138316433353830356566396130353436 3562643163366231316662386565383735653432386435610a306664636137376132643732393835 63383038383730306639353234326630666539346233376330303938323639306661313032396437 6233623062366136310a633866373936313238333730653739323461656662303864663666653563 3138''' b_plaintext = self.v.decrypt(vaulttext) self.assertEqual(b_plaintext, b_plaintext, msg="decryption failed") b_vaulttext = to_bytes(vaulttext, encoding='ascii', errors='strict') b_plaintext = self.v.decrypt(b_vaulttext) self.assertEqual(b_plaintext, b_orig_plaintext, msg="decryption failed") def test_encrypt_decrypt_aes256_bad_hmac(self): # FIXME This test isn't working quite yet. raise SkipTest if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = 'AES256' # plaintext = "Setec Astronomy" enc_data = '''$ANSIBLE_VAULT;1.1;AES256 33363965326261303234626463623963633531343539616138316433353830356566396130353436 3562643163366231316662386565383735653432386435610a306664636137376132643732393835 63383038383730306639353234326630666539346233376330303938323639306661313032396437 6233623062366136310a633866373936313238333730653739323461656662303864663666653563 3138''' b_data = to_bytes(enc_data, errors='strict', encoding='utf-8') b_data = self.v._split_header(b_data) foo = binascii.unhexlify(b_data) lines = foo.splitlines() # line 0 is salt, line 1 is hmac, line 2+ is ciphertext b_salt = lines[0] b_hmac = lines[1] b_ciphertext_data = b'\n'.join(lines[2:]) b_ciphertext = binascii.unhexlify(b_ciphertext_data) # b_orig_ciphertext = b_ciphertext[:] # now muck with the text # b_munged_ciphertext = b_ciphertext[:10] + b'\x00' + b_ciphertext[11:] # b_munged_ciphertext = b_ciphertext # assert b_orig_ciphertext != b_munged_ciphertext b_ciphertext_data = binascii.hexlify(b_ciphertext) b_payload = b'\n'.join([b_salt, b_hmac, b_ciphertext_data]) # reformat b_invalid_ciphertext = self.v._format_output(b_payload) # assert we throw an error self.v.decrypt(b_invalid_ciphertext) def test_encrypt_encrypted(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = u'AES' b_vaulttext = b"$ANSIBLE_VAULT;9.9;TEST\n%s" % hexlify(b"ansible") vaulttext = to_text(b_vaulttext, errors='strict') self.assertRaises(errors.AnsibleError, self.v.encrypt, b_vaulttext) self.assertRaises(errors.AnsibleError, self.v.encrypt, vaulttext) def test_decrypt_decrypted(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest plaintext = u"ansible" self.assertRaises(errors.AnsibleError, self.v.decrypt, plaintext) b_plaintext = b"ansible" self.assertRaises(errors.AnsibleError, self.v.decrypt, b_plaintext) 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 plaintext = u"ansible" self.v.encrypt(plaintext) self.assertEquals(self.v.cipher_name, "AES256")
def encrypt_file(self, password): # TODO change to use vault property vault = VaultLib([(DEFAULT_VAULT_ID_MATCH, VaultSecret(to_bytes(password)))]) buffer = self.vim.current.buffer buffer[:] = vault.encrypt("\n".join(buffer[:])).split()
class Vault(): """Read and write data using the Ansible vault.""" def __init__(self, password): """Create a vault.""" self.password = password try: from ansible.parsing.vault import VaultSecret from ansible.module_utils._text import to_bytes pass_bytes = to_bytes(password, encoding='utf-8', errors='strict') secrets = [('password', VaultSecret(_bytes=pass_bytes))] # pylint: disable=unexpected-keyword-arg, no-value-for-parameter self.vault = VaultLib(secrets=secrets) except ImportError: self.vault = VaultLib(password) # pylint: disable=inconsistent-return-statements def dump(self, data, stream=None): """Encrypt data and print stdout or write to stream. :param data: The information to be encrypted :param stream: If not None the location to write the encrypted data to. :returns: If stream is None then the encrypted bytes otherwise None. """ encrypted = self.vault.encrypt(data) if stream: stream.write(encrypted) else: return encrypted def load(self, stream): """Read vault steam and return python object. :param stream: The stream to read data from :returns: The decrypted data """ return self.vault.decrypt(stream) def dump_as_yaml(self, obj, stream=None): """Convert object to yaml and encrypt the data. :param obj: Python object to convert to yaml :param stream: If not None the location to write the encrypted data to. :returns: If stream is None then the encrypted bytes otherwise None. """ data = yaml.dump(obj, allow_unicode=True, default_flow_style=False, Dumper=AnsibleDumper) return self.dump(data, stream) def dump_as_yaml_to_tempfile(self, obj): """Convert object to yaml and encrypt the data. :param obj: Python object to convert to yaml :returns: The filepath to write data """ with tempfile.NamedTemporaryFile(delete=False, suffix='.yaml') as data_temp: self.dump_as_yaml(obj, data_temp) data_temp.close() return data_temp.name
vault_pw = VaultSecret(pw) vault_pw.load() except FileNotFoundError: print("Password file not found") sys.exit(1) else: vault_pw = PromptVaultSecret(prompt_formats=["password: "]) vault_pw.load() vl = VaultLib(secrets=[ (None, vault_pw) ]) def to_yaml(representer, node): return representer.represent_scalar('!vault', node, style='|') yaml = YAML() yaml.indent(mapping=2, sequence=4, offset=2) yaml.representer.add_representer(LiteralScalarString, to_yaml) with open(sys.argv[1], 'r') as orig: y = yaml.load(orig) for value in y: y[value] = vl.encrypt(y[value], vault_pw).decode('utf-8') scalarstring.walk_tree(y) with open(sys.argv[2], 'w') as dest: yaml.dump(y, dest)
def ansible_vault_encrypt(key, text): vault = VaultLib([(DEFAULT_VAULT_ID_MATCH, VaultSecret(key.rstrip('\n').encode('utf-8')))]) return vault.encrypt(text.rstrip('\n').encode('utf-8')).decode('utf-8')
def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp is deprecated # If user supplies vault-id and vault-pass, use them. Otherwise use those that are automatically loaded with the playbook if 'vaultpass' in self._task.args: oVaultSecret = VaultSecret( self._task.args["vaultpass"].encode('utf-8')) if 'vaultid' in self._task.args: oVaultLib = VaultLib([(self._task.args["vaultid"], oVaultSecret)]) else: display.v(u'No vault-id supplied, using default identity.') oVaultLib = VaultLib([(C.DEFAULT_VAULT_IDENTITY, oVaultSecret) ]) else: display.v( u'No vault-id or vault-pass supplied, using playbook-sourced variables.' ) oVaultLib = self._loader._vault if len(self._loader._vault.secrets) == 0: display.warning( "No Vault secrets loaded by config and none supplied to plugin. Vault operations are not possible." ) if self._task.args["action"] == "encrypt": if "plaintext" not in self._task.args: return { "failed": True, "msg": "'plaintext' is required for encrypt." } b_vaulttext = oVaultLib.encrypt(self._task.args["plaintext"]) b_ciphertext, b_version, cipher_name, vault_id = parse_vaulttext_envelope( b_vaulttext) vaulttext_header = b_vaulttext.decode('utf-8').split('\n', 1)[0] ciphertext = b_ciphertext.decode('utf-8') if 'multiline_out' in self._task.args and self._task.args[ "multiline_out"] == True: multiline_length = 80 ciphertext = '\n'.join([ ciphertext[i:i + multiline_length] for i in range(0, len(ciphertext), multiline_length) ]) result['vaulttext'] = vaulttext_header + "\n" + ciphertext result['plaintext'] = self._task.args["plaintext"] else: if "vaulttext" not in self._task.args: return { "failed": True, "msg": "'vaulttext' is required for decrypt." } plaintext = oVaultLib.decrypt(self._task.args["vaulttext"]) result['vaulttext'] = self._task.args["vaulttext"] result['plaintext'] = plaintext result['failed'] = False return result
class TestVaultLib(unittest.TestCase): def setUp(self): self.v = VaultLib('test-vault-password') def test_encrypt(self): plaintext = u'Some text to encrypt in a café' b_vaulttext = self.v.encrypt(plaintext) self.assertIsInstance(b_vaulttext, six.binary_type) b_header = b'$ANSIBLE_VAULT;1.1;AES256\n' self.assertEqual(b_vaulttext[:len(b_header)], b_header) def test_encrypt_bytes(self): plaintext = to_bytes(u'Some text to encrypt in a café') b_vaulttext = self.v.encrypt(plaintext) self.assertIsInstance(b_vaulttext, six.binary_type) b_header = b'$ANSIBLE_VAULT;1.1;AES256\n' self.assertEqual(b_vaulttext[:len(b_header)], b_header) def test_is_encrypted(self): self.assertFalse(self.v.is_encrypted(b"foobar"), msg="encryption check on plaintext yielded false positive") b_data = b"$ANSIBLE_VAULT;9.9;TEST\n%s" % hexlify(b"ansible") self.assertTrue(self.v.is_encrypted(b_data), msg="encryption check on headered text failed") def test_format_output(self): self.v.cipher_name = "TEST" b_ciphertext = b"ansible" b_vaulttext = self.v._format_output(b_ciphertext) b_lines = b_vaulttext.split(b'\n') self.assertGreater(len(b_lines), 1, msg="failed to properly add header") b_header = b_lines[0] self.assertTrue(b_header.endswith(b';TEST'), msg="header does not end with cipher name") b_header_parts = b_header.split(b';') self.assertEqual(len(b_header_parts), 3, msg="header has the wrong number of parts") self.assertEqual(b_header_parts[0], b'$ANSIBLE_VAULT', msg="header does not start with $ANSIBLE_VAULT") self.assertEqual(b_header_parts[1], self.v.b_version, msg="header version is incorrect") self.assertEqual(b_header_parts[2], b'TEST', msg="header does not end with cipher name") def test_split_header(self): b_vaulttext = b"$ANSIBLE_VAULT;9.9;TEST\nansible" b_ciphertext = self.v._split_header(b_vaulttext) b_lines = b_ciphertext.split(b'\n') self.assertEqual(b_lines[0], b"ansible", msg="Payload was not properly split from the header") self.assertEqual(self.v.cipher_name, u'TEST', msg="cipher name was not properly set") self.assertEqual(self.v.b_version, b"9.9", msg="version was not properly set") def test_encrypt_decrypt_aes(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = u'AES' self.v.b_password = b'ansible' # AES encryption code has been removed, so this is old output for # AES-encrypted 'foobar' with password 'ansible'. b_vaulttext = b'''$ANSIBLE_VAULT;1.1;AES 53616c7465645f5fc107ce1ef4d7b455e038a13b053225776458052f8f8f332d554809d3f150bfa3 fe3db930508b65e0ff5947e4386b79af8ab094017629590ef6ba486814cf70f8e4ab0ed0c7d2587e 786a5a15efeb787e1958cbdd480d076c ''' b_plaintext = self.v.decrypt(b_vaulttext) self.assertEqual(b_plaintext, b"foobar", msg="decryption failed") def test_encrypt_decrypt_aes256(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = u'AES256' plaintext = u"foobar" b_vaulttext = self.v.encrypt(plaintext) b_plaintext = self.v.decrypt(b_vaulttext) self.assertNotEqual(b_vaulttext, b"foobar", msg="encryption failed") self.assertEqual(b_plaintext, b"foobar", msg="decryption failed") def test_encrypt_decrypt_aes256_existing_vault(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = u'AES256' b_orig_plaintext = b"Setec Astronomy" vaulttext = u'''$ANSIBLE_VAULT;1.1;AES256 33363965326261303234626463623963633531343539616138316433353830356566396130353436 3562643163366231316662386565383735653432386435610a306664636137376132643732393835 63383038383730306639353234326630666539346233376330303938323639306661313032396437 6233623062366136310a633866373936313238333730653739323461656662303864663666653563 3138''' b_plaintext = self.v.decrypt(vaulttext) self.assertEqual(b_plaintext, b_plaintext, msg="decryption failed") b_vaulttext = to_bytes(vaulttext, encoding='ascii', errors='strict') b_plaintext = self.v.decrypt(b_vaulttext) self.assertEqual(b_plaintext, b_orig_plaintext, msg="decryption failed") def test_encrypt_decrypt_aes256_bad_hmac(self): # FIXME This test isn't working quite yet. raise SkipTest if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = 'AES256' # plaintext = "Setec Astronomy" enc_data = '''$ANSIBLE_VAULT;1.1;AES256 33363965326261303234626463623963633531343539616138316433353830356566396130353436 3562643163366231316662386565383735653432386435610a306664636137376132643732393835 63383038383730306639353234326630666539346233376330303938323639306661313032396437 6233623062366136310a633866373936313238333730653739323461656662303864663666653563 3138''' b_data = to_bytes(enc_data, errors='strict', encoding='utf-8') b_data = self.v._split_header(b_data) foo = binascii.unhexlify(b_data) lines = foo.splitlines() # line 0 is salt, line 1 is hmac, line 2+ is ciphertext b_salt = lines[0] b_hmac = lines[1] b_ciphertext_data = b'\n'.join(lines[2:]) b_ciphertext = binascii.unhexlify(b_ciphertext_data) # b_orig_ciphertext = b_ciphertext[:] # now muck with the text # b_munged_ciphertext = b_ciphertext[:10] + b'\x00' + b_ciphertext[11:] # b_munged_ciphertext = b_ciphertext # assert b_orig_ciphertext != b_munged_ciphertext b_ciphertext_data = binascii.hexlify(b_ciphertext) b_payload = b'\n'.join([b_salt, b_hmac, b_ciphertext_data]) # reformat b_invalid_ciphertext = self.v._format_output(b_payload) # assert we throw an error self.v.decrypt(b_invalid_ciphertext) def test_encrypt_encrypted(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest self.v.cipher_name = u'AES' b_vaulttext = b"$ANSIBLE_VAULT;9.9;TEST\n%s" % hexlify(b"ansible") vaulttext = to_text(b_vaulttext, errors='strict') self.assertRaises(errors.AnsibleError, self.v.encrypt, b_vaulttext) self.assertRaises(errors.AnsibleError, self.v.encrypt, vaulttext) def test_decrypt_decrypted(self): if not HAS_AES or not HAS_COUNTER or not HAS_PBKDF2: raise SkipTest plaintext = u"ansible" self.assertRaises(errors.AnsibleError, self.v.decrypt, plaintext) b_plaintext = b"ansible" self.assertRaises(errors.AnsibleError, self.v.decrypt, b_plaintext) 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 plaintext = u"ansible" self.v.encrypt(plaintext) self.assertEquals(self.v.cipher_name, "AES256")