diff --git a/README-vault.md b/README-vault.md index 61f684147030b6918bc48505a65823c37b4862c3..c7ae6916cc56926d3ca600592d57cab7f67a1009 100644 --- a/README-vault.md +++ b/README-vault.md @@ -144,8 +144,7 @@ Example playbook to retrieve vault data from a symmetric vault: name: symvault username: admin password: SomeVAULTpassword - retrieve: true - action: member + state: retrieved ``` Example playbook to make sure vault data is absent in a symmetric vault: @@ -180,6 +179,9 @@ Example playbook to make sure vault is absent: name: symvault username: admin state: absent + register: result + - debug: + msg: "{{ result.data }}" ``` Variables @@ -199,7 +201,7 @@ Variable | Description | Required `public_key ` \| `vault_public_key` \| `ipavaultpublickey` | Base64 encoded vault public key. | no `public_key_file` \| `vault_public_key_file` | Path to file with public key. | no `private_key `\| `vault_private_key` | Base64 encoded vault private key. Used only to retrieve data. | no -`private_key_file` \| `vault_private_key_file` | Path to file with private key. | no +`private_key_file` \| `vault_private_key_file` | Path to file with private key. Used only to retrieve data. | no `salt` \| `vault_salt` \| `ipavaultsalt` | Vault salt. | no `vault_type` \| `ipavaulttype` | Vault types are based on security level. It can be one of `standard`, `symmetric` or `asymmetric`, default: `symmetric` | no `user` \| `username` | Any user can own one or more user vaults. | no @@ -211,9 +213,21 @@ Variable | Description | Required `data` \|`vault_data` \| `ipavaultdata` | Data to be stored in the vault. | no `in` \| `datafile_in` | Path to file with data to be stored in the vault. | no `out` \| `datafile_out` | Path to file to store data retrieved from the vault. | no -`retrieve` | If set to True, retrieve data stored in the vault. (bool) | no `action` | Work on vault or member level. It can be on of `member` or `vault` and defaults to `vault`. | no -`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | no +`state` | The state to ensure. It can be one of `present`, `absent` or `retrieved`, default: `present`. | no + + +Return Values +============= + +ipavault +-------- + +There is only a return value if `state` is `retrieved`. + +Variable | Description | Returned When +-------- | ----------- | ------------- +`data` | The data stored in the vault. | If `state` is `retrieved`. Notes diff --git a/playbooks/vault/retrive-data-asymmetric-vault.yml b/playbooks/vault/retrive-data-asymmetric-vault.yml index 420acc78f206d444e3b0d07d438608f8befe6c87..5f67c593b1aedad864752b50cbc30dde4b95ed98 100644 --- a/playbooks/vault/retrive-data-asymmetric-vault.yml +++ b/playbooks/vault/retrive-data-asymmetric-vault.yml @@ -1,19 +1,17 @@ --- - name: Tests hosts: ipaserver - become: true - gather_facts: True + become: no + gather_facts: no tasks: - name: Retrieve data from assymetric vault with a private key file. ipavault: ipaadmin_password: SomeADMINpassword - name: symvault - username: admin + name: asymvault + username: user01 private_key_file: private.pem - retrieve: True + state: retrieved register: result - debug: msg: "Data: {{ result.data }}" - - debug: - msg: "Decoded Data: {{ result.data | b64decode }}" diff --git a/playbooks/vault/retrive-data-symmetric-vault.yml b/playbooks/vault/retrive-data-symmetric-vault.yml index 0ac1337291e91f9661b17f2e9c05a8d5a3cc3898..163f8b9544b3c0ec85575513309dffa70e991fbb 100644 --- a/playbooks/vault/retrive-data-symmetric-vault.yml +++ b/playbooks/vault/retrive-data-symmetric-vault.yml @@ -1,8 +1,8 @@ --- - name: Tests hosts: ipaserver - become: true - gather_facts: True + become: no + gather_facts: no tasks: - name: Retrieve data from symmetric vault. @@ -11,8 +11,7 @@ name: symvault username: admin password: SomeVAULTpassword - retrieve: yes - action: member + state: retrieved register: result - debug: msg: "{{ result.data | b64decode }}" diff --git a/plugins/modules/ipavault.py b/plugins/modules/ipavault.py index d97c022ced9a6ae7a10ecd5f55042ce29d133c5c..ad5dd413285188a0ae4d298c0be246eb8f00dbe0 100644 --- a/plugins/modules/ipavault.py +++ b/plugins/modules/ipavault.py @@ -74,7 +74,7 @@ options: description: file with password to be used on symmetric vault. required: false type: string - aliases: ["ipavaultpassword", "vault_password"] + aliases: ["vault_password_file"] salt: description: Vault salt. required: false @@ -99,16 +99,24 @@ options: description: Vault is shared. required: false type: boolean + users: + description: Users that are member of the vault. + required: false + type: list + groups: + description: Groups that are member of the vault. + required: false + type: list owners: - description: Users that are owners of the container. + description: Users that are owners of the vault. required: false type: list - users: - description: Users that are member of the container. + ownergroups: + description: Groups that are owners of the vault. required: false type: list - groups: - description: Groups that are member of the container. + ownerservices: + description: Services that are owners of the vault. required: false type: list services: @@ -130,10 +138,6 @@ options: required: false type: string aliases: ["datafile_out"] - retrieve: - description: If set to True, retrieve data stored in the vault. - required: false - type: bool action: description: Work on vault or member level. default: vault @@ -141,7 +145,7 @@ options: state: description: State to ensure default: present - choices: ["present", "absent"] + choices: ["present", "absent", "retrieved"] author: - Rafael Jeffman """ @@ -228,8 +232,7 @@ EXAMPLES = """ name: symvault username: admin password: SomeVAULTpassword - retrieve: yes - action: member + state: retrieved register: result - debug: msg: "{{ result.data | b64decode }}" @@ -266,14 +269,13 @@ EXAMPLES = """ More data archived. action: member -# Retrive data archived in an asymmetric vault +# Retrive data archived in an asymmetric vault, using a private key file. - ipavault: ipaadmin_password: SomeADMINpassword name: asymvault username: admin - retrieve: yes - private_key: - + private_key_file: private.pem + state: retrieved # Ensure asymmetric vault is absent. - ipavault: @@ -285,10 +287,14 @@ EXAMPLES = """ """ RETURN = """ +user: + description: The vault data. + returned: If state is retrieved. + type: string """ import os -from base64 import b64encode, b64decode +from base64 import b64decode from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.ansible_freeipa_module import temp_kinit, \ temp_kdestroy, valid_creds, api_connect, api_command, \ @@ -355,14 +361,17 @@ def gen_member_args(args, users, groups, services): if arg in _args: del _args[arg] - if users is not None: - _args['user'] = users - if groups is not None: - _args['group'] = groups - if services is not None: - _args['services'] = services + if any([users, groups, services]): + if users is not None: + _args['user'] = users + if groups is not None: + _args['group'] = groups + if services is not None: + _args['services'] = services - return _args + return _args + + return None def data_storage_args(args, data, password, password_file, private_key, @@ -410,26 +419,28 @@ def check_parameters(module, state, action, description, username, service, private_key_file, vault_data, datafile_in, datafile_out): invalid = [] if state == "present": - if salt is not None: - if vault_type is not None and vault_type != "symmetric": - module.fail_json( - msg="Attribute `salt` can only be used with `symmetric` " - "vaults.") - if not any([password, password_file]): - module.fail_json( - msg="Value of `salt` can only modified by providing " - "vault password.") + invalid = ['private_key', 'private_key_file', 'datafile_out'] + if action == "member": - invalid = ['description'] + invalid.extend(['description']) elif state == "absent": - invalid = ['description', 'salt', 'vault_type', 'datafile_in', + invalid = ['description', 'salt', 'vault_type', 'private_key', + 'private_key_file', 'datafile_in', 'datafile_out', 'vault_data'] if action == "vault": - invalid.extend(['users', 'groups', 'owners', 'ownergroups', - 'password', 'password_file', 'public_key', - 'public_key_file']) + invalid.extend(['users', 'groups', 'services', 'owners', + 'ownergroups', 'ownerservices', 'password', + 'password_file', 'public_key', 'public_key_file']) + + elif state == "retrieved": + invalid = ['description', 'salt', 'datafile_in', 'users', 'groups', + 'owners', 'ownergroups', 'public_key', 'public_key_file', + 'vault_data'] + if action == 'member': + module.fail_json( + msg="State `retrieved` do not support action `member`.") for arg in invalid: if vars()[arg] is not None: @@ -437,16 +448,6 @@ def check_parameters(module, state, action, description, username, service, msg="Argument '%s' can not be used with state '%s', " "action '%s'" % (arg, state, action)) - elif state == "absent": - invalid = ['description', 'salt', 'vault_type', 'private_key', - 'private_key_file', 'retrieve', 'datafile_in', - 'datafile_out', 'vault_data'] - - if action == "vault": - invalid.extend(['users', 'groups', 'services', 'owners', - 'ownergroups', 'ownerservices', 'password', - 'password_file', 'public_key', 'public_key_file']) - for arg in invalid: if vars()[arg] is not None: module.fail_json( @@ -454,30 +455,30 @@ def check_parameters(module, state, action, description, username, service, "action '%s'" % (arg, state, action)) -def check_encryption_params(module, state, vault_type, salt, password, - password_file, public_key, public_key_file, - private_key, private_key_file, vault_data, - datafile_in, datafile_out, res_find): +def check_encryption_params(module, state, action, vault_type, salt, + password, password_file, public_key, + public_key_file, private_key, private_key_file, + vault_data, datafile_in, datafile_out, res_find): vault_type_invalid = [] - if state == "present": - if vault_type == "standard": - vault_type_invalid = ['public_key', 'public_key_file', 'password', - 'password_file', 'salt'] - - if vault_type is None or vault_type == "symmetric": - vault_type_invalid = ['public_key', 'public_key_file', - 'private_key', 'private_key_file'] - if not any([password, password_file]): - module.fail_json( - msg="Symmetric vault requires password or password_file " - "to store data.") - - if vault_type == "asymmetric": - vault_type_invalid = ['password', 'password_file'] - if not any([public_key, public_key_file]) and res_find is None: - module.fail_json( - msg="Assymmetric vault requires public_key " - "or public_key_file to store data.") + if vault_type == "standard": + vault_type_invalid = ['public_key', 'public_key_file', 'password', + 'password_file', 'salt'] + + if vault_type is None or vault_type == "symmetric": + vault_type_invalid = ['public_key', 'public_key_file', + 'private_key', 'private_key_file'] + + if password is None and password_file is None and action != 'member': + module.fail_json( + msg="Symmetric vault requires password or password_file " + "to store data or change `salt`.") + + if vault_type == "asymmetric": + vault_type_invalid = ['password', 'password_file'] + if not any([public_key, public_key_file]) and res_find is None: + module.fail_json( + msg="Assymmetric vault requires public_key " + "or public_key_file to store data.") for param in vault_type_invalid: if vars()[param] is not None: @@ -511,7 +512,6 @@ def main(): vault_private_key_file=dict(type="str", required=False, default=None, aliases=['private_key_file']), - retrieve=dict(type="bool", required=False, default=None), vault_salt=dict(type="str", required=False, default=None, aliases=['ipavaultsalt', 'salt']), username=dict(type="str", required=False, default=None, @@ -541,7 +541,7 @@ def main(): action=dict(type="str", default="vault", choices=["vault", "data", "member"]), state=dict(type="str", default="present", - choices=["present", "absent"]), + choices=["present", "absent", "retrieved"]), ), supports_check_mode=True, mutually_exclusive=[['username', 'service', 'shared'], @@ -602,6 +602,11 @@ def main(): if len(names) < 1: ansible_module.fail_json(msg="No name given.") + elif state == "retrieved": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one vault can be retrieved at a time.") + else: ansible_module.fail_json(msg="Invalid state '%s'" % state) @@ -648,15 +653,14 @@ def main(): else: args['ipavaulttype'] = vault_type = "symmetric" - # verify data encription args - check_encryption_params(ansible_module, state, vault_type, salt, - password, password_file, public_key, - public_key_file, private_key, - private_key_file, vault_data, - datafile_in, datafile_out, res_find) - # Create command if state == "present": + # verify data encription args + check_encryption_params( + ansible_module, state, action, vault_type, salt, password, + password_file, public_key, public_key_file, private_key, + private_key_file, vault_data, datafile_in, datafile_out, + res_find) # Found the vault if action == "vault": @@ -702,25 +706,32 @@ def main(): # Add users and groups user_add_args = gen_member_args(args, user_add, group_add, service_add) - commands.append([name, 'vault_add_member', user_add_args]) + if user_add_args is not None: + commands.append( + [name, 'vault_add_member', user_add_args]) # Remove users and groups user_del_args = gen_member_args(args, user_del, group_del, service_del) - commands.append( - [name, 'vault_remove_member', user_del_args]) + if user_del_args is not None: + commands.append( + [name, 'vault_remove_member', user_del_args]) # Add owner users and groups owner_add_args = gen_member_args( args, owner_add, ownergroups_add, ownerservice_add) - commands.append( - [name, 'vault_add_owner', owner_add_args]) + if owner_add_args is not None: + # ansible_module.warn("OWNER ADD: %s" % owner_add_args) + commands.append( + [name, 'vault_add_owner', owner_add_args]) # Remove owner users and groups owner_del_args = gen_member_args( args, owner_del, ownergroups_del, ownerservice_del) - commands.append( - [name, 'vault_remove_owner', owner_del_args]) + if owner_del_args is not None: + # ansible_module.warn("OWNER DEL: %s" % owner_del_args) + commands.append( + [name, 'vault_remove_owner', owner_del_args]) if vault_type == 'symmetric' \ and 'ipavaultsalt' not in args: @@ -746,10 +757,28 @@ def main(): private_key_file, datafile_in, datafile_out) if any([vault_data, datafile_in]): commands.append([name, "vault_archive", pwdargs]) - if retrieve: - if 'data' in pwdargs: - del pwdargs['data'] - commands.append([name, "vault_retrieve", pwdargs]) + + elif state == "retrieved": + if res_find is None: + ansible_module.fail_json( + msg="Vault `%s` not found to retrieve data." % name) + + vault_type = res_find['cn'] + + # verify data encription args + check_encryption_params( + ansible_module, state, action, vault_type, salt, password, + password_file, public_key, public_key_file, private_key, + private_key_file, vault_data, datafile_in, datafile_out, + res_find) + + pwdargs = data_storage_args( + args, vault_data, password, password_file, private_key, + private_key_file, datafile_in, datafile_out) + if 'data' in pwdargs: + del pwdargs['data'] + + commands.append([name, "vault_retrieve", pwdargs]) elif state == "absent": if 'ipavaulttype' in args: @@ -777,21 +806,30 @@ def main(): msg="Invalid action '%s' for state '%s'" % (action, state)) else: - ansible_module.fail_json(msg="Unkown state '%s'" % state) + ansible_module.fail_json(msg="Unknown state '%s'" % state) # Execute commands errors = [] for name, command, args in commands: try: + # ansible_module.warn("RUN: %s %s %s" % (command, name, args)) result = api_command(ansible_module, command, name, args) if command == 'vault_archive': changed = 'Archived data into' in result['summary'] elif command == 'vault_retrieve': - exit_args['data'] = b64encode(result['result']['data']) + if 'result' not in result: + raise Exception("No result obtained.") + if 'data' in result['result']: + exit_args['data'] = result['result']['data'] + elif 'vault_data' in result['result']: + exit_args['data'] = result['result']['vault_data'] + else: + raise Exception("No data retrieved.") changed = False else: + # ansible_module.warn("RESULT: %s" % (result)) if "completed" in result: if result["completed"] > 0: changed = True diff --git a/tests/vault/test_vault.yml b/tests/vault/test_vault.yml index 6211c8d94be52546ccf19a0d230b02030fdea9b1..2e2c03e3fce0d3503dcccec36a26ac20b3bca232 100644 --- a/tests/vault/test_vault.yml +++ b/tests/vault/test_vault.yml @@ -1,5 +1,4 @@ --- - - name: Test vault hosts: ipaserver become: true @@ -120,7 +119,6 @@ ipavault: ipaadmin_password: SomeADMINpassword name: stdvault - vault_type: standard state: absent register: result failed_when: not result.changed @@ -129,7 +127,6 @@ ipavault: ipaadmin_password: SomeADMINpassword name: stdvault - vault_type: standard state: absent register: result failed_when: result.changed @@ -221,7 +218,6 @@ register: result failed_when: not result.changed - - name: Ensure symmetric vault is present, with password from file. ipavault: ipaadmin_password: SomeADMINpassword @@ -330,11 +326,11 @@ name: asymvault username: user01 vault_type: asymmetric - retrieve: true private_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBck01L2Y2ZGQvWUltL2E5ZW9HVlRXOGpvYkVncmY5UFhSQTNhSHNBN2tKbzZmQjE4CkhENCtSVlV3eC9scWxrUFliVWk5YlhWL3JKQWtVd0FFRE9uSmVxWEVTWitnVkNWbWlnUnptS1dLMmFkOWFnbVkKU2lxeXlOeEZJSnZaQW8wZEc0Q0FXallLMjd0TGc0SWg2b0dzWklERytXVkVTNVc4OUsrTDBid1ZqcTR0c2hoZQpETU81N3Vudm1JS0VtYUJFMGV3UGZ2a2RaaDVrOEd0czlINGZoMGZHazV0YklZYTBiaHdNVXBMK1dIT202bmJkCituN0JiYVZjODIwVGdaRE8vclNZdG51WGFJYzZXeDBVOUxYWmtVbWszYXBNbnprbk5hVHFndUFRZFRuNzlHOFAKcXJHcW15V2QvRTFjSDJiNWp6SXhpR284cHNMNXN4V1ZZN1dKZHdJREFRQUJBb0lCQUE2ZTlpaXQxNFVBZ3g0Sgp2WDdpczlmYk90Y1drQitqbzk0Tk1meFNGWGdacElNbDEzOW9RTXFLOTdLanhzSHFBYURWZTdtTUxINUVQOTZKCjdNM081ZzRyZ2wwY1ZXdHBNckRReVpzTHZxREZ6Qld4dENIcVZQQXJ1dW1VWmhzU0ozbFJPUXJvOGFnL3c1YmYKNXRDNW9nVnE0K3JzQjRoQnBoZ3Axakdyc1VNK0U4TzdEWFhGSDY4RjhXZ0JpNzI1V3Zjam5iSTlpcmtiMEdjcQoxYkNQSndOM2ZBMWkyVldpUndWWVdiTlRXbkRvTk05WmRZWXhLMGt1VWtEK1F0cmV5Y1dQZjlWNDlsdlVpMVZwCkZWTm1CVUR2R0szSzFNd2JnWFJ3T1hoYWNZN1B0amtkdmFlYjJRY3U1UmpUa3J1R2h6VVlzT1AzcC9jdyt3S1YKdnpRcWNlRUNnWUVBNVd6N1YyU2xSYTJyLy96K0VUUWtKZkVOSjBLRG5DYjBwTUNsQ1FoM2pUTlBBNkRiaGlNawpGVGtjb05icWNwVGlWU2x2aGg2VEtzY1NncVlRVWpRL09xeUc3U2tqS1ZqUTcyajViZVFMeGlMVHRVeWoxT21QClhoOWNXSlh4OGlRKzQ1Y1BvbitrTU9BSWlUd2lCM21tRlJmUWpJR3ZlMURQVW85SitOWjRYZEVDZ1lFQXdOS2cKT2RHWXh4S3RDclhWejFtZGc2UERsVjhxaDdueHhaYlBjaCthTUlRbDErb1RDZ1NpdzhvT1lFZDhnMEhPZFY2dAoxRytJV2h2UHhpaVd5My9BRTBRaGdvS2syR1VzU2pXU01MY0piYVV6RG9FSEZqVExqZWNSbHFkem83cXhSWHFCCm1lTjRMNVdKWUtuTEM0ODJLN2h2dWZTK3VvNWZCNXF3UG10MTNNY0NnWUFlNFRWUFJQK3R5anR0WUNyK084dGwKdy9VbVJLQ2NRdTRJd3Rrenh3ejRWMkNhTjJ0MHVZUWd5eWdjU2ZFU2JSR3RycjhSQ1VwN3BvSEtUZm5DWnIvZgo4TnJVVHdZcGlZZk53WTVaQ1NuQWlHMkFhSWxnbmZNckV3T0Y5T0MwMjhZUE1nVHJ0VXh2TzZoS2VHcUlJUXFHCnFrYnFzb1hoRGpacGdWbk9nV2VBRVFLQmdHdWlaMHcvSXFBbFhiQzMxZlViMmlCTWZ2WFhuSjhNL2RmRkdtRmoKSUtmcWJGRjlXVWxqVXhRbHF5YTFZTnpJRkI1U1RvaGlCZVArMkZtTitMYjV4ZGM3VmRWTFpnZGhXbnJHTXFlOAoxS2QrNnVReXhDanlLWm81blFqU3ltdGY0R3FmT3M4VE9kaWVDWVNLNDB1OWtvaVBPTmE5dHVYZWFVK09Xc2xOCkpRcXJBb0dCQUozTUtPdnNuUXp1WlZQMnZ6MFpxTHdJRTNYalJpRkd2ZVZwaXpxNGh3T1ZldU5zVjA4SnZBMHQKcHVlTkl5OWtsUFNjRmM5T1VkaVpXa0VYMDlCd0prVklyT0hvdHVTQjhBU3RPNVVBbnRObnV5V0xKRUZDNFVxNApHcEI4bGJqOWpreFNLYVU3WDNHYWMyM0s5Skw4ZXVMaDdFN3JQdVpSWWE2bVlONG5iS3F1Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== + state: retrieved register: result - failed_when: result.data | b64decode != 'Hello World.' or result.changed + failed_when: result.changed or result.failed or result['data'] != 'Hello World.' - name: Retrieve data from asymmetric vault, with private key file. ipavault: @@ -342,10 +338,10 @@ name: asymvault username: user01 vault_type: asymmetric - retrieve: true private_key_file: "{{ ansible_env.HOME }}/private.pem" + state: retrieved register: result - failed_when: result.data | b64decode != 'Hello World.' or result.changed + failed_when: result.failed or result.changed or result['data'] != 'Hello World.' - name: Ensure asymmetric vault is absent. ipavault: @@ -394,22 +390,13 @@ register: result failed_when: not result.changed - - name: Archive data from a file, in standard vault. - ipavault: - ipaadmin_password: SomeADMINpassword - name: stdvault - username: user01 - in: "{{ ansible_env.HOME }}/in.txt" - register: result - failed_when: not result.changed - - name: Retrieve data from standard vault. ipavault: ipaadmin_password: SomeADMINpassword name: stdvault username: user01 - retrieve: yes out: "{{ ansible_env.HOME }}/data.txt" + state: retrieved register: result failed_when: result.changed @@ -419,7 +406,7 @@ register: slurpfile failed_when: slurpfile['content'] | b64decode != 'Hello World.' - - name: Archive data in standard vault. + - name: Archive data in standard vault, from file. ipavault: ipaadmin_password: SomeADMINpassword name: stdvault @@ -434,9 +421,9 @@ name: stdvault username: user01 vault_type: standard - retrieve: true + state: retrieved register: result - failed_when: result.data | b64decode != 'Another World.' or result.changed + failed_when: result.data != 'Another World.' or result.changed - name: Ensure standard vault member user is present. ipavault: