Blame SOURCES/ansible-freeipa-0.1.12-Add-suppport-for-changing-password-of-symmetric-vaults_rhbz#1839197.patch

fb9e9a
From 78b635ae78346fdfb298dd0d0c82ae1ff34b754a Mon Sep 17 00:00:00 2001
fb9e9a
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
fb9e9a
Date: Tue, 23 Jun 2020 17:53:47 -0300
fb9e9a
Subject: [PATCH] Add suppport for changing password of symmetric vaults.
fb9e9a
fb9e9a
Allows changing passwords of symmetric waults, using a new variable
fb9e9a
`new_password` (or the file-base version, `new_password_file`). The
fb9e9a
old password must be passed using the `password` or `password_file`
fb9e9a
variables that also received new aliases `old_password` and
fb9e9a
`old_password_file`, respectively.
fb9e9a
fb9e9a
Tests were modyfied to reflect the changes.
fb9e9a
---
fb9e9a
 README-vault.md                               |  23 +++-
fb9e9a
 .../vault/change-password-symmetric-vault.yml |   2 +-
fb9e9a
 plugins/modules/ipavault.py                   | 129 +++++++++++++++---
fb9e9a
 tests/vault/test_vault_symmetric.yml          |  64 +++++++++
fb9e9a
 4 files changed, 194 insertions(+), 24 deletions(-)
fb9e9a
fb9e9a
diff --git a/README-vault.md b/README-vault.md
fb9e9a
index c7ae6916..fa1d3e11 100644
fb9e9a
--- a/README-vault.md
fb9e9a
+++ b/README-vault.md
fb9e9a
@@ -165,6 +165,22 @@ Example playbook to make sure vault data is absent in a symmetric vault:
fb9e9a
       state: absent
fb9e9a
 ```
fb9e9a
 
fb9e9a
+Example playbook to change the password of a symmetric:
fb9e9a
+
fb9e9a
+```yaml
fb9e9a
+---
fb9e9a
+- name: Playbook to handle vaults
fb9e9a
+  hosts: ipaserver
fb9e9a
+  become: true
fb9e9a
+
fb9e9a
+  tasks:
fb9e9a
+  - ipavault:
fb9e9a
+      ipaadmin_password: SomeADMINpassword
fb9e9a
+      name: symvault
fb9e9a
+      old_password: SomeVAULTpassword
fb9e9a
+      new_password: SomeNEWpassword
fb9e9a
+```
fb9e9a
+
fb9e9a
 Example playbook to make sure vault is absent:
fb9e9a
 
fb9e9a
 ```yaml
fb9e9a
@@ -197,8 +213,11 @@ Variable | Description | Required
fb9e9a
 `name` \| `cn` | The list of vault name strings. | yes
fb9e9a
 `description` | The vault description string. | no
fb9e9a
 `nomembers` | Suppress processing of membership attributes. (bool) | no
fb9e9a
-`password ` \| `vault_password` \| `ipavaultpassword` | Vault password. | no
fb9e9a
-`public_key ` \| `vault_public_key` \| `ipavaultpublickey` | Base64 encoded vault public key. | no
fb9e9a
+`password` \| `vault_password` \| `ipavaultpassword` \| `old_password`| Vault password. | no
fb9e9a
+`password_file` \| `vault_password_file` \| `old_password_file`| File containing Base64 encoded Vault password. | no
fb9e9a
+`new_password` | Vault new password. | no
fb9e9a
+`new_password_file` | File containing Base64 encoded new Vault password. | no
fb9e9a
+`public_key ` \| `vault_public_key` \| `old_password_file` | Base64 encoded vault public key. | no
fb9e9a
 `public_key_file` \| `vault_public_key_file` | Path to file with public key. | no
fb9e9a
 `private_key `\| `vault_private_key` | Base64 encoded vault private key. Used only to retrieve data. | no
fb9e9a
 `private_key_file` \| `vault_private_key_file` | Path to file with private key. Used only to retrieve data. | no
