#!/usr/bin/python # Copyright (c) 2018, Juan Manuel Parrilla # 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 annotations DOCUMENTATION = r""" module: ipa_vault author: Juan Manuel Parrilla (@jparrill) short_description: Manage FreeIPA vaults description: - Add, modify and delete vaults and secret vaults. - KRA service should be enabled to use this module. attributes: check_mode: support: full diff_mode: support: none options: cn: description: - Vault name. - Can not be changed as it is the unique identifier. required: true aliases: ["name"] type: str description: description: - Description. type: str ipavaulttype: description: - Vault types are based on security level. default: "symmetric" choices: ["asymmetric", "standard", "symmetric"] aliases: ["vault_type"] type: str ipavaultpublickey: description: - Public key. aliases: ["vault_public_key"] type: str ipavaultsalt: description: - Vault Salt. aliases: ["vault_salt"] type: str username: description: - Any user can own one or more user vaults. - Mutually exclusive with O(service). aliases: ["user"] type: list elements: str service: description: - Any service can own one or more service vaults. - Mutually exclusive with O(user). type: str state: description: - State to ensure. default: "present" choices: ["absent", "present"] type: str replace: description: - Force replace the existent vault on IPA server. type: bool default: false choices: ["True", "False"] validate_certs: description: - Validate IPA server certificates. type: bool default: true extends_documentation_fragment: - community.general.ipa.documentation - community.general.ipa.connection_notes - community.general.attributes """ EXAMPLES = r""" - name: Ensure vault is present community.general.ipa_vault: name: vault01 vault_type: standard user: user01 ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Ensure vault is present for Admin user community.general.ipa_vault: name: vault01 vault_type: standard ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Ensure vault is absent community.general.ipa_vault: name: vault01 vault_type: standard user: user01 state: absent ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Modify vault if already exists community.general.ipa_vault: name: vault01 vault_type: standard description: "Vault for test" ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret replace: true - name: Get vault info if already exists community.general.ipa_vault: name: vault01 ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret """ RETURN = r""" vault: description: Vault as returned by IPA API. returned: always type: dict """ import traceback from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec class VaultIPAClient(IPAClient): def __init__(self, module, host, port, protocol): super().__init__(module, host, port, protocol) def vault_find(self, name): return self._post_json(method="vault_find", name=None, item={"all": True, "cn": name}) def vault_add_internal(self, name, item): return self._post_json(method="vault_add_internal", name=name, item=item) def vault_mod_internal(self, name, item): return self._post_json(method="vault_mod_internal", name=name, item=item) def vault_del(self, name): return self._post_json(method="vault_del", name=name) def get_vault_dict(description=None, vault_type=None, vault_salt=None, vault_public_key=None, service=None): vault = {} if description is not None: vault["description"] = description if vault_type is not None: vault["ipavaulttype"] = vault_type if vault_salt is not None: vault["ipavaultsalt"] = vault_salt if vault_public_key is not None: vault["ipavaultpublickey"] = vault_public_key if service is not None: vault["service"] = service return vault def get_vault_diff(client, ipa_vault, module_vault, module): return client.get_diff(ipa_data=ipa_vault, module_data=module_vault) def ensure(module, client): state = module.params["state"] name = module.params["cn"] # user = module.params["username"] TODO is this really not needed? replace = module.params["replace"] module_vault = get_vault_dict( description=module.params["description"], vault_type=module.params["ipavaulttype"], vault_salt=module.params["ipavaultsalt"], vault_public_key=module.params["ipavaultpublickey"], service=module.params["service"], ) ipa_vault = client.vault_find(name=name) changed = False if state == "present": if not ipa_vault: # New vault changed = True if not module.check_mode: ipa_vault = client.vault_add_internal(name, item=module_vault) else: # Already exists if replace: diff = get_vault_diff(client, ipa_vault, module_vault, module) if len(diff) > 0: changed = True if not module.check_mode: data = {} for key in diff: data[key] = module_vault.get(key) client.vault_mod_internal(name=name, item=data) else: if ipa_vault: changed = True if not module.check_mode: client.vault_del(name) return changed, client.vault_find(name=name) def main(): argument_spec = ipa_argument_spec() argument_spec.update( cn=dict(type="str", required=True, aliases=["name"]), description=dict(type="str"), ipavaulttype=dict( type="str", default="symmetric", choices=["standard", "symmetric", "asymmetric"], aliases=["vault_type"] ), ipavaultsalt=dict(type="str", aliases=["vault_salt"]), ipavaultpublickey=dict(type="str", aliases=["vault_public_key"]), service=dict(type="str"), replace=dict(type="bool", default=False, choices=[True, False]), state=dict(type="str", default="present", choices=["present", "absent"]), username=dict(type="list", elements="str", aliases=["user"]), ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, mutually_exclusive=[["username", "service"]] ) client = VaultIPAClient( module=module, host=module.params["ipa_host"], port=module.params["ipa_port"], protocol=module.params["ipa_prot"], ) try: client.login(username=module.params["ipa_user"], password=module.params["ipa_pass"]) changed, vault = ensure(module, client) module.exit_json(changed=changed, vault=vault) except Exception as e: module.fail_json(msg=f"{e}", exception=traceback.format_exc()) if __name__ == "__main__": main()