Vaultfilters (#74998)

add vault and unvault filters

Co-authored-by: Felix Fontein <felix@fontein.de>
Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
pull/75151/head
Brian Coca 3 years ago committed by GitHub
parent 60c9f045cc
commit 363c1a3fba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
add plugin.filter:
- name: vault
description: Create Ansible Vault to encrypt your data
- name: unvault
description: Open an Ansible Vault to retrieve your data

@ -580,7 +580,7 @@ class VaultLib:
def is_encrypted(vaulttext): def is_encrypted(vaulttext):
return is_encrypted(vaulttext) return is_encrypted(vaulttext)
def encrypt(self, plaintext, secret=None, vault_id=None): def encrypt(self, plaintext, secret=None, vault_id=None, salt=None):
"""Vault encrypt a piece of data. """Vault encrypt a piece of data.
:arg plaintext: a text or byte string to encrypt. :arg plaintext: a text or byte string to encrypt.
@ -618,7 +618,7 @@ class VaultLib:
else: else:
display.vvvvv(u'Encrypting without a vault_id using vault secret %s' % to_text(secret)) display.vvvvv(u'Encrypting without a vault_id using vault secret %s' % to_text(secret))
b_ciphertext = this_cipher.encrypt(b_plaintext, secret) b_ciphertext = this_cipher.encrypt(b_plaintext, secret, salt)
# format the data for output to the file # format the data for output to the file
b_vaulttext = format_vaulttext_envelope(b_ciphertext, b_vaulttext = format_vaulttext_envelope(b_ciphertext,
@ -1196,10 +1196,18 @@ class VaultAES256:
return to_bytes(hexlify(b_hmac), errors='surrogate_or_strict'), hexlify(b_ciphertext) return to_bytes(hexlify(b_hmac), errors='surrogate_or_strict'), hexlify(b_ciphertext)
@classmethod @classmethod
def encrypt(cls, b_plaintext, secret): def encrypt(cls, b_plaintext, secret, salt=None):
if secret is None: if secret is None:
raise AnsibleVaultError('The secret passed to encrypt() was None') raise AnsibleVaultError('The secret passed to encrypt() was None')
b_salt = os.urandom(32)
if salt is None:
b_salt = os.urandom(32)
elif not salt:
raise AnsibleVaultError('Empty or invalid salt passed to encrypt()')
else:
b_salt = to_bytes(salt)
b_password = secret.bytes b_password = secret.bytes
b_key1, b_key2, b_iv = cls._gen_key_initctr(b_password, b_salt) b_key1, b_key2, b_iv = cls._gen_key_initctr(b_password, b_salt)

@ -0,0 +1,82 @@
# Copyright: (c) 2021, Ansible Project
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from jinja2.runtime import Undefined
from jinja2.exceptions import UndefinedError
from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError
from ansible.module_utils._text import to_native, to_bytes
from ansible.module_utils.six import string_types, binary_type
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
from ansible.parsing.vault import is_encrypted, VaultSecret, VaultLib
from ansible.utils.display import Display
display = Display()
def do_vault(data, secret, salt=None, vaultid='filter_default', wrap_object=False):
if not isinstance(secret, (string_types, binary_type, Undefined)):
raise AnsibleFilterTypeError("Secret passed is required to be a string, instead we got: %s" % type(secret))
if not isinstance(data, (string_types, binary_type, Undefined)):
raise AnsibleFilterTypeError("Can only vault strings, instead we got: %s" % type(data))
vault = ''
vs = VaultSecret(to_bytes(secret))
vl = VaultLib()
try:
vault = vl.encrypt(to_bytes(data), vs, vaultid, salt)
except UndefinedError:
raise
except Exception as e:
raise AnsibleFilterError("Unable to encrypt: %s" % to_native(e), orig_exc=e)
if wrap_object:
vault = AnsibleVaultEncryptedUnicode(vault)
else:
vault = to_native(vault)
return vault
def do_unvault(vault, secret, vaultid='filter_default'):
if not isinstance(secret, (string_types, binary_type, Undefined)):
raise AnsibleFilterTypeError("Secret passed is required to be as string, instead we got: %s" % type(secret))
if not isinstance(vault, (string_types, binary_type, AnsibleVaultEncryptedUnicode, Undefined)):
raise AnsibleFilterTypeError("Vault should be in the form of a string, instead we got: %s" % type(vault))
data = ''
vs = VaultSecret(to_bytes(secret))
vl = VaultLib([(vaultid, vs)])
if isinstance(vault, AnsibleVaultEncryptedUnicode):
vault.vault = vl
data = vault.data
elif is_encrypted(vault):
try:
data = vl.decrypt(vault)
except UndefinedError:
raise
except Exception as e:
raise AnsibleFilterError("Unable to decrypt: %s" % to_native(e), orig_exc=e)
else:
data = vault
return to_native(data)
class FilterModule(object):
''' Ansible vault jinja2 filters '''
def filters(self):
filters = {
'vault': do_vault,
'unvault': do_unvault,
}
return filters

@ -0,0 +1,4 @@
shippable/posix/group2
skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller
skip/python2.7 # filters are controller only, and we no longer support Python 2.7 on the controller
skip/aix

@ -0,0 +1,37 @@
- hosts: localhost
gather_facts: true
vars:
data: secret
dvault: '{{ "secret"|vault("test")}}'
password: test
s_32: '{{(2**31-1)}}'
s_64: '{{(2**63-1)}}'
vaultedstring_32: "$ANSIBLE_VAULT;1.2;AES256;filter_default\n33360a30386436633031333665316161303732656333373131373935623033393964633637346464\n6234613765313539306138373564366363306533356464613334320a666339363037303764636538\n3131633564326637303237313463613864626231\n"
vaultedstring_64: "$ANSIBLE_VAULT;1.2;AES256;filter_default\n33370a34333734353636633035656232613935353432656132646533346233326431346232616261\n6133383034376566366261316365633931356133633337396363370a376664386236313834326561\n6338373864623763613165366636633031303739\n"
vault: !vault |
$ANSIBLE_VAULT;1.1;AES256
33323332333033383335333533383338333333303339333733323339333833303334333133313339
33373339333133323331333833373335333933323338333633343338333133343334333733383334
33333335333433383337333133303339333433353332333333363339333733363335333233303330
3337333733353331333633313335333733373334333733320a373938666533366165653830313163
62386564343438653437333564383664646538653364343138303831613039313232636437336530
3438376662373764650a633366646563386335623161646262366137393635633464333265613938
6661
# allow testing against 32b/64b limited archs, normally you can set higher values for random (2**256)
is_64: '{{ "64" in ansible_facts["architecture"] }}'
salt: '{{ is_64|bool|ternary(s_64, s_32)|random(seed=inventory_hostname)}}'
vaultedstring: '{{ is_64|bool|ternary(vaultedstring_64, vaultedstring_32) }}'
tasks:
- name: check vaulting
assert:
that:
- data|vault(password, salt=salt) == vaultedstring
- "data|vault(password, salt=salt)|type_debug != 'AnsibleVaultEncryptedUnicode'"
- "data|vault(password, salt=salt, wrap_object=True)|type_debug == 'AnsibleVaultEncryptedUnicode'"
- name: check unvaulting
assert:
that:
- vaultedstring|unvault(password) == data
- vault|unvault(password) == data

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -eux
ANSIBLE_GATHER_SUBSET='min' ansible-playbook base.yml "$@"
Loading…
Cancel
Save