fb9e9a
diff --git a/playbooks/vault/change-password-symmetric-vault.yml b/playbooks/vault/change-password-symmetric-vault.yml
fb9e9a
index 3871f45d..396a79f6 100644
fb9e9a
--- a/playbooks/vault/change-password-symmetric-vault.yml
fb9e9a
+++ b/playbooks/vault/change-password-symmetric-vault.yml
fb9e9a
@@ -10,7 +10,7 @@
fb9e9a
       ipaadmin_password: SomeADMINpassword
fb9e9a
       name: symvault
fb9e9a
       password: SomeVAULTpassword
fb9e9a
-  - name: Change vault passord.
fb9e9a
+  - name: Change vault password.
fb9e9a
     ipavault:
fb9e9a
       ipaadmin_password: SomeADMINpassword
fb9e9a
       name: symvault
fb9e9a
diff --git a/plugins/modules/ipavault.py b/plugins/modules/ipavault.py
fb9e9a
index ad5dd413..46c6fcdb 100644
fb9e9a
--- a/plugins/modules/ipavault.py
fb9e9a
+++ b/plugins/modules/ipavault.py
fb9e9a
@@ -69,12 +69,20 @@
fb9e9a
     description: password to be used on symmetric vault.
fb9e9a
     required: false
fb9e9a
     type: string
fb9e9a
-    aliases: ["ipavaultpassword", "vault_password"]
fb9e9a
+    aliases: ["ipavaultpassword", "vault_password", "old_password"]
fb9e9a
   password_file:
fb9e9a
     description: file with password to be used on symmetric vault.
fb9e9a
     required: false
fb9e9a
     type: string
fb9e9a
-    aliases: ["vault_password_file"]
fb9e9a
+    aliases: ["vault_password_file", "old_password_file"]
fb9e9a
+  new_password:
fb9e9a
+    description: new password to be used on symmetric vault.
fb9e9a
+    required: false
fb9e9a
+    type: string
fb9e9a
+  new_password_file:
fb9e9a
+    description: file with new password to be used on symmetric vault.
fb9e9a
+    required: false
fb9e9a
+    type: string
fb9e9a
   salt:
fb9e9a
     description: Vault salt.
fb9e9a
     required: false
fb9e9a
@@ -235,7 +243,15 @@
fb9e9a
     state: retrieved
fb9e9a
   register: result
fb9e9a
 - debug:
fb9e9a
-    msg: "{{ result.data | b64decode }}"
fb9e9a
+    msg: "{{ result.data }}"
fb9e9a
+
fb9e9a
+# Change password of a symmetric vault
fb9e9a
+- ipavault:
fb9e9a
+    ipaadmin_password: SomeADMINpassword
fb9e9a
+    name: symvault
fb9e9a
+    username: admin
fb9e9a
+    old_password: SomeVAULTpassword
fb9e9a
+    new_password: SomeNEWpassword
fb9e9a
 
fb9e9a
 # Ensure vault symvault is absent
fb9e9a
 - ipavault:
fb9e9a
@@ -416,18 +432,29 @@ def check_parameters(module, state, action, description, username, service,
fb9e9a
                      shared, users, groups, services, owners, ownergroups,
fb9e9a
                      ownerservices, vault_type, salt, password, password_file,
fb9e9a
                      public_key, public_key_file, private_key,
fb9e9a
-                     private_key_file, vault_data, datafile_in, datafile_out):
fb9e9a
+                     private_key_file, vault_data, datafile_in, datafile_out,
fb9e9a
+                     new_password, new_password_file):
fb9e9a
     invalid = []
fb9e9a
     if state == "present":
fb9e9a
         invalid = ['private_key', 'private_key_file', 'datafile_out']
fb9e9a
 
fb9e9a
+        if all([password, password_file]) \
fb9e9a
+           or all([new_password, new_password_file]):
fb9e9a
+            module.fail_json(msg="Password specified multiple times.")
fb9e9a
+
fb9e9a
+        if any([new_password, new_password_file]) \
fb9e9a
+           and not any([password, password_file]):
fb9e9a
+            module.fail_json(
fb9e9a
+                msg="Either `password` or `password_file` must be provided to "
fb9e9a
+                    "change symmetric vault password.")
fb9e9a
+
fb9e9a
         if action == "member":
fb9e9a
             invalid.extend(['description'])
