diff --git a/doc/examples/example-playbook.yml b/doc/examples/example-playbook.yml index 1af582f..9bbe776 100644 --- a/doc/examples/example-playbook.yml +++ b/doc/examples/example-playbook.yml @@ -10,7 +10,8 @@ slash_login: "{{ lookup('viczem.keepass.keepass', 'slash\\/group/slash\\/title', 'username') }}" slash_url: "{{ lookup('viczem.keepass.keepass', 'slash\\/group/slash\\/title', 'url') }}" pork_custom_property: "{{ lookup('viczem.keepass.keepass', 'example/pork', 'custom_properties', 'pork_custom_property')}}" - + keepass_attachment_1_name: "attachment_1.txt" + keepass_attachment_2_name: "attachment_2.zip" tasks: - debug: @@ -30,3 +31,20 @@ msg: "fetch entry: '/slash\\/group/slash\\/title'; username: '{{ slash_login }}'; url: '{{ slash_url }}'" - debug: "{{ lookup('viczem.keepass.keepass', 'close') }}" + + - name: "Export file: {{ keepass_attachment_1_name }}" + viczem.keepass.attachment: + database: "{{ keepass_dbx }}" + password: "{{ keepass_psw }}" + entrypath: example/attachments + attachment: "{{ keepass_attachment_1_name }}" + dest: "{{ keepass_attachment_1_name }}" + + - name: "Export file: {{ keepass_attachment_2_name }}" + viczem.keepass.attachment: + database: "{{ keepass_dbx }}" + password: "{{ keepass_psw }}" + entrypath: example/attachments + attachment: "{{ keepass_attachment_2_name }}" + dest: "{{ keepass_attachment_2_name }}" + mode: 0600 \ No newline at end of file diff --git a/doc/examples/example.kdbx b/doc/examples/example.kdbx index 2d7001f..127e279 100644 Binary files a/doc/examples/example.kdbx and b/doc/examples/example.kdbx differ diff --git a/plugins/modules/attachment.py b/plugins/modules/attachment.py new file mode 100644 index 0000000..e409145 --- /dev/null +++ b/plugins/modules/attachment.py @@ -0,0 +1,188 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Jimisola Laursen +# Copyright: (c) 2022, LFV + +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: attachment +author: + - Jimisola Laursen (@lfvjimisola) + - Jimisola Laursen (@jimisola) + +short_description: Exports KeePass attachments +description: + - This module will export an attachment in a KeePass entry to a file. + +version_added: "0.1.0" + +extends_documentation_fragment: + - files + - action_common_attributes + +requirements: + - pykeepass + +options: + database: + description: Path to KeePass database file. + required: true + type: str + password: + description: Password for KeePass database file. + required: true + type: str + entrypath: + description: Path to KeePass entry containing the attachment that should be exported. + required: true + type: str + attachment: + description: Name of attachment that should be exported. + required: true + type: str + dest: + description: Absolute path where the file should be exported to. + required: true + type: str + +attributes: + check_mode: + support: none + diff_mode: + support: none + platform: + platforms: posix +''' + +EXAMPLES = r''' +# Export a file +- name: Export a file from KeePass + keepass: + database: database.kdbx + password: somepassword + path: "group/subgroup/entry" + attachment: somefile.txt + dest: somefile_exported.txt +''' + +RETURN = r''' # ''' + +from importlib.metadata import EntryPoint +import traceback +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils._text import to_bytes, to_native + +import os +import tempfile + +LIB_IMP_ERR = None +try: + from pykeepass import PyKeePass + HAS_LIB = True +except Exception as e: + HAS_LIB = False + LIB_IMP_ERR = traceback.format_exc() + + +def check_file_attrs(module, result, diff): + + changed, msg = result['changed'], result['msg'] + + file_args = module.load_file_common_arguments(module.params) + if module.set_fs_attributes_if_different(file_args, False, diff=diff): + + if changed: + msg += " and " + changed = True + msg += "ownership, perms or SE linux context changed" + + result['changed'] = changed + result['msg'] = msg + + return result + + +def export_attachment(module, result): + try: + # load database + kp = PyKeePass(module.params['database'], password=module.params['password']) + + entrypath = module.params['entrypath'] + dest = module.params['dest'] + attachment = module.params['attachment'] + + # find entry + kp_entry = kp.find_entries(path=entrypath.split('/'), first=True) + + if (kp_entry is None): + module.fail_json(msg="Entry '{0}' not found".format(entrypath)) + + kp_attachment = None + for item in kp_entry.attachments: + if item.filename == attachment: + kp_attachment = item + + if (kp_attachment is None): + module.fail_json(msg="Entry '{0}' does not contain attachment '{1}'".format(entrypath, attachment)) + + b_data = kp_attachment.binary + + tmpfd, tmpfile = tempfile.mkstemp() + f = os.fdopen(tmpfd, 'wb') + f.write(b_data) + f.close() + + module.atomic_move(tmpfile, + to_native(os.path.realpath(to_bytes(dest, errors='surrogate_or_strict')), errors='surrogate_or_strict'), + unsafe_writes=module.params['unsafe_writes']) + + result['changed'] = True + result['msg'] = "attachment '{0}' exported to file '{1}'".format(module.params['attachment'], dest) + + except Exception as e: + result['msg'] = "Module viczem.keepass.attachment failed: {0}".format(e) + module.fail_json(**result) + + attr_diff = None + + result = check_file_attrs(module, result, attr_diff) + + module.exit_json(**result, diff=attr_diff) + + +def main(): + module_args = dict( + database=dict(type='str', required=True), + password=dict(type='str', no_log=True, required=True), + entrypath=dict(type='str', required=True), + attachment=dict(type='str', required=True), + dest=dict(type='path', required=True), + ) + + module = AnsibleModule( + argument_spec=module_args, + add_file_common_args=True, + ) + + if not HAS_LIB: + module.fail_json(msg=missing_required_lib("pykeepass"), exception=LIB_IMP_ERR) + + result = dict( + changed=False, + ) + + dest = module.params['dest'] + b_dest = to_bytes(dest, errors='surrogate_or_strict') + + if os.path.isdir(b_dest): + module.fail_json(rc=256, msg='Destination {0} is a directory!'.format(dest)) + + export_attachment(module, result) + + +if __name__ == '__main__': + main()