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