fb9e9a
 
fb9e9a
     elif state == "absent":
fb9e9a
         invalid = ['description', 'salt', 'vault_type', 'private_key',
fb9e9a
                    'private_key_file', 'datafile_in', 'datafile_out',
fb9e9a
-                   'vault_data']
fb9e9a
+                   'vault_data', 'new_password', 'new_password_file']
fb9e9a
 
fb9e9a
         if action == "vault":
fb9e9a
             invalid.extend(['users', 'groups', 'services', 'owners',
fb9e9a
@@ -437,7 +464,7 @@ def check_parameters(module, state, action, description, username, service,
fb9e9a
     elif state == "retrieved":
fb9e9a
         invalid = ['description', 'salt', 'datafile_in', 'users', 'groups',
fb9e9a
                    'owners', 'ownergroups', 'public_key', 'public_key_file',
fb9e9a
-                   'vault_data']
fb9e9a
+                   'vault_data', 'new_password', 'new_password_file']
fb9e9a
         if action == 'member':
fb9e9a
             module.fail_json(
fb9e9a
                 msg="State `retrieved` do not support action `member`.")
fb9e9a
@@ -458,11 +485,17 @@ def check_parameters(module, state, action, description, username, service,
fb9e9a
 def check_encryption_params(module, state, action, vault_type, salt,
fb9e9a
                             password, password_file, public_key,
fb9e9a
                             public_key_file, private_key, private_key_file,
fb9e9a
-                            vault_data, datafile_in, datafile_out, res_find):
fb9e9a
+                            vault_data, datafile_in, datafile_out,
fb9e9a
+                            new_password, new_password_file, res_find):
fb9e9a
     vault_type_invalid = []
fb9e9a
+
fb9e9a
+    if res_find is not None:
fb9e9a
+        vault_type = res_find['ipavaulttype']
fb9e9a
+
fb9e9a
     if vault_type == "standard":
fb9e9a
         vault_type_invalid = ['public_key', 'public_key_file', 'password',
fb9e9a
-                              'password_file', 'salt']
fb9e9a
+                              'password_file', 'salt', 'new_password',
fb9e9a
+                              'new_password_file']
fb9e9a
 
fb9e9a
     if vault_type is None or vault_type == "symmetric":
fb9e9a
         vault_type_invalid = ['public_key', 'public_key_file',
fb9e9a
@@ -473,8 +506,14 @@ def check_encryption_params(module, state, action, vault_type, salt,
fb9e9a
                 msg="Symmetric vault requires password or password_file "
fb9e9a
                     "to store data or change `salt`.")
fb9e9a
 
fb9e9a
+        if any([new_password, new_password_file]) and res_find is None:
fb9e9a
+            module.fail_json(
fb9e9a
+                msg="Cannot modify password of inexistent vault.")
fb9e9a
+
fb9e9a
     if vault_type == "asymmetric":
fb9e9a
-        vault_type_invalid = ['password', 'password_file']
fb9e9a
+        vault_type_invalid = [
fb9e9a
+            'password', 'password_file', 'new_password', 'new_password_file'
fb9e9a
+        ]
fb9e9a
         if not any([public_key, public_key_file]) and res_find is None:
fb9e9a
             module.fail_json(
fb9e9a
                 msg="Assymmetric vault requires public_key "
fb9e9a
@@ -487,6 +526,43 @@ def check_encryption_params(module, state, action, vault_type, salt,
fb9e9a
                 (param, vault_type or 'symmetric'))
fb9e9a
 
fb9e9a
 
fb9e9a
+def change_password(module, res_find, password, password_file, new_password,
fb9e9a
+                    new_password_file):
fb9e9a
+    """
fb9e9a
+    Change the password of a symmetric vault.
fb9e9a
+
fb9e9a
+    To change the password of a vault, it is needed to retrieve the stored
fb9e9a
+    data with the current password, and store the data again, with the new
fb9e9a
+    password, forcing it to override the old one.
fb9e9a
+    """
fb9e9a
+    # verify parameters.
fb9e9a
+    if not any([new_password, new_password_file]):
fb9e9a
+        return []
fb9e9a
+    if res_find["ipavaulttype"][0] != "symmetric":
fb9e9a
+        module.fail_json(msg="Cannot change password of `%s` vault."
fb9e9a
+                             % res_find["ipavaulttype"])
fb9e9a
+
fb9e9a
+    # prepare arguments to retrieve data.
fb9e9a
+    name = res_find["cn"][0]
fb9e9a
+    args = {}
fb9e9a
+    if password:
fb9e9a
+        args["password"] = password
fb9e9a
+    if password_file:
fb9e9a
+        args["password"] = password_file
fb9e9a
+    # retrieve current stored data
fb9e9a
+    result = api_command(module, 'vault_retrieve', name, args)
fb9e9a
+    args['data'] = result['result']['data']
fb9e9a
+
fb9e9a
+    # modify arguments to store data with new password.
fb9e9a
+    if password:
fb9e9a
+        args["password"] = new_password
fb9e9a
+    if password_file:
fb9e9a
+        args["password"] = new_password_file
fb9e9a
+    args["override_password"] = True
fb9e9a
+    # return the command to store data with the new password.
fb9e9a
+    return [(name, "vault_archive", args)]
fb9e9a
+
fb9e9a
+
fb9e9a
 def main():
fb9e9a
     ansible_module = AnsibleModule(
fb9e9a
         argument_spec=dict(
fb9e9a
@@ -533,10 +609,18 @@ def main():
fb9e9a
             datafile_out=dict(type="str", required=False, default=None,
fb9e9a
                               aliases=['out']),
fb9e9a
             vault_password=dict(type="str", required=False, default=None,
fb9e9a
-                                aliases=['ipavaultpassword', 'password'],
fb9e9a
-                                no_log=True),
fb9e9a
+                                no_log=True,
fb9e9a
+                                aliases=['ipavaultpassword', 'password',
fb9e9a
+                                         "old_password"]),
fb9e9a
             vault_password_file=dict(type="str", required=False, default=None,
fb9e9a
-                                     no_log=False, aliases=['password_file']),
fb9e9a
+                                     no_log=False,
fb9e9a
+                                     aliases=[
fb9e9a
+                                        'password_file', "old_password_file"
fb9e9a
+                                     ]),
fb9e9a
+            new_password=dict(type="str", required=False, default=None,
fb9e9a
+                              no_log=True),
fb9e9a
+            new_password_file=dict(type="str", required=False, default=None,
fb9e9a
+                                   no_log=False),
fb9e9a
             # state
fb9e9a
             action=dict(type="str", default="vault",
fb9e9a
                         choices=["vault", "data", "member"]),
fb9e9a
@@ -546,6 +630,7 @@ def main():
fb9e9a
         supports_check_mode=True,
fb9e9a
         mutually_exclusive=[['username', 'service', 'shared'],
fb9e9a
                             ['datafile_in', 'vault_data'],
fb9e9a
+                            ['new_password', 'new_password_file'],
fb9e9a
                             ['vault_password', 'vault_password_file'],
fb9e9a
                             ['vault_public_key', 'vault_public_key_file']],
fb9e9a
     )
fb9e9a
@@ -576,6 +661,8 @@ def main():
fb9e9a
     salt = module_params_get(ansible_module, "vault_salt")
fb9e9a
     password = module_params_get(ansible_module, "vault_password")
fb9e9a
     password_file = module_params_get(ansible_module, "vault_password_file")
fb9e9a
+    new_password = module_params_get(ansible_module, "new_password")
fb9e9a
+    new_password_file = module_params_get(ansible_module, "new_password_file")
fb9e9a
     public_key = module_params_get(ansible_module, "vault_public_key")
fb9e9a
     public_key_file = module_params_get(ansible_module,
fb9e9a
                                         "vault_public_key_file")
fb9e9a
@@ -614,7 +701,8 @@ def main():
fb9e9a
                      service, shared, users, groups, services, owners,
fb9e9a
                      ownergroups, ownerservices, vault_type, salt, password,
fb9e9a
                      password_file, public_key, public_key_file, private_key,
fb9e9a
-                     private_key_file, vault_data, datafile_in, datafile_out)
fb9e9a
+                     private_key_file, vault_data, datafile_in, datafile_out,
fb9e9a
+                     new_password, new_password_file)
fb9e9a
     # Init
