diff --git a/README-vault.md b/README-vault.md new file mode 100644 index 0000000000000000000000000000000000000000..4be246961832b658c7d81a066fe81669fb5e8b78 --- /dev/null +++ b/README-vault.md @@ -0,0 +1,203 @@ +Vault module +=================== + +Description +----------- + +The vault module allows to ensure presence and absence of vault and members of vaults. + +The vault module is as compatible as possible to the Ansible upstream `ipa_vault` module, and additionally offers to make sure that vault members, groups and owners are present or absent in a vault, and allow the archival of data in vaults. + + +Features +-------- +* Vault management + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipavault module. + + +Requirements +------------ + +**Controller** +* Ansible version: 2.8+ + +**Node** +* Supported FreeIPA version (see above) +* KRA service must be enabled + + +Usage +===== + +Example inventory file + +```ini +[ipaserver] +ipaserver.test.local +``` + +Example playbook to make sure vault is present: + +```yaml +--- +- name: Playbook to handle vaults + hosts: ipaserver + become: true + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + vault_password: MyVaultPassword123 + description: A standard private vault. +``` + +Example playbook to make sure that a vault and its members are present: + +```yaml +--- +- name: Playbook to handle vaults + hosts: ipaserver + become: true + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + users: user01 +``` + +`action` controls if the vault, data, member or owner will be handled. To add or remove members or vault data, set `action` to `member`. + +Example playbook to make sure that a vault member is present in vault: + +```yaml +--- +- name: Playbook to handle vaults + hosts: ipaserver + become: true + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + users: user01 + action: member +``` + +Example playbook to make sure that a vault owner is absent in vault: + +```yaml +--- +- name: Playbook to handle vaults + hosts: ipaserver + become: true + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + owner: user01 + action: member + state: absent +``` + +Example playbook to make sure vault data is present in a symmetric vault: + +```yaml +--- +- name: Playbook to handle vaults + hosts: ipaserver + become: true + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + vault_password: MyVaultPassword123 + vault_data: > + Data archived. + More data archived. + action: member +``` + +Example playbook to make sure vault data is absent in a symmetric vault: + +```yaml +--- +- name: Playbook to handle vaults + hosts: ipaserver + become: true + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + vault_password: MyVaultPassword123 + action: member + state: absent +``` + +Example playbook to make sure vault is absent: + +```yaml +--- +- name: Playbook to handle vaults + hosts: ipaserver + become: true + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + state: absent +``` + +Variables +========= + +ipavault +------- + +Variable | Description | Required +-------- | ----------- | -------- +`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no +`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no +`name` \| `cn` | The list of vault name strings. | yes +`description` | The vault description string. | no +`nomembers` | Suppress processing of membership attributes. (bool) | no +`vault_public_key` \| `ipavaultpublickey` | Vault public key. | no +`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 +`service` | Any service can own one or more service vaults. | no +`user` | Any user can own one or more user vaults. | no +`shared` | Vault is shared. Default to false. (bool) | no +`users` | Users that are members of the vault. | no +`groups` | Groups that are member of the vault. | no +`vault_data` \| `ipavaultdata` | Data to be stored in the vault. | 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 + + +Notes +===== + +ipavault uses a client context to execute, and it might affect execution time. + + +Authors +======= + +Rafael Jeffman diff --git a/README.md b/README.md index 5efbd08884101f434adccc282e9c32226313ff17..9b150a1ab12ad9b798738a5f4f8ce623b9236850 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Features * Modules for sudorule management * Modules for topology management * Modules for user management +* Modules for vault management Supported FreeIPA Versions -------------------------- @@ -416,3 +417,4 @@ Modules in plugin/modules * [ipatopologysegment](README-topology.md) * [ipatopologysuffix](README-topology.md) * [ipauser](README-user.md) +* [ipavault](README-vault.md) diff --git a/playbooks/vault/data-archive-in-asymmetric-vault.yml b/playbooks/vault/data-archive-in-asymmetric-vault.yml new file mode 100644 index 0000000000000000000000000000000000000000..f70d76db5cfb9ae61d23dab6036c344bcb18dffc --- /dev/null +++ b/playbooks/vault/data-archive-in-asymmetric-vault.yml @@ -0,0 +1,13 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: user01 + vault_data: The world of π is half rounded. + action: member diff --git a/playbooks/vault/data-archive-in-symmetric-vault.yml b/playbooks/vault/data-archive-in-symmetric-vault.yml new file mode 100644 index 0000000000000000000000000000000000000000..eb8b0a0fa459437e6cf476ef86a478886f8269af --- /dev/null +++ b/playbooks/vault/data-archive-in-symmetric-vault.yml @@ -0,0 +1,14 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + vault_password: MyVaultPassword123 + vault_data: The world of π is half rounded. + action: member diff --git a/playbooks/vault/ensure-asymetric-vault-is-absent.yml b/playbooks/vault/ensure-asymetric-vault-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..62866c4355366e91aa66dccb8e8f00e224e1ef6b --- /dev/null +++ b/playbooks/vault/ensure-asymetric-vault-is-absent.yml @@ -0,0 +1,12 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: admin + state: absent diff --git a/playbooks/vault/ensure-asymetric-vault-is-present.yml b/playbooks/vault/ensure-asymetric-vault-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..f946779b97b4ffda539741471627b4fa33d13c9a --- /dev/null +++ b/playbooks/vault/ensure-asymetric-vault-is-present.yml @@ -0,0 +1,13 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: admin + vault_public_key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDdGFudjRkK3ptSTZ0T3ova1RXdGowY3AxRAowUENoYy8vR0pJMTUzTi9CN3UrN0h3SXlRVlZoNUlXZG1UcCtkWXYzd09yeVpPbzYvbHN5eFJaZ2pZRDRwQ3VGCjlxM295VTFEMnFOZERYeGtSaFFETXBiUEVSWWlHbE1jbzdhN0hIVDk1bGNQbmhObVFkb3VGdHlVbFBUVS96V1kKZldYWTBOeU1UbUtoeFRseUV3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo= + vault_type: asymmetric diff --git a/playbooks/vault/ensure-service-vault-is-absent.yml b/playbooks/vault/ensure-service-vault-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..1affb4c7e0048fe08ddc34ede936ff2d796fe0b7 --- /dev/null +++ b/playbooks/vault/ensure-service-vault-is-absent.yml @@ -0,0 +1,12 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: svcvault + service: "HTTP/{{ groups.ipaserver[0] }}" + state: absent diff --git a/playbooks/vault/ensure-service-vault-is-present.yml b/playbooks/vault/ensure-service-vault-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..423fef1ff39f3225db728e3cf8f63785b5da6259 --- /dev/null +++ b/playbooks/vault/ensure-service-vault-is-present.yml @@ -0,0 +1,13 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: svcvault + service: "HTTP/{{ groups.ipaserver[0] }}" + ipavaultpassword: MyVaultPassword123 + state: present diff --git a/playbooks/vault/ensure-shared-vault-is-absent.yml b/playbooks/vault/ensure-shared-vault-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..fdf1babd4c5d1f7a5054ef8e3f13d251ddc40dac --- /dev/null +++ b/playbooks/vault/ensure-shared-vault-is-absent.yml @@ -0,0 +1,12 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: sharedvault + shared: True + state: absent diff --git a/playbooks/vault/ensure-shared-vault-is-present.yml b/playbooks/vault/ensure-shared-vault-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..9f25e407fd708e9c6ecbf6ea753cd3485cf3e3df --- /dev/null +++ b/playbooks/vault/ensure-shared-vault-is-present.yml @@ -0,0 +1,13 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: sharedvault + shared: True + ipavaultpassword: MyVaultPassword123 + state: present diff --git a/playbooks/vault/ensure-standard-vault-is-absent.yml b/playbooks/vault/ensure-standard-vault-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..c52806e6958e1948480730d59f1dd872bfccf251 --- /dev/null +++ b/playbooks/vault/ensure-standard-vault-is-absent.yml @@ -0,0 +1,12 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: admin + state: absent diff --git a/playbooks/vault/ensure-standard-vault-is-present.yml b/playbooks/vault/ensure-standard-vault-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..a55f55cc06369c43f625fcfc39477d7ff9c6f7a1 --- /dev/null +++ b/playbooks/vault/ensure-standard-vault-is-present.yml @@ -0,0 +1,13 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + vault_type: standard + username: admin + description: A standard private vault. diff --git a/playbooks/vault/ensure-symetric-vault-is-absent.yml b/playbooks/vault/ensure-symetric-vault-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..87c3bdc257c85be0514294730ff922484fd730ec --- /dev/null +++ b/playbooks/vault/ensure-symetric-vault-is-absent.yml @@ -0,0 +1,12 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + state: absent diff --git a/playbooks/vault/ensure-symetric-vault-is-present.yml b/playbooks/vault/ensure-symetric-vault-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..ea10d5735cd5556e6243f6c0d6307765500d17e1 --- /dev/null +++ b/playbooks/vault/ensure-symetric-vault-is-present.yml @@ -0,0 +1,13 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + vault_password: MyVaultPassword123 + vault_type: symmetric diff --git a/playbooks/vault/ensure-vault-is-present-with-members.yml b/playbooks/vault/ensure-vault-is-present-with-members.yml new file mode 100644 index 0000000000000000000000000000000000000000..65cd72d49a3fd53c92233d5e320f1b5ca94b2571 --- /dev/null +++ b/playbooks/vault/ensure-vault-is-present-with-members.yml @@ -0,0 +1,17 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + vault_type: standard + username: admin + users: + - user01 + - user02 + groups: + - ipausers diff --git a/playbooks/vault/ensure-vault-member-group-is-absent.yml b/playbooks/vault/ensure-vault-member-group-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..f26bc670380578757853a25242ecb3a1d8150967 --- /dev/null +++ b/playbooks/vault/ensure-vault-member-group-is-absent.yml @@ -0,0 +1,14 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: keychain + username: admin + state: absent + action: member + groups: ipausers diff --git a/playbooks/vault/ensure-vault-member-group-is-present.yml b/playbooks/vault/ensure-vault-member-group-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..84dc087ee681748229e63f713dd916ae1591c320 --- /dev/null +++ b/playbooks/vault/ensure-vault-member-group-is-present.yml @@ -0,0 +1,14 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: keychain + username: admin + state: present + action: member + groups: ipausers diff --git a/playbooks/vault/ensure-vault-member-user-is-absent.yml b/playbooks/vault/ensure-vault-member-user-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..99c9d6cc1bfa94fb5be4bbf4a3424a56cd4848c4 --- /dev/null +++ b/playbooks/vault/ensure-vault-member-user-is-absent.yml @@ -0,0 +1,16 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: keychain + username: admin + state: absent + action: member + users: + - user01 + - user02 diff --git a/playbooks/vault/ensure-vault-member-user-is-present.yml b/playbooks/vault/ensure-vault-member-user-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..820a9f7cdedd776a0fba276ad21605b89044ab35 --- /dev/null +++ b/playbooks/vault/ensure-vault-member-user-is-present.yml @@ -0,0 +1,14 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: keychain + username: admin + state: present + action: member + users: user1 diff --git a/playbooks/vault/ensure-vault-owner-is-absent.yml b/playbooks/vault/ensure-vault-owner-is-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..33670fc89ec779a3486723a344c54f48bdecd64f --- /dev/null +++ b/playbooks/vault/ensure-vault-owner-is-absent.yml @@ -0,0 +1,15 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + owners: user01 + ownergroups: ipausers + action: member + state: absent diff --git a/playbooks/vault/ensure-vault-owner-is-present.yml b/playbooks/vault/ensure-vault-owner-is-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..52b1d93c594f7e2cf2659c966031985ae83c51c3 --- /dev/null +++ b/playbooks/vault/ensure-vault-owner-is-present.yml @@ -0,0 +1,15 @@ +--- +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + - ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + owners: user01 + ownergroups: ipausers + action: member + state: present diff --git a/plugins/modules/ipavault.py b/plugins/modules/ipavault.py new file mode 100644 index 0000000000000000000000000000000000000000..32137ee7ca8b55f53fa4bd60408ebe371a21926a --- /dev/null +++ b/plugins/modules/ipavault.py @@ -0,0 +1,646 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Authors: +# Rafael Guterres Jeffman <rjeffman@redhat.com> +# +# Copyright (C) 2019 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "supported_by": "community", + "status": ["preview"], +} + +DOCUMENTATION = """ +--- +module: ipavault +short description: Manage vaults and secret vaults. +description: Manage vaults and secret vaults. KRA service must be enabled. +options: + ipaadmin_principal: + description: The admin principal + default: admin + ipaadmin_password: + description: The admin password + required: false + name: + description: The vault name + required: true + aliases: ["cn"] + description: + description: The vault description + required: false + vault_public_key: + description: Base64 encoded public key. + required: false + type: list + aliases: ["ipavaultpublickey"] + vault_salt: + description: Vault salt. + required: false + type: list + aliases: ["ipavaultsalt"] + vault_password: + description: password to be used on symmetric vault. + required: false + type: string + aliases: ["ipavaultpassword"] + vault_type: + description: Vault types are based on security level. + required: true + default: symmetric + choices: ["standard", "symmetric", "asymmetric"] + aliases: ["ipavaulttype"] + service: + description: Any service can own one or more service vaults. + required: false + type: list + username: + description: Any user can own one or more user vaults. + required: false + type: string + aliases: ["user"] + shared: + description: Vault is shared. + required: false + type: boolean + vault_data: + description: Data to be stored in the vault. + required: false + type: string + aliases: ["ipavaultdata"] + owners: + description: Users that are owners of the container. + required: false + type: list + users: + description: Users that are member of the container. + required: false + type: list + groups: + description: Groups that are member of the container. + required: false + type: list + action: + description: Work on vault or member level. + default: vault + choices: ["vault", "member"] + state: + description: State to ensure + default: present + choices: ["present", "absent"] +author: + - Rafael Jeffman +""" + +EXAMPLES = """ +# Ensure vault symvault is present +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + vault_password: MyVaultPassword123 + vault_salt: MTIzNDU2Nzg5MAo= + vault_type: symmetric + +# Ensure group ipausers is a vault member. +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + groups: ipausers + action: member + +# Ensure group ipausers is not a vault member. +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + groups: ipausers + action: member + state: absent + +# Ensure vault users are present. +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + users: + - user01 + - user02 + action: member + +# Ensure vault users are absent. +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + users: + - user01 + - user02 + action: member + status: absent + +# Ensure user owns vault. +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + action: member + owners: user01 + +# Ensure user does not own vault. +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + owners: user01 + action: member + status: absent + +# Ensure data is archived to a symmetric vault +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: admin + vault_password: MyVaultPassword123 + vault_data: > + Data archived. + More data archived. + action: member + +# Ensure vault symvault is absent +- ipavault: + ipaadmin_password: MyPassword123 + name: symvault + user: admin + state: absent + +# Ensure asymmetric vault is present. +- ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: user01 + description: An asymmetric vault + vault_type: asymmetric + vault_public_key: + LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTR + HTkFEQ0JpUUtCZ1FDdGFudjRkK3ptSTZ0T3ova1RXdGowY3AxRAowUENoYy8vR0pJMTUzTi + 9CN3UrN0h3SXlRVlZoNUlXZG1UcCtkWXYzd09yeVpPbzYvbHN5eFJaZ2pZRDRwQ3VGCjlxM + 295VTFEMnFOZERYeGtSaFFETXBiUEVSWWlHbE1jbzdhN0hIVDk1bGNQbmhObVFkb3VGdHlV + bFBUVS96V1kKZldYWTBOeU1UbUtoeFRseUV3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVk + tLS0tLQo= + +# Ensure data is archived in an asymmetric vault +- ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: admin + vault_data: > + Data archived. + More data archived. + action: member + +# Ensure asymmetric vault is absent. +- ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: user01 + vault_type: asymmetric + state: absent +""" + +RETURN = """ +""" + +import os +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, \ + gen_add_del_lists, compare_args_ipa, module_params_get +from ipalib.errors import EmptyModlist + + +def find_vault(module, name, username, service, shared): + _args = { + "all": True, + "cn": name, + } + + if username is not None: + _args['username'] = username + elif service is not None: + _args['service'] = service + else: + _args['shared'] = shared + + _result = api_command(module, "vault_find", name, _args) + + if len(_result["result"]) > 1: + module.fail_json( + msg="There is more than one vault '%s'" % (name)) + if len(_result["result"]) == 1: + return _result["result"][0] + + return None + + +def gen_args(description, username, service, shared, vault_type, salt, + public_key, vault_data): + _args = {} + + if description is not None: + _args['description'] = description + if username is not None: + _args['username'] = username + if service is not None: + _args['service'] = service + if shared is not None: + _args['shared'] = shared + if vault_type is not None: + _args['ipavaulttype'] = vault_type + if salt is not None: + _args['ipavaultsalt'] = salt + if public_key is not None: + _args['ipavaultpublickey'] = public_key + if vault_data is not None: + _args['data'] = vault_data.encode('utf-8') + + return _args + + +def gen_member_args(args, users, groups): + _args = args.copy() + + for arg in ['ipavaulttype', 'description', 'ipavaultpublickey', + 'ipavaultsalt']: + if arg in _args: + del _args[arg] + + _args['user'] = users + _args['group'] = groups + + return _args + + +def data_storage_args(args, data, password): + _args = {} + + if 'username' in args: + _args['username'] = args['username'] + if 'service' in args: + _args['service'] = args['service'] + if 'shared' in args: + _args['shared'] = args['shared'] + + if password is not None: + _args['password'] = password + + _args['data'] = data + + return _args + + +def check_parameters(module, state, action, description, username, service, + shared, users, groups, owners, ownergroups, vault_type, + salt, password, public_key, vault_data): + invalid = [] + if state == "present": + if action == "member": + invalid = ['description', 'public_key', 'salt'] + + for param in invalid: + if vars()[param] is not None: + module.fail_json( + msg="Argument '%s' can not be used with action '%s'" % + (param, action)) + + elif state == "absent": + invalid = ['description', 'salt'] + + if action == "vault": + invalid.extend(['users', 'groups', 'owners', 'ownergroups', + 'password', 'public_key']) + + for arg in invalid: + if vars()[arg] is not None: + module.fail_json( + msg="Argument '%s' can not be used with action '%s'" % + (arg, state)) + + +def check_encryption_params(module, state, vault_type, password, public_key, + vault_data, res_find): + if state == "present": + if vault_type == "symmetric": + if password is None \ + and (vault_data is not None or res_find is None): + module.fail_json( + msg="Vault password required for symmetric vault.") + + if vault_type == "asymmetric": + if public_key is None and res_find is None: + module.fail_json( + msg="Public Key required for asymmetric vault.") + + +def main(): + ansible_module = AnsibleModule( + argument_spec=dict( + # generalgroups + ipaadmin_principal=dict(type="str", default="admin"), + ipaadmin_password=dict(type="str", required=False, no_log=True), + + name=dict(type="list", aliases=["cn"], default=None, + required=True), + + # present + + description=dict(required=False, type="str", default=None), + vault_type=dict(type="str", aliases=["ipavaulttype"], + default=None, required=False, + choices=["standard", "symmetric", "asymmetric"]), + vault_public_key=dict(type="str", required=False, default=None, + aliases=['ipavaultpublickey']), + vault_salt=dict(type="str", required=False, default=None, + aliases=['ipavaultsalt']), + username=dict(type="str", required=False, default=None, + aliases=['user']), + service=dict(type="str", required=False, default=None), + shared=dict(type="bool", required=False, default=None), + + users=dict(required=False, type='list', default=None), + groups=dict(required=False, type='list', default=None), + owners=dict(required=False, type='list', default=None), + ownergroups=dict(required=False, type='list', default=None), + + vault_data=dict(type="str", required=False, default=None, + aliases=['ipavaultdata']), + vault_password=dict(type="str", required=False, default=None, + no_log=True, aliases=['ipavaultpassword']), + + # state + action=dict(type="str", default="vault", + choices=["vault", "data", "member"]), + state=dict(type="str", default="present", + choices=["present", "absent"]), + ), + supports_check_mode=True, + mutually_exclusive=[['username', 'service', 'shared']], + required_one_of=[['username', 'service', 'shared']] + ) + + ansible_module._ansible_debug = True + + # general + ipaadmin_principal = module_params_get(ansible_module, + "ipaadmin_principal") + ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password") + names = module_params_get(ansible_module, "name") + + # present + description = module_params_get(ansible_module, "description") + + username = module_params_get(ansible_module, "username") + service = module_params_get(ansible_module, "service") + shared = module_params_get(ansible_module, "shared") + + users = module_params_get(ansible_module, "users") + groups = module_params_get(ansible_module, "groups") + owners = module_params_get(ansible_module, "owners") + ownergroups = module_params_get(ansible_module, "ownergroups") + + vault_type = module_params_get(ansible_module, "vault_type") + salt = module_params_get(ansible_module, "vault_salt") + password = module_params_get(ansible_module, "vault_password") + public_key = module_params_get(ansible_module, "vault_public_key") + + vault_data = module_params_get(ansible_module, "vault_data") + + action = module_params_get(ansible_module, "action") + # state + state = module_params_get(ansible_module, "state") + + # Check parameters + + if state == "present": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one vault can be added at a time.") + + elif state == "absent": + if len(names) < 1: + ansible_module.fail_json(msg="No name given.") + + else: + ansible_module.fail_json(msg="Invalid state '%s'" % state) + + check_parameters(ansible_module, state, action, description, username, + service, shared, users, groups, owners, ownergroups, + vault_type, salt, password, public_key, vault_data) + # Init + + changed = False + exit_args = {} + ccache_dir = None + ccache_name = None + try: + if not valid_creds(ansible_module, ipaadmin_principal): + ccache_dir, ccache_name = temp_kinit(ipaadmin_principal, + ipaadmin_password) + + api_connect(context='ansible-freeipa') + + commands = [] + + for name in names: + # Make sure vault exists + res_find = find_vault( + ansible_module, name, username, service, shared) + + # Generate args + args = gen_args(description, username, service, shared, vault_type, + salt, public_key, vault_data) + + # Set default vault_type if needed. + if vault_type is None and vault_data is not None: + if res_find is not None: + res_vault_type = res_find.get('ipavaulttype')[0] + args['ipavaulttype'] = vault_type = res_vault_type + else: + args['ipavaulttype'] = vault_type = "symmetric" + + # verify data encription args + check_encryption_params(ansible_module, state, vault_type, + password, public_key, vault_data, res_find) + + # Create command + if state == "present": + + # Found the vault + if action == "vault": + if res_find is not None: + # For all settings is args, check if there are + # different settings in the find result. + # If yes: modify + if not compare_args_ipa(ansible_module, args, + res_find): + commands.append([name, "vault_mod_internal", args]) + else: + if 'ipavaultsault' not in args: + args['ipavaultsalt'] = os.urandom(32) + commands.append([name, "vault_add_internal", args]) + # archive empty data to set password + pwdargs = data_storage_args( + args, args.get('data', ''), password) + commands.append([name, "vault_archive", pwdargs]) + + # Set res_find to empty dict for next step # noqa + res_find = {} + + # Generate adittion and removal lists + user_add, user_del = \ + gen_add_del_lists(users, + res_find.get('member_user', [])) + group_add, group_del = \ + gen_add_del_lists(groups, + res_find.get('member_group', [])) + owner_add, owner_del = \ + gen_add_del_lists(owners, + res_find.get('owner_user', [])) + ownergroups_add, ownergroups_del = \ + gen_add_del_lists(ownergroups, + res_find.get('owner_group', [])) + + # Add users and groups + if len(user_add) > 0 or len(group_add) > 0: + user_add_args = gen_member_args(args, user_add, + group_add) + commands.append([name, 'vault_add_member', + user_add_args]) + + # Remove users and groups + if len(user_del) > 0 or len(group_del) > 0: + user_del_args = gen_member_args(args, user_del, + group_del) + commands.append([name, 'vault_remove_member', + user_del_args]) + + # Add owner users and groups + if len(user_add) > 0 or len(group_add) > 0: + owner_add_args = gen_member_args(args, owner_add, + ownergroups_add) + commands.append([name, 'vault_add_owner', + owner_add_args]) + + # Remove owner users and groups + if len(user_del) > 0 or len(group_del) > 0: + owner_del_args = gen_member_args(args, owner_del, + ownergroups_del) + commands.append([name, 'vault_remove_owner', + owner_del_args]) + + elif action in "member": + # Add users and groups + if users is not None or groups is not None: + user_args = gen_member_args(args, users, groups) + commands.append([name, 'vault_add_member', user_args]) + if owners is not None or ownergroups is not None: + owner_args = gen_member_args(args, owners, ownergroups) + commands.append([name, 'vault_add_owner', owner_args]) + + if vault_data is not None: + data_args = data_storage_args( + args, args.get('data', ''), password) + commands.append([name, 'vault_archive', data_args]) + + elif state == "absent": + if 'ipavaulttype' in args: + del args['ipavaulttype'] + + if action == "vault": + if res_find is not None: + commands.append([name, "vault_del", args]) + + elif action == "member": + # remove users and groups + if users is not None or groups is not None: + user_args = gen_member_args(args, users, groups) + commands.append([name, 'vault_remove_member', + user_args]) + + if owners is not None or ownergroups is not None: + owner_args = gen_member_args(args, owners, ownergroups) + commands.append([name, 'vault_remove_owner', + owner_args]) + else: + ansible_module.fail_json( + msg="Invalid action '%s' for state '%s'" % + (action, state)) + else: + ansible_module.fail_json(msg="Unkown state '%s'" % state) + + # Execute commands + + errors = [] + for name, command, args in commands: + try: + result = api_command(ansible_module, command, name, args) + + if command == 'vault_archive': + changed = 'Archived data into' in result['summary'] + else: + if "completed" in result: + if result["completed"] > 0: + changed = True + else: + changed = True + except EmptyModlist: + result = {} + except Exception as exception: + ansible_module.fail_json( + msg="%s: %s: %s" % (command, name, str(exception))) + + # Get all errors + # All "already a member" and "not a member" failures in the + # result are ignored. All others are reported. + if "failed" in result and len(result["failed"]) > 0: + for item in result["failed"]: + failed_item = result["failed"][item] + for member_type in failed_item: + for member, failure in failed_item[member_type]: + if "already a member" in failure \ + or "not a member" in failure: + continue + errors.append("%s: %s %s: %s" % ( + command, member_type, member, failure)) + if len(errors) > 0: + ansible_module.fail_json(msg=", ".join(errors)) + + except Exception as exception: + ansible_module.fail_json(msg=str(exception)) + + finally: + temp_kdestroy(ccache_dir, ccache_name) + + # Done + ansible_module.exit_json(changed=changed, **exit_args) + + +if __name__ == "__main__": + main() diff --git a/tests/vault/test_vault.yml b/tests/vault/test_vault.yml new file mode 100644 index 0000000000000000000000000000000000000000..b48f1ea4163fdb5b3a57ff5c90c1251b07368041 --- /dev/null +++ b/tests/vault/test_vault.yml @@ -0,0 +1,562 @@ +--- + +- name: Tests + hosts: ipaserver + become: true + gather_facts: false + + tasks: + + - name: Ensure user vaults are absent + ipavault: + ipaadmin_password: MyPassword123 + name: + - stdvault + - symvault + - asymvault + username: user01 + state: absent + + - name: Ensure test users do not exist. + ipauser: + ipaadmin_password: MyPassword123 + name: + - user01 + - user02 + - user03 + state: absent + + - name: Ensure test groups do not exist. + ipagroup: + ipaadmin_password: MyPassword123 + name: vaultgroup + state: absent + + - name: Ensure vaultgroup exists. + ipagroup: + ipaadmin_password: MyPassword123 + name: vaultgroup + + - name: Ensure user01 exists. + ipauser: + ipaadmin_password: MyPassword123 + name: user01 + first: First + last: Start + + - name: Ensure user02 exists. + ipauser: + ipaadmin_password: MyPassword123 + name: user02 + first: Second + last: Middle + + - name: Ensure user03 exists. + ipauser: + ipaadmin_password: MyPassword123 + name: user03 + first: Third + last: Last + + - name: Ensure shared vaults are absent + ipavault: + ipaadmin_password: MyPassword123 + name: sharedvault + shared: True + state: absent + + - name: Ensure service vaults are absent + ipavault: + ipaadmin_password: MyPassword123 + name: svcvault + service: "HTTP/{{ groups.ipaserver[0] }}" + state: absent + + - name: Ensure symmetric vault is present + ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: user01 + vault_password: MyVaultPassword123 + vault_type: symmetric + register: result + failed_when: not result.changed + + - name: Ensure symmetric vault is present, again + ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: user01 + vault_password: MyVaultPassword123 + vault_type: symmetric + register: result + failed_when: result.changed + + - name: Archive data to symmetric vault + ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: user01 + vault_password: MyVaultPassword123 + vault_data: Hello World. + action: member + register: result + failed_when: not result.changed + + - name: Archive data with non-ASCII characters to symmetric vault + ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: user01 + vault_password: MyVaultPassword123 + vault_data: The world of π is half rounded. + action: member + register: result + failed_when: not result.changed + + - name: Ensure symmetric vault is absent + ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: user01 + state: absent + register: result + failed_when: not result.changed + + - name: Ensure symmetric vault is absent, again + ipavault: + ipaadmin_password: MyPassword123 + name: symvault + username: user01 + state: absent + register: result + failed_when: result.changed + + - name: Ensure asymmetric vault is present. + ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: user01 + description: A symmetric private vault. + vault_public_key: + LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTR + HTkFEQ0JpUUtCZ1FDdGFudjRkK3ptSTZ0T3ova1RXdGowY3AxRAowUENoYy8vR0pJMTUzTi + 9CN3UrN0h3SXlRVlZoNUlXZG1UcCtkWXYzd09yeVpPbzYvbHN5eFJaZ2pZRDRwQ3VGCjlxM + 295VTFEMnFOZERYeGtSaFFETXBiUEVSWWlHbE1jbzdhN0hIVDk1bGNQbmhObVFkb3VGdHlV + bFBUVS96V1kKZldYWTBOeU1UbUtoeFRseUV3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVk + tLS0tLQo= + vault_type: asymmetric + register: result + failed_when: not result.changed + + - name: Ensure asymmetric vault is present, again. + ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: user01 + vault_public_key: + LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTR + HTkFEQ0JpUUtCZ1FDdGFudjRkK3ptSTZ0T3ova1RXdGowY3AxRAowUENoYy8vR0pJMTUzTi + 9CN3UrN0h3SXlRVlZoNUlXZG1UcCtkWXYzd09yeVpPbzYvbHN5eFJaZ2pZRDRwQ3VGCjlxM + 295VTFEMnFOZERYeGtSaFFETXBiUEVSWWlHbE1jbzdhN0hIVDk1bGNQbmhObVFkb3VGdHlV + bFBUVS96V1kKZldYWTBOeU1UbUtoeFRseUV3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVk + tLS0tLQo= + vault_type: asymmetric + register: result + failed_when: result.changed + + - name: Archive data in asymmetric vault. + ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: user01 + vault_data: Hello World. + action: member + register: result + failed_when: not result.changed + + - name: Ensure asymmetric vault is absent. + ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: user01 + state: absent + register: result + failed_when: not result.changed + + - name: Ensure asymmetric vault is absent, again. + ipavault: + ipaadmin_password: MyPassword123 + name: asymvault + username: user01 + state: absent + register: result + failed_when: result.changed + + - name: Ensure standard vault is present. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + vault_type: standard + username: user01 + description: A standard private vault. + register: result + failed_when: not result.changed + + - name: Ensure standard vault is present, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + vault_type: standard + description: A standard private vault. + register: result + failed_when: result.changed + + - name: Archive data in standard vault. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + vault_data: Hello World. + action: member + register: result + failed_when: not result.changed + + - name: Ensure standard vault member user is present. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + users: + - user02 + register: result + failed_when: not result.changed + + - name: Ensure standard vault member user is present, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + users: + - user02 + register: result + failed_when: result.changed + + - name: Ensure more vault member users are present. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + users: + - user01 + - user02 + register: result + failed_when: not result.changed + + - name: Ensure vault member user is still present. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + users: + - user02 + register: result + failed_when: result.changed + + - name: Ensure vault users are absent. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + users: + - user01 + - user02 + state: absent + register: result + failed_when: not result.changed + + - name: Ensure vault users are absent, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + users: + - user01 + - user02 + state: absent + register: result + failed_when: result.changed + + - name: Ensure vault user is absent, once more. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + users: + - user01 + state: absent + register: result + failed_when: result.changed + + - name: Ensure vault member group is present. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + groups: vaultgroup + register: result + failed_when: not result.changed + + - name: Ensure vault member group is present, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + groups: vaultgroup + register: result + failed_when: result.changed + + - name: Ensure vault member group is absent. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + groups: vaultgroup + state: absent + register: result + failed_when: not result.changed + + - name: Ensure vault member group is absent, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + action: member + groups: vaultgroup + state: absent + register: result + failed_when: result.changed + + - name: Ensure vault is absent. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + state: absent + register: result + failed_when: not result.changed + + - name: Ensure vault is absent, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + state: absent + register: result + failed_when: result.changed + + - name: Ensure shared vault is present. + ipavault: + ipaadmin_password: MyPassword123 + name: sharedvault + shared: True + ipavaultpassword: MyVaultPassword123 + register: result + failed_when: not result.changed + + - name: Ensure shared vault is absent. + ipavault: + ipaadmin_password: MyPassword123 + name: sharedvault + shared: True + state: absent + register: result + failed_when: not result.changed + + - name: Ensure service vault is present. + ipavault: + ipaadmin_password: MyPassword123 + name: svcvault + ipavaultpassword: MyVaultPassword123 + service: "HTTP/{{ groups.ipaserver[0] }}" + register: result + failed_when: not result.changed + + - name: Ensure service vault is absent. + ipavault: + ipaadmin_password: MyPassword123 + name: svcvault + service: "HTTP/{{ groups.ipaserver[0] }}" + state: absent + register: result + failed_when: not result.changed + + - name: Ensure vault is present, with members. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + vault_type: standard + users: + - user02 + - user03 + groups: + - vaultgroup + register: result + failed_when: not result.changed + + - name: Ensure vault is present, with members, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + vault_type: standard + users: + - user02 + - user03 + groups: + - vaultgroup + register: result + failed_when: result.changed + + - name: Ensure user02 is not a member of vault stdvault. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + users: user02 + state: absent + action: member + register: result + failed_when: not result.changed + + - name: Ensure user02 is not a member of vault stdvault, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + users: user02 + state: absent + action: member + register: result + failed_when: result.changed + + - name: Ensure user02 is a member of vault stdvault. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + users: user02 + action: member + register: result + failed_when: not result.changed + + - name: Ensure user02 is a member of vault stdvault, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + users: user03 + action: member + register: result + failed_when: result.changed + + - name: Ensure user03 owns vault stdvault. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + owners: user03 + action: member + register: result + failed_when: not result.changed + + - name: Ensure user03 owns vault stdvault, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + owners: user03 + action: member + register: result + failed_when: result.changed + + - name: Ensure user03 is not owner of stdvault. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + owners: user03 + state: absent + action: member + register: result + failed_when: not result.changed + + - name: Ensure user03 is not owner of stdvault, again. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + owners: user03 + state: absent + action: member + register: result + failed_when: result.changed + + - name: Ensure vault is absent. + ipavault: + ipaadmin_password: MyPassword123 + name: stdvault + username: user01 + state: absent + + # cleaup + - name: Ensure test vaults are absent + ipavault: + ipaadmin_password: MyPassword123 + name: + - stdvault + - symvault + - asymvault + username: user01 + state: absent + + - name: Ensure shared vaults are absent + ipavault: + ipaadmin_password: MyPassword123 + name: sharedvault + shared: True + state: absent + + - name: Ensure service vaults are absent + ipavault: + ipaadmin_password: MyPassword123 + name: svcvault + service: "HTTP/{{ groups.ipaserver[0] }}" + state: absent + + - name: Ensure test users do not exist. + ipauser: + ipaadmin_password: MyPassword123 + name: + - user01 + - user02 + - user03 + state: absent + + - name: Ensure test groups do not exist. + ipagroup: + ipaadmin_password: MyPassword123 + name: vaultgroup + state: absent