summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/sops/plugins/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/sops/plugins/modules
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/sops/plugins/modules')
-rw-r--r--ansible_collections/community/sops/plugins/modules/load_vars.py117
-rw-r--r--ansible_collections/community/sops/plugins/modules/sops_encrypt.py237
2 files changed, 354 insertions, 0 deletions
diff --git a/ansible_collections/community/sops/plugins/modules/load_vars.py b/ansible_collections/community/sops/plugins/modules/load_vars.py
new file mode 100644
index 000000000..27e9ae8c2
--- /dev/null
+++ b/ansible_collections/community/sops/plugins/modules/load_vars.py
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+---
+author: Felix Fontein (@felixfontein)
+module: load_vars
+short_description: Load sops-encrypted variables from files, dynamically within a task
+version_added: '0.1.0'
+description:
+ - Loads sops-encrypted YAML/JSON variables dynamically from a file during task runtime.
+ - To assign included variables to a different host than C(inventory_hostname),
+ use C(delegate_to) and set C(delegate_facts=true).
+options:
+ file:
+ description:
+ - The file name from which variables should be loaded.
+ - If the path is relative, it will look for the file in C(vars/) subdirectory of a role or relative to playbook.
+ type: path
+ name:
+ description:
+ - The name of a variable into which assign the included vars.
+ - If omitted (C(null)) they will be made top level vars.
+ type: str
+ expressions:
+ description:
+ - This option controls how Jinja2 expressions in values in the loaded file are handled.
+ - If set to C(ignore), expressions will not be evaluated, but treated as regular strings.
+ - If set to C(evaluate-on-load), expressions will be evaluated on execution of this module,
+ in other words, when the file is loaded.
+ - Unfortunately, there is no way for non-core modules to handle expressions "unsafe",
+ in other words, evaluate them only on use. This can only achieved by M(ansible.builtin.include_vars),
+ which unfortunately cannot handle sops-encrypted files.
+ type: str
+ default: ignore
+ choices:
+ - ignore
+ - evaluate-on-load
+extends_documentation_fragment:
+ - community.sops.sops
+ - community.sops.attributes
+ - community.sops.attributes.facts
+ - community.sops.attributes.flow
+attributes:
+ action:
+ support: full
+ async:
+ support: none
+ details:
+ - This action runs completely on the controller.
+ check_mode:
+ support: full
+ diff_mode:
+ support: N/A
+ details:
+ - This action does not modify state.
+ facts:
+ support: full
+seealso:
+ - module: ansible.builtin.set_fact
+ - module: ansible.builtin.include_vars
+ - ref: playbooks_delegation
+ description: More information related to task delegation.
+ - ref: community.sops.sops lookup <ansible_collections.community.sops.sops_lookup>
+ description: The sops lookup can be used decrypt sops-encrypted files.
+ # - plugin: community.sops.sops
+ # plugin_type: lookup
+ - ref: community.sops.decrypt filter <ansible_collections.community.sops.decrypt_filter>
+ description: The decrypt filter can be used to descrypt sops-encrypted in-memory data.
+ # - plugin: community.sops.decrypt
+ # plugin_type: filter
+ - ref: community.sops.sops vars plugin <ansible_collections.community.sops.sops_vars>
+ description: The sops vars plugin can be used to load sops-encrypted host or group variables.
+ # - plugin: community.sops.sops
+ # plugin_type: vars
+'''
+
+EXAMPLES = r'''
+- name: Include variables of stuff.sops.yaml into the 'stuff' variable
+ community.sops.load_vars:
+ file: stuff.sops.yaml
+ name: stuff
+ expressions: evaluate-on-load # interpret Jinja2 expressions in stuf.sops.yaml on load-time!
+
+- name: Conditionally decide to load in variables into 'plans' when x is 0, otherwise do not
+ community.sops.load_vars:
+ file: contingency_plan.sops.yaml
+ name: plans
+ expressions: ignore # do not interpret possible Jinja2 expressions
+ when: x == 0
+
+- name: Load variables into the global namespace
+ community.sops.load_vars:
+ file: contingency_plan.sops.yaml
+'''
+
+RETURN = r'''
+ansible_facts:
+ description: Variables that were included and their values.
+ returned: success
+ type: dict
+ sample: {'variable': 'value'}
+ansible_included_var_files:
+ description: A list of files that were successfully included
+ returned: success
+ type: list
+ elements: str
+ sample: [ /path/to/file.sops.yaml ]
+'''
diff --git a/ansible_collections/community/sops/plugins/modules/sops_encrypt.py b/ansible_collections/community/sops/plugins/modules/sops_encrypt.py
new file mode 100644
index 000000000..d4ba34353
--- /dev/null
+++ b/ansible_collections/community/sops/plugins/modules/sops_encrypt.py
@@ -0,0 +1,237 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+---
+author: Felix Fontein (@felixfontein)
+module: sops_encrypt
+short_description: Encrypt data with sops
+version_added: '0.1.0'
+description:
+ - Allows to encrypt binary data (Base64 encoded), text data, JSON or YAML data with sops.
+options:
+ path:
+ description:
+ - The sops encrypt file.
+ type: path
+ required: true
+ force:
+ description:
+ - Force rewriting the encrypted file.
+ type: bool
+ default: false
+ content_text:
+ description:
+ - The data to encrypt. Must be a Unicode text.
+ - Please note that the module might not be idempotent if the text can be parsed as JSON or YAML.
+ - Exactly one of I(content_text), I(content_binary), I(content_json) and I(content_yaml) must be specified.
+ type: str
+ content_binary:
+ description:
+ - The data to encrypt. Must be L(Base64 encoded,https://en.wikipedia.org/wiki/Base64) binary data.
+ - Please note that the module might not be idempotent if the data can be parsed as JSON or YAML.
+ - Exactly one of I(content_text), I(content_binary), I(content_json) and I(content_yaml) must be specified.
+ type: str
+ content_json:
+ description:
+ - The data to encrypt. Must be a JSON dictionary.
+ - Exactly one of I(content_text), I(content_binary), I(content_json) and I(content_yaml) must be specified.
+ type: dict
+ content_yaml:
+ description:
+ - The data to encrypt. Must be a YAML dictionary.
+ - Please note that Ansible only allows to pass data that can be represented as a JSON dictionary.
+ - Exactly one of I(content_text), I(content_binary), I(content_json) and I(content_yaml) must be specified.
+ type: dict
+extends_documentation_fragment:
+ - ansible.builtin.files
+ - community.sops.sops
+ - community.sops.sops.encrypt_specific
+ - community.sops.attributes
+ - community.sops.attributes.files
+attributes:
+ check_mode:
+ support: full
+ diff_mode:
+ support: none
+ safe_file_operations:
+ support: full
+seealso:
+ - ref: community.sops.sops lookup <ansible_collections.community.sops.sops_lookup>
+ description: The sops lookup can be used decrypt sops-encrypted files.
+ # - plugin: community.sops.sops
+ # plugin_type: lookup
+'''
+
+EXAMPLES = r'''
+- name: Encrypt a secret text
+ community.sops.sops_encrypt:
+ path: text-data.sops
+ content_text: This is a secret text.
+
+- name: Encrypt the contents of a file
+ community.sops.sops_encrypt:
+ path: binary-data.sops
+ content_binary: "{{ lookup('ansible.builtin.file', '/path/to/file', rstrip=false) | b64encode }}"
+
+- name: Encrypt some datastructure as YAML
+ community.sops.sops_encrypt:
+ path: stuff.sops.yaml
+ content_yaml: "{{ result }}"
+'''
+
+RETURN = r''' # '''
+
+
+import base64
+import json
+import os
+import traceback
+
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+from ansible.module_utils.common.text.converters import to_text
+
+from ansible_collections.community.sops.plugins.module_utils.io import write_file
+from ansible_collections.community.sops.plugins.module_utils.sops import Sops, SopsError, get_sops_argument_spec
+
+try:
+ import yaml
+ YAML_IMP_ERR = None
+ HAS_YAML = True
+except ImportError:
+ YAML_IMP_ERR = traceback.format_exc()
+ HAS_YAML = False
+ yaml = None
+
+
+def get_data_type(module):
+ if module.params['content_text'] is not None:
+ return 'binary'
+ if module.params['content_binary'] is not None:
+ return 'binary'
+ if module.params['content_json'] is not None:
+ return 'json'
+ if module.params['content_yaml'] is not None:
+ return 'yaml'
+ module.fail_json(msg='Internal error: unknown content type')
+
+
+def compare_encoded_content(module, binary_data, content):
+ if module.params['content_text'] is not None:
+ return content == module.params['content_text'].encode('utf-8')
+ if module.params['content_binary'] is not None:
+ return content == binary_data
+ if module.params['content_json'] is not None:
+ # Compare JSON
+ try:
+ return json.loads(content) == module.params['content_json']
+ except Exception:
+ # Treat parsing errors as content not equal
+ return False
+ if module.params['content_yaml'] is not None:
+ # Compare YAML
+ try:
+ return yaml.safe_load(content) == module.params['content_yaml']
+ except Exception:
+ # Treat parsing errors as content not equal
+ return False
+ module.fail_json(msg='Internal error: unknown content type')
+
+
+def get_encoded_type_content(module, binary_data):
+ if module.params['content_text'] is not None:
+ return 'binary', module.params['content_text'].encode('utf-8')
+ if module.params['content_binary'] is not None:
+ return 'binary', binary_data
+ if module.params['content_json'] is not None:
+ return 'json', json.dumps(module.params['content_json']).encode('utf-8')
+ if module.params['content_yaml'] is not None:
+ return 'yaml', yaml.safe_dump(module.params['content_yaml']).encode('utf-8')
+ module.fail_json(msg='Internal error: unknown content type')
+
+
+def main():
+ argument_spec = dict(
+ path=dict(type='path', required=True),
+ force=dict(type='bool', default=False),
+ content_text=dict(type='str', no_log=True),
+ content_binary=dict(type='str', no_log=True),
+ content_json=dict(type='dict', no_log=True),
+ content_yaml=dict(type='dict', no_log=True),
+ )
+ argument_spec.update(get_sops_argument_spec(add_encrypt_specific=True))
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ ('content_text', 'content_binary', 'content_json', 'content_yaml'),
+ ],
+ required_one_of=[
+ ('content_text', 'content_binary', 'content_json', 'content_yaml'),
+ ],
+ supports_check_mode=True,
+ add_file_common_args=True,
+ )
+
+ # Check YAML
+ if module.params['content_yaml'] is not None and not HAS_YAML:
+ module.fail_json(msg=missing_required_lib('pyyaml'), exception=YAML_IMP_ERR)
+
+ # Decode binary data
+ binary_data = None
+ if module.params['content_binary'] is not None:
+ try:
+ binary_data = base64.b64decode(module.params['content_binary'])
+ except Exception as e:
+ module.fail_json(msg='Cannot decode Base64 encoded data: {0}'.format(e))
+
+ path = module.params['path']
+ directory = os.path.dirname(path) or None
+ changed = False
+
+ def get_option_value(argument_name):
+ return module.params.get(argument_name)
+
+ try:
+ if module.params['force'] or not os.path.exists(path):
+ # Simply encrypt
+ changed = True
+ else:
+ # Change detection: check if encrypted data equals new data
+ decrypted_content = Sops.decrypt(
+ path, decode_output=False, output_type=get_data_type(module), rstrip=False,
+ get_option_value=get_option_value, module=module,
+ )
+ if not compare_encoded_content(module, binary_data, decrypted_content):
+ changed = True
+
+ if changed and not module.check_mode:
+ input_type, input_data = get_encoded_type_content(module, binary_data)
+ output_type = None
+ if path.endswith('.json'):
+ output_type = 'json'
+ elif path.endswith('.yaml'):
+ output_type = 'yaml'
+ data = Sops.encrypt(
+ data=input_data, cwd=directory, input_type=input_type, output_type=output_type,
+ get_option_value=get_option_value, module=module,
+ )
+ write_file(module, data)
+ except SopsError as e:
+ module.fail_json(msg=to_text(e))
+
+ file_args = module.load_file_common_arguments(module.params)
+ changed = module.set_fs_attributes_if_different(file_args, changed)
+
+ module.exit_json(changed=changed)
+
+
+if __name__ == '__main__':
+ main()