fb9e9a
 
fb9e9a
     changed = False
fb9e9a
@@ -660,7 +748,7 @@ def main():
fb9e9a
                     ansible_module, state, action, vault_type, salt, password,
fb9e9a
                     password_file, public_key, public_key_file, private_key,
fb9e9a
                     private_key_file, vault_data, datafile_in, datafile_out,
fb9e9a
-                    res_find)
fb9e9a
+                    new_password, new_password_file, res_find)
fb9e9a
 
fb9e9a
                 # Found the vault
fb9e9a
                 if action == "vault":
fb9e9a
@@ -721,7 +809,6 @@ def main():
fb9e9a
                     owner_add_args = gen_member_args(
fb9e9a
                         args, owner_add, ownergroups_add, ownerservice_add)
fb9e9a
                     if owner_add_args is not None:
fb9e9a
-                        # ansible_module.warn("OWNER ADD: %s" % owner_add_args)
fb9e9a
                         commands.append(
fb9e9a
                             [name, 'vault_add_owner', owner_add_args])
fb9e9a
 
fb9e9a
@@ -729,7 +816,6 @@ def main():
fb9e9a
                     owner_del_args = gen_member_args(
fb9e9a
                         args, owner_del, ownergroups_del, ownerservice_del)
fb9e9a
                     if owner_del_args is not None:
