From af4e8432ad5348994ca7c0764530bdaaf2d1fa1e Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Sat, 30 Nov 2019 21:19:33 -0300
Subject: [PATCH] New vault management module.

There is a new vault management module placed in the plugins folder:

  plugins/modules/ipavault.py

The vault module allows to ensure presence and absence of vaults, manage
members and owner of the vault, and archive data in the vault.

Here is the documentation for the module:

    README-vault.md

New example playbooks have been added:

    playbooks/vault/data-archive-in-asymmetric-vault.yml
    playbooks/vault/data-archive-in-symmetric-vault.yml
    playbooks/vault/ensure-asymetric-vault-is-absent.yml
    playbooks/vault/ensure-asymetric-vault-is-present.yml
    playbooks/vault/ensure-service-vault-is-absent.yml
    playbooks/vault/ensure-service-vault-is-present.yml
    playbooks/vault/ensure-shared-vault-is-absent.yml
    playbooks/vault/ensure-shared-vault-is-present.yml
    playbooks/vault/ensure-standard-vault-is-absent.yml
    playbooks/vault/ensure-standard-vault-is-present.yml
    playbooks/vault/ensure-symetric-vault-is-absent.yml
    playbooks/vault/ensure-symetric-vault-is-present.yml
    playbooks/vault/ensure-vault-is-present-with-members.yml
    playbooks/vault/ensure-vault-member-group-is-absent.yml
    playbooks/vault/ensure-vault-member-group-is-present.yml
    playbooks/vault/ensure-vault-member-user-is-absent.yml
    playbooks/vault/ensure-vault-member-user-is-present.yml
    playbooks/vault/ensure-vault-owner-is-absent.yml
    playbooks/vault/ensure-vault-owner-is-present.yml

New tests added for the module:

    tests/vault/test_vault.yml
---
 README-vault.md                               | 203 ++++++
 README.md                                     |   2 +
 .../data-archive-in-asymmetric-vault.yml      |  13 +
 .../vault/data-archive-in-symmetric-vault.yml |  14 +
 .../ensure-asymetric-vault-is-absent.yml      |  12 +
 .../ensure-asymetric-vault-is-present.yml     |  13 +
 .../vault/ensure-service-vault-is-absent.yml  |  12 +
 .../vault/ensure-service-vault-is-present.yml |  13 +
 .../vault/ensure-shared-vault-is-absent.yml   |  12 +
 .../vault/ensure-shared-vault-is-present.yml  |  13 +
 .../vault/ensure-standard-vault-is-absent.yml |  12 +
 .../ensure-standard-vault-is-present.yml      |  13 +
 .../vault/ensure-symetric-vault-is-absent.yml |  12 +
 .../ensure-symetric-vault-is-present.yml      |  13 +
 .../ensure-vault-is-present-with-members.yml  |  17 +
 .../ensure-vault-member-group-is-absent.yml   |  14 +
 .../ensure-vault-member-group-is-present.yml  |  14 +
 .../ensure-vault-member-user-is-absent.yml    |  16 +
 .../ensure-vault-member-user-is-present.yml   |  14 +
 .../vault/ensure-vault-owner-is-absent.yml    |  15 +
 .../vault/ensure-vault-owner-is-present.yml   |  15 +
 plugins/modules/ipavault.py                   | 646 ++++++++++++++++++
 tests/vault/test_vault.yml                    | 562 +++++++++++++++
 23 files changed, 1670 insertions(+)
 create mode 100644 README-vault.md
 create mode 100644 playbooks/vault/data-archive-in-asymmetric-vault.yml
 create mode 100644 playbooks/vault/data-archive-in-symmetric-vault.yml
 create mode 100644 playbooks/vault/ensure-asymetric-vault-is-absent.yml
 create mode 100644 playbooks/vault/ensure-asymetric-vault-is-present.yml
 create mode 100644 playbooks/vault/ensure-service-vault-is-absent.yml
 create mode 100644 playbooks/vault/ensure-service-vault-is-present.yml
 create mode 100644 playbooks/vault/ensure-shared-vault-is-absent.yml
 create mode 100644 playbooks/vault/ensure-shared-vault-is-present.yml
 create mode 100644 playbooks/vault/ensure-standard-vault-is-absent.yml
 create mode 100644 playbooks/vault/ensure-standard-vault-is-present.yml
 create mode 100644 playbooks/vault/ensure-symetric-vault-is-absent.yml
 create mode 100644 playbooks/vault/ensure-symetric-vault-is-present.yml
 create mode 100644 playbooks/vault/ensure-vault-is-present-with-members.yml
 create mode 100644 playbooks/vault/ensure-vault-member-group-is-absent.yml
 create mode 100644 playbooks/vault/ensure-vault-member-group-is-present.yml
 create mode 100644 playbooks/vault/ensure-vault-member-user-is-absent.yml
 create mode 100644 playbooks/vault/ensure-vault-member-user-is-present.yml
 create mode 100644 playbooks/vault/ensure-vault-owner-is-absent.yml
 create mode 100644 playbooks/vault/ensure-vault-owner-is-present.yml
 create mode 100644 plugins/modules/ipavault.py
 create mode 100644 tests/vault/test_vault.yml

diff --git a/README-vault.md b/README-vault.md
new file mode 100644
index 00000000..4be24696
--- /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 5efbd088..9b150a1a 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 00000000..f70d76db
--- /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 00000000..eb8b0a0f
--- /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 00000000..62866c43
--- /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 00000000..f946779b
--- /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 00000000..1affb4c7
--- /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 00000000..423fef1f
--- /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 00000000..fdf1babd
--- /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 00000000..9f25e407
--- /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 00000000..c52806e6
--- /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 00000000..a55f55cc
--- /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 00000000..87c3bdc2
--- /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 00000000..ea10d573
--- /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 00000000..65cd72d4
--- /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 00000000..f26bc670
--- /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 00000000..84dc087e
--- /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 00000000..99c9d6cc
--- /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 00000000..820a9f7c
--- /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 00000000..33670fc8
--- /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 00000000..52b1d93c
--- /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 00000000..32137ee7
--- /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 00000000..b48f1ea4
--- /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
-- 
GitLab