fb9e9a
-                        # ansible_module.warn("OWNER DEL: %s" % owner_del_args)
fb9e9a
                         commands.append(
fb9e9a
                             [name, 'vault_remove_owner', owner_del_args])
fb9e9a
 
fb9e9a
@@ -758,19 +844,22 @@ def main():
fb9e9a
                 if any([vault_data, datafile_in]):
fb9e9a
                     commands.append([name, "vault_archive", pwdargs])
fb9e9a
 
fb9e9a
+                cmds = change_password(
fb9e9a
+                    ansible_module, res_find, password, password_file,
fb9e9a
+                    new_password, new_password_file)
fb9e9a
+                commands.extend(cmds)
fb9e9a
+
fb9e9a
             elif state == "retrieved":
fb9e9a
                 if res_find is None:
fb9e9a
                     ansible_module.fail_json(
fb9e9a
                         msg="Vault `%s` not found to retrieve data." % name)
fb9e9a
 
fb9e9a
-                vault_type = res_find['cn']
fb9e9a
-
fb9e9a
                 # verify data encription args
fb9e9a
                 check_encryption_params(
fb9e9a
                     ansible_module, state, action, vault_type, salt, password,
fb9e9a
                     password_file, public_key, public_key_file, private_key,
fb9e9a
                     private_key_file, vault_data, datafile_in, datafile_out,
fb9e9a
-                    res_find)
fb9e9a
+                    new_password, new_password_file, res_find)
fb9e9a
 
fb9e9a
                 pwdargs = data_storage_args(
fb9e9a
                     args, vault_data, password, password_file, private_key,
fb9e9a
@@ -813,7 +902,6 @@ def main():
fb9e9a
         errors = []
fb9e9a
         for name, command, args in commands:
fb9e9a
             try:
fb9e9a
-                # ansible_module.warn("RUN: %s %s %s" % (command, name, args))
fb9e9a
                 result = api_command(ansible_module, command, name, args)
fb9e9a
 
fb9e9a
                 if command == 'vault_archive':
fb9e9a
@@ -829,7 +917,6 @@ def main():
fb9e9a
                         raise Exception("No data retrieved.")
fb9e9a
                     changed = False
fb9e9a
                 else:
fb9e9a
-                    # ansible_module.warn("RESULT: %s" % (result))
fb9e9a
                     if "completed" in result:
fb9e9a
                         if result["completed"] > 0:
fb9e9a
                             changed = True
fb9e9a
diff --git a/tests/vault/test_vault_symmetric.yml b/tests/vault/test_vault_symmetric.yml
fb9e9a
index c9429f4f..a6072d88 100644
fb9e9a
--- a/tests/vault/test_vault_symmetric.yml
fb9e9a
+++ b/tests/vault/test_vault_symmetric.yml
fb9e9a
@@ -178,6 +178,61 @@
fb9e9a
     register: result
fb9e9a
     failed_when: result.data != 'Hello World.' or result.changed
fb9e9a
 
fb9e9a
+  - name: Change vault password.
fb9e9a
+    ipavault:
fb9e9a
+      ipaadmin_password: SomeADMINpassword
fb9e9a
+      name: symvault
fb9e9a
+      password: SomeVAULTpassword
fb9e9a
+      new_password: SomeNEWpassword
fb9e9a
+    register: result
fb9e9a
+    failed_when: not result.changed
fb9e9a
+
fb9e9a
+  - name: Retrieve data from symmetric vault, with wrong password.
fb9e9a
+    ipavault:
fb9e9a
+      ipaadmin_password: SomeADMINpassword
fb9e9a
+      name: symvault
fb9e9a
+      password: SomeVAULTpassword
fb9e9a
+      state: retrieved
fb9e9a
+    register: result
fb9e9a
+    failed_when: not result.failed or "Invalid credentials" not in result.msg
fb9e9a
+
fb9e9a
+  - name: Change vault password, with wrong `old_password`.
fb9e9a
+    ipavault:
fb9e9a
+      ipaadmin_password: SomeADMINpassword
fb9e9a
+      name: symvault
fb9e9a
+      password: SomeVAULTpassword
fb9e9a
+      new_password: SomeNEWpassword
fb9e9a
+    register: result
fb9e9a
+    failed_when: not result.failed or "Invalid credentials" not in result.msg
fb9e9a
+
fb9e9a
+  - name: Retrieve data from symmetric vault, with new password.
fb9e9a
+    ipavault:
fb9e9a
+      ipaadmin_password: SomeADMINpassword
fb9e9a
+      name: symvault
fb9e9a
+      password: SomeNEWpassword
fb9e9a
+      state: retrieved
fb9e9a
+    register: result
fb9e9a
+    failed_when: result.data != 'Hello World.' or result.changed
fb9e9a
+
fb9e9a
+  - name: Try to add vault with multiple passwords.
fb9e9a
+    ipavault:
fb9e9a
+      ipaadmin_password: SomeADMINpassword
fb9e9a
+      name: inexistentvault
fb9e9a
+      password: SomeVAULTpassword
fb9e9a
+      password_file: "{{ ansible_env.HOME }}/password.txt"
fb9e9a
+    register: result
fb9e9a
+    failed_when: not result.failed or "parameters are mutually exclusive" not in result.msg
fb9e9a
+
fb9e9a
+  - name: Try to add vault with multiple new passwords.
fb9e9a
+    ipavault:
fb9e9a
+      ipaadmin_password: SomeADMINpassword
fb9e9a
+      name: inexistentvault
fb9e9a
+      password: SomeVAULTpassword
fb9e9a
+      new_password: SomeVAULTpassword
fb9e9a
+      new_password_file: "{{ ansible_env.HOME }}/password.txt"
fb9e9a
+    register: result
fb9e9a
+    failed_when: not result.failed or "parameters are mutually exclusive" not in result.msg
fb9e9a
+
fb9e9a
   - name: Ensure symmetric vault is absent
fb9e9a
     ipavault:
fb9e9a
       ipaadmin_password: SomeADMINpassword
fb9e9a
@@ -194,5 +249,14 @@
fb9e9a
     register: result
fb9e9a
     failed_when: result.changed
fb9e9a
 
fb9e9a
+  - name: Try to change password of inexistent vault.
fb9e9a
+    ipavault:
fb9e9a
+      ipaadmin_password: SomeADMINpassword
fb9e9a
+      name: inexistentvault
fb9e9a
+      password: SomeVAULTpassword
fb9e9a
+      new_password: SomeNEWpassword
fb9e9a
+    register: result
fb9e9a
+    failed_when: not result.failed or "Cannot modify password of inexistent vault" not in result.msg
fb9e9a
+
fb9e9a
   - name: Cleanup testing environment.
fb9e9a
     import_tasks: env_cleanup.yml