[Freeipa-devel] [PATCH] Password vault

Endi Sukma Dewata edewata at redhat.com
Tue Mar 17 11:34:43 UTC 2015


On 3/17/2015 11:59 AM, Endi Sukma Dewata wrote:
> On 3/13/2015 2:27 AM, Endi Sukma Dewata wrote:
>> On 3/11/2015 9:12 PM, Endi Sukma Dewata wrote:
>>> Thanks for the review. New patch attached to be applied on top of all
>>> previous patches. Please see comments below.
>>
>> New patch #362-1 attached replacing #362. It fixed some issues in
>> handle_not_found().
>
> New patch #363 attached. It adds supports for vault & vaultcontainer ID
> parameter.

Attached are patch #363-1 replacing #363, and #364 providing vault copy 
functionality.

-- 
Endi S. Dewata
-------------- next part --------------
>From cd5ebe18edd3583ba044ba76e30ad40e9346d890 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at redhat.com>
Date: Thu, 12 Mar 2015 09:21:02 -0400
Subject: [PATCH] Vault ID improvements.

The vault plugin has been modified to accept a single vault ID
in addition to separate name and parent ID attributes. The vault
container has also been modified in the same way. New test cases
have been added to verify this functionality.

https://fedorahosted.org/freeipa/ticket/3872
---
 API.txt                                            |  50 +++----
 ipalib/plugins/vault.py                            | 143 +++++++++++++++++--
 ipalib/plugins/vaultcontainer.py                   | 137 +++++++++++++++++--
 ipalib/plugins/vaultsecret.py                      |  10 +-
 ipatests/test_xmlrpc/test_vault_plugin.py          | 151 +++++++++++++++++++++
 ipatests/test_xmlrpc/test_vaultcontainer_plugin.py |  90 ++++++------
 ipatests/test_xmlrpc/test_vaultsecret_plugin.py    | 115 ++++++++++++++++
 7 files changed, 590 insertions(+), 106 deletions(-)

diff --git a/API.txt b/API.txt
index 3a741755ab3e15e0175599a16a090b04d46d6be8..3f78493d986a292538dcca68133824bfb591149b 100644
--- a/API.txt
+++ b/API.txt
@@ -4515,7 +4515,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vault_add
 args: 1,20,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
@@ -4541,7 +4541,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vault_add_member
 args: 1,7,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
 option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
@@ -4554,7 +4554,7 @@ output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vault_add_owner
 args: 1,7,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
 option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
@@ -4567,7 +4567,7 @@ output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vault_archive
 args: 1,15,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
 option: Bytes('data?', cli_name='data')
@@ -4588,7 +4588,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vault_del
 args: 1,3,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Str('container_id?', cli_name='container_id')
 option: Flag('continue', autofill=True, cli_name='continue', default=False)
 option: Str('version?', exclude='webui')
@@ -4599,7 +4599,7 @@ command: vault_find
 args: 1,15,4
 arg: Str('criteria?', noextrawhitespace=False)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
-option: Str('cn', attribute=True, autofill=False, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=False)
+option: Str('cn', attribute=True, autofill=False, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=False)
 option: Str('container_id?', cli_name='container_id')
 option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
 option: Bytes('ipaescrowpublickey', attribute=True, autofill=False, cli_name='escrow_public_key', multivalue=False, query=True, required=False)
@@ -4619,7 +4619,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: vault_mod
 args: 1,15,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
@@ -4640,7 +4640,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vault_remove_member
 args: 1,7,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
 option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
@@ -4653,7 +4653,7 @@ output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vault_remove_owner
 args: 1,7,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
 option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
@@ -4666,7 +4666,7 @@ output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vault_retrieve
 args: 1,16,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
 option: Bytes('escrow_private_key?', cli_name='escrow_private_key')
@@ -4688,7 +4688,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vault_show
 args: 1,6,3
-arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
@@ -4705,7 +4705,7 @@ option: Str('version?', exclude='webui')
 output: Output('result', None, None)
 command: vaultcontainer_add
 args: 1,9,3
-arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, required=True)
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id', attribute=False, cli_name='container_id', multivalue=False, required=False)
@@ -4720,7 +4720,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vaultcontainer_add_member
 args: 1,7,3
-arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
@@ -4733,7 +4733,7 @@ output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vaultcontainer_add_owner
 args: 1,7,3
-arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
@@ -4746,7 +4746,7 @@ output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vaultcontainer_del
 args: 1,4,3
-arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('continue', autofill=True, cli_name='continue', default=False)
 option: Flag('force?', autofill=True, default=False)
 option: Str('parent_id?', cli_name='parent_id')
@@ -4758,7 +4758,7 @@ command: vaultcontainer_find
 args: 1,11,4
 arg: Str('criteria?', noextrawhitespace=False)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
-option: Str('cn', attribute=True, autofill=False, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=False)
+option: Str('cn', attribute=True, autofill=False, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=False)
 option: Str('container_id', attribute=False, autofill=False, cli_name='container_id', multivalue=False, query=True, required=False)
 option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
@@ -4774,7 +4774,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: vaultcontainer_mod
 args: 1,11,3
-arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id', attribute=False, autofill=False, cli_name='container_id', multivalue=False, required=False)
@@ -4791,7 +4791,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vaultcontainer_remove_member
 args: 1,7,3
-arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
@@ -4804,7 +4804,7 @@ output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vaultcontainer_remove_owner
 args: 1,7,3
-arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('group*', alwaysask=True, cli_name='groups', csv=True)
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
@@ -4817,7 +4817,7 @@ output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vaultcontainer_show
 args: 1,6,3
-arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='container_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
 option: Str('parent_id?', cli_name='parent_id')
@@ -4829,7 +4829,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vaultsecret_add
 args: 2,12,3
-arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 arg: Str('secret_name', attribute=True, cli_name='secret', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
@@ -4848,7 +4848,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vaultsecret_del
 args: 2,8,3
-arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 arg: Str('secret_name', attribute=True, cli_name='secret', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
@@ -4863,7 +4863,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vaultsecret_find
 args: 2,12,4
-arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 arg: Str('criteria?', noextrawhitespace=False)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
@@ -4883,7 +4883,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: vaultsecret_mod
 args: 2,12,3
-arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 arg: Str('secret_name', attribute=True, cli_name='secret', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
@@ -4902,7 +4902,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vaultsecret_show
 args: 2,11,3
-arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
+arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 arg: Str('secret_name', attribute=True, cli_name='secret', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
index d47067758186601365e5924f5d13c7ab51ba66e5..38693d0710e000695cae21fb4db5dfb4c85b5c74 100644
--- a/ipalib/plugins/vault.py
+++ b/ipalib/plugins/vault.py
@@ -61,7 +61,7 @@ EXAMPLES:
    ipa vault-find
 """) + _("""
  List shared vaults:
-   ipa vault-find --container-id /shared
+   ipa vault-find /shared
 """) + _("""
  Add a standard vault:
    ipa vault-add MyVault
@@ -171,8 +171,8 @@ class vault(LDAPObject):
             cli_name='vault_name',
             label=_('Vault name'),
             primary_key=True,
-            pattern='^[a-zA-Z0-9_.-]+$',
-            pattern_errmsg='may only include letters, numbers, _, ., and -',
+            pattern='^[a-zA-Z0-9_.-/]+$',
+            pattern_errmsg='may only include letters, numbers, _, ., -, and /',
             maxlength=255,
         ),
         Str('vault_id?',
@@ -217,7 +217,7 @@ class vault(LDAPObject):
 
         # get vault ID from parameters
         name = keys[-1]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
         vault_id = container_id + name
 
         dn = self.base_dn
@@ -250,6 +250,81 @@ class vault(LDAPObject):
 
         return id
 
+    def split_id(self, id):
+        """
+        Splits a vault ID into (vault name, container ID) tuple.
+        """
+
+        if not id:
+            return (None, None)
+
+        # split ID into container ID and vault name
+        parts = id.rsplit(u'/', 1)
+
+        if len(parts) == 2:
+            vault_name = parts[1]
+            container_id = u'%s/' % parts[0]
+
+        else:
+            vault_name = parts[0]
+            container_id = None
+
+        if not vault_name:
+            vault_name = None
+
+        return (vault_name, container_id)
+
+    def merge_id(self, vault_name, container_id):
+        """
+        Merges a vault name and a container ID into a vault ID.
+        """
+
+        if not vault_name:
+            id = container_id
+
+        elif vault_name.startswith('/') or not container_id:
+            id = vault_name
+
+        else:
+            id = container_id + vault_name
+
+        return id
+
+    def normalize_params(self, *args, **options):
+        """
+        Normalizes the vault ID in the parameters.
+        """
+
+        vault_id = self.parse_params(*args, **options)
+        (vault_name, container_id) = self.split_id(vault_id)
+        return self.update_params(vault_name, container_id, *args, **options)
+
+    def parse_params(self, *args, **options):
+        """
+        Extracts the vault name and container ID in the parameters.
+        """
+
+        # get vault name and container ID from parameters
+        vault_name = args[0]
+        if type(vault_name) is tuple:
+            vault_name = vault_name[0]
+        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+
+        return self.merge_id(vault_name, container_id)
+
+    def update_params(self, new_vault_name, new_container_id, *args, **options):
+        """
+        Stores vault name and container ID back into the parameters.
+        """
+
+        args_list = list(args)
+        args_list[0] = new_vault_name
+        args = tuple(args_list)
+
+        options['container_id'] = new_container_id
+
+        return (args, options)
+
     def get_kra_id(self, id):
         """
         Generates a client key ID to store/retrieve data in KRA.
@@ -363,10 +438,14 @@ class vault_add(LDAPCreate):
 
     msg_summary = _('Added vault "%(value)s"')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_add, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def forward(self, *args, **options):
 
         vault_name = args[0]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         vault_type = options.get('ipavaulttype')
         data = options.get('data')
@@ -549,7 +628,7 @@ class vault_add(LDAPCreate):
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
 
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         # set owner
         principal = getattr(context, 'principal')
@@ -576,7 +655,7 @@ class vault_add(LDAPCreate):
 
     def handle_not_found(self, *args, **options):
 
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         raise errors.NotFound(
             reason=self.obj.parent_not_found_msg % {
@@ -598,6 +677,10 @@ class vault_del(LDAPDelete):
 
     msg_summary = _('Deleted vault "%(value)s"')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_del, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, dn, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -638,10 +721,16 @@ class vault_find(LDAPSearch):
         '%(count)d vault matched', '%(count)d vaults matched', 0
     )
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_find, self).params_2_args_options(**params)
+        container_id = self.obj.parse_params(*args, **options)
+        container_id = self.api.Object.vaultcontainer.normalize_id(container_id)
+        return self.obj.update_params(None, container_id, *args, **options)
+
     def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, **options):
         assert isinstance(base_dn, DN)
 
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
         (name, parent_id) = self.api.Object.vaultcontainer.split_id(container_id)
         base_dn = self.api.Object.vaultcontainer.get_dn(name, parent_id=parent_id)
 
@@ -657,7 +746,7 @@ class vault_find(LDAPSearch):
 
     def handle_not_found(self, *args, **options):
 
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         # vault container is user's private container, ignore
         if container_id == self.api.Object.vaultcontainer.get_private_id():
@@ -684,6 +773,10 @@ class vault_mod(LDAPUpdate):
 
     msg_summary = _('Modified vault "%(value)s"')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_mod, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -703,6 +796,10 @@ class vault_show(LDAPRetrieve):
         ),
     )
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_show, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -804,10 +901,14 @@ class vault_archive(LDAPRetrieve):
 
     msg_summary = _('Archived data into vault "%(value)s"')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_archive, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def forward(self, *args, **options):
 
         vault_name = args[0]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         vault_type = 'standard'
         salt = None
@@ -1118,10 +1219,14 @@ class vault_retrieve(LDAPRetrieve):
 
     msg_summary = _('Retrieved data from vault "%(value)s"')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_retrieve, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def forward(self, *args, **options):
 
         vault_name = args[0]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         vault_type = 'standard'
         salt = None
@@ -1396,6 +1501,10 @@ class vault_add_owner(LDAPAddMember):
     member_attributes = ['owner']
     member_count_out = ('%i owner added.', '%i owners added.')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_add_owner, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -1418,6 +1527,10 @@ class vault_remove_owner(LDAPRemoveMember):
     member_attributes = ['owner']
     member_count_out = ('%i owner removed.', '%i owners removed.')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_remove_owner, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -1437,6 +1550,10 @@ class vault_add_member(LDAPAddMember):
         ),
     )
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_add_member, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -1456,6 +1573,10 @@ class vault_remove_member(LDAPRemoveMember):
         ),
     )
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vault_remove_member, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
diff --git a/ipalib/plugins/vaultcontainer.py b/ipalib/plugins/vaultcontainer.py
index 27cb6fff3479335943bae59340c9afc773dfc004..5e8ee353ba2a625751fbfa9867366d98bdd5aea3 100644
--- a/ipalib/plugins/vaultcontainer.py
+++ b/ipalib/plugins/vaultcontainer.py
@@ -40,7 +40,10 @@ EXAMPLES:
    ipa vaultcontainer-find
 """) + _("""
  List top-level vault containers:
-   ipa vaultcontainer-find --parent-id /
+   ipa vaultcontainer-find /
+""") + _("""
+ List shared vault containers:
+   ipa vaultcontainer-find /shared
 """) + _("""
  Add a vault container:
    ipa vaultcontainer-add MyContainer
@@ -104,8 +107,8 @@ class vaultcontainer(LDAPObject):
             cli_name='container_name',
             label=_('Container name'),
             primary_key=True,
-            pattern='^[a-zA-Z0-9_.-]+$',
-            pattern_errmsg='may only include letters, numbers, _, ., and -',
+            pattern='^[a-zA-Z0-9_.-/]+$',
+            pattern_errmsg='may only include letters, numbers, _, ., -, and /',
             maxlength=255,
         ),
         Str('container_id?',
@@ -128,7 +131,7 @@ class vaultcontainer(LDAPObject):
 
         # get container ID from parameters
         name = keys[-1]
-        parent_id = self.normalize_id(options.get('parent_id'))
+        parent_id = self.absolute_id(options.get('parent_id'))
 
         container_id = parent_id
         if name:
@@ -176,14 +179,21 @@ class vaultcontainer(LDAPObject):
         Normalizes container ID.
         """
 
+        # make sure ID ends with slash
+        if id and not id.endswith(u'/'):
+            return id + u'/'
+
+        return id
+
+    def absolute_id(self, id):
+        """
+        Generate absolute container ID.
+        """
+
         # if ID is empty, return user's private container ID
         if not id:
             return self.get_private_id()
 
-        # make sure ID ends with slash
-        if not id.endswith(u'/'):
-            id += u'/'
-
         # if it's an absolute ID, do nothing
         if id.startswith(u'/'):
             return id
@@ -203,8 +213,68 @@ class vaultcontainer(LDAPObject):
         # split ID into parent ID, container name, and empty string
         parts = id.rsplit(u'/', 2)
 
-        # return container name and parent ID
-        return (parts[1], parts[0] + u'/')
+        if len(parts) == 3:
+            container_name = parts[1]
+            parent_id = u'%s/' % parts[0]
+
+        elif len(parts) == 2:
+            container_name = parts[0]
+            parent_id = None
+
+        if not container_name:
+            container_name = None
+
+        return (container_name, parent_id)
+
+    def merge_id(self, container_name, parent_id):
+        """
+        Merges a container name and a parent ID into a container ID.
+        """
+
+        if not container_name:
+            id = parent_id
+
+        elif container_name.startswith('/') or not parent_id:
+            id = container_name
+
+        else:
+            id = parent_id + container_name
+
+        return self.normalize_id(id)
+
+    def normalize_params(self, *args, **options):
+        """
+        Normalizes the container ID in the parameters.
+        """
+
+        container_id = self.parse_params(*args, **options)
+        (container_name, parent_id) = self.split_id(container_id)
+        return self.update_params(container_name, parent_id, *args, **options)
+
+    def parse_params(self, *args, **options):
+        """
+        Extracts the container name and parent ID in the parameters.
+        """
+
+        container_name = args[0]
+        if type(container_name) is tuple:
+            container_name = container_name[0]
+        parent_id = self.normalize_id(options.get('parent_id'))
+
+        return self.merge_id(container_name, parent_id)
+
+    def update_params(self, new_container_name, new_parent_id, *args, **options):
+        """
+        Stores container name and parent ID back into the parameters.
+        """
+
+        args_list = list(args)
+        args_list[0] = new_container_name
+        args = tuple(args_list)
+
+        options['parent_id'] = new_parent_id
+
+        return (args, options)
 
     def create_entry(self, dn, owner=None):
         """
@@ -249,10 +319,14 @@ class vaultcontainer_add(LDAPCreate):
 
     msg_summary = _('Added vault container "%(value)s"')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_add, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
 
-        parent_id = self.obj.normalize_id(options.get('parent_id'))
+        parent_id = self.obj.absolute_id(options.get('parent_id'))
 
         # set owner
         principal = getattr(context, 'principal')
@@ -279,7 +353,7 @@ class vaultcontainer_add(LDAPCreate):
 
     def handle_not_found(self, *args, **options):
 
-        parent_id = self.obj.normalize_id(options.get('parent_id'))
+        parent_id = self.obj.absolute_id(options.get('parent_id'))
 
         raise errors.NotFound(
             reason=self.obj.parent_not_found_msg % {
@@ -306,6 +380,10 @@ class vaultcontainer_del(LDAPDelete):
 
     msg_summary = _('Deleted vault container "%(value)s"')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_del, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def pre_callback(self, ldap, dn, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -335,10 +413,16 @@ class vaultcontainer_find(LDAPSearch):
         '%(count)d vault container matched', '%(count)d vault containers matched', 0
     )
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_find, self).params_2_args_options(**params)
+
+        parent_id = self.obj.parse_params(*args, **options)
+        return self.obj.update_params(None, parent_id, *args, **options)
+
     def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, **options):
         assert isinstance(base_dn, DN)
 
-        parent_id = self.obj.normalize_id(options.get('parent_id'))
+        parent_id = self.obj.absolute_id(options.get('parent_id'))
         (name, grandparent_id) = self.obj.split_id(parent_id)
         base_dn = self.obj.get_dn(name, parent_id=grandparent_id)
 
@@ -353,7 +437,7 @@ class vaultcontainer_find(LDAPSearch):
 
     def handle_not_found(self, *args, **options):
 
-        parent_id = self.obj.normalize_id(options.get('parent_id'))
+        parent_id = self.obj.absolute_id(options.get('parent_id'))
 
         # parent is user's private container, ignore
         if parent_id == self.obj.get_private_id():
@@ -381,6 +465,10 @@ class vaultcontainer_mod(LDAPUpdate):
 
     msg_summary = _('Modified vault container "%(value)s"')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_mod, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -400,6 +488,10 @@ class vaultcontainer_show(LDAPRetrieve):
         ),
     )
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_show, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -422,6 +514,10 @@ class vaultcontainer_add_owner(LDAPAddMember):
     member_attributes = ['owner']
     member_count_out = ('%i owner added.', '%i owners added.')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_add_owner, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -444,6 +540,10 @@ class vaultcontainer_remove_owner(LDAPRemoveMember):
     member_attributes = ['owner']
     member_count_out = ('%i owner removed.', '%i owners removed.')
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_remove_owner, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -463,6 +563,10 @@ class vaultcontainer_add_member(LDAPAddMember):
         ),
     )
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_add_member, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
@@ -475,7 +579,6 @@ class vaultcontainer_add_member(LDAPAddMember):
 class vaultcontainer_remove_member(LDAPRemoveMember):
     __doc__ = _('Remove members from a vault container.')
 
-
     takes_options = LDAPRemoveMember.takes_options + (
         Str('parent_id?',
             cli_name='parent_id',
@@ -483,6 +586,10 @@ class vaultcontainer_remove_member(LDAPRemoveMember):
         ),
     )
 
+    def params_2_args_options(self, **params):
+        (args, options) = super(vaultcontainer_remove_member, self).params_2_args_options(**params)
+        return self.obj.normalize_params(*args, **options)
+
     def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
 
diff --git a/ipalib/plugins/vaultsecret.py b/ipalib/plugins/vaultsecret.py
index 7f44f0816b571dac718fa02edfd187fe8666565e..de15b014ca285387c7a1730fca3e110664a3ecf2 100644
--- a/ipalib/plugins/vaultsecret.py
+++ b/ipalib/plugins/vaultsecret.py
@@ -147,7 +147,7 @@ class vaultsecret_add(LDAPRetrieve):
 
         vault_name = args[0]
         secret_name = args[1]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         vault_type = 'standard'
         salt = None
@@ -362,7 +362,7 @@ class vaultsecret_del(LDAPRetrieve):
 
         vault_name = args[0]
         secret_name = args[1]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         vault_type = 'standard'
         salt = None
@@ -539,7 +539,7 @@ class vaultsecret_find(LDAPSearch):
     def forward(self, *args, **options):
 
         vault_name = args[0]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         vault_type = 'standard'
         salt = None
@@ -716,7 +716,7 @@ class vaultsecret_mod(LDAPRetrieve):
 
         vault_name = args[0]
         secret_name = args[1]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         vault_type = 'standard'
         salt = None
@@ -946,7 +946,7 @@ class vaultsecret_show(LDAPRetrieve):
 
         vault_name = args[0]
         secret_name = args[1]
-        container_id = self.api.Object.vaultcontainer.normalize_id(options.get('container_id'))
+        container_id = self.api.Object.vaultcontainer.absolute_id(options.get('container_id'))
 
         vault_type = 'standard'
         salt = None
diff --git a/ipatests/test_xmlrpc/test_vault_plugin.py b/ipatests/test_xmlrpc/test_vault_plugin.py
index f3a280b40d5b6972e8755f63d46013cadaa68334..98e0b543d280d5bb36d5f9aae5c925019e79e962 100644
--- a/ipatests/test_xmlrpc/test_vault_plugin.py
+++ b/ipatests/test_xmlrpc/test_vault_plugin.py
@@ -34,6 +34,8 @@ asymmetric_vault = u'asymmetric_vault'
 escrowed_symmetric_vault = u'escrowed_symmetric_vault'
 escrowed_asymmetric_vault = u'escrowed_asymmetric_vault'
 
+shared_test_vault = u'/shared/%s' % test_vault
+
 password = u'password'
 
 public_key = """
@@ -158,6 +160,7 @@ class test_vault_plugin(Declarative):
         ('vault_del', [asymmetric_vault], {'continue': True}),
         ('vault_del', [escrowed_symmetric_vault], {'continue': True}),
         ('vault_del', [escrowed_asymmetric_vault], {'continue': True}),
+        ('vault_del', [shared_test_vault], {'continue': True}),
     ]
 
     tests = [
@@ -621,4 +624,152 @@ class test_vault_plugin(Declarative):
             },
         },
 
+        {
+            'desc': 'Create test vault with absolute ID',
+            'command': (
+                'vault_add',
+                [shared_test_vault],
+                {},
+            ),
+            'expected': {
+                'value': test_vault,
+                'summary': u'Added vault "%s"' % test_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_vault, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [test_vault],
+                    'vault_id': shared_test_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Find test vaults with absolute ID',
+            'command': (
+                'vault_find',
+                [u'/shared/'],
+                {},
+            ),
+            'expected': {
+                'count': 1,
+                'truncated': False,
+                'summary': u'1 vault matched',
+                'result': [
+                    {
+                        'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_vault, api.env.basedn),
+                        'cn': [test_vault],
+                        'vault_id': shared_test_vault,
+                        'ipavaulttype': [u'standard'],
+                    },
+                ],
+            },
+        },
+
+        {
+            'desc': 'Show test vault with absolute ID',
+            'command': (
+                'vault_show',
+                [shared_test_vault],
+                {},
+            ),
+            'expected': {
+                'value': test_vault,
+                'summary': None,
+                'result': {
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_vault, api.env.basedn),
+                    'cn': [test_vault],
+                    'vault_id': shared_test_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Modify test vault with absolute ID',
+            'command': (
+                'vault_mod',
+                [shared_test_vault],
+                {
+                    'description': u'Test vault',
+                },
+            ),
+            'expected': {
+                'value': test_vault,
+                'summary': u'Modified vault "%s"' % test_vault,
+                'result': {
+                    'cn': [test_vault],
+                    'vault_id': shared_test_vault,
+                    'description': [u'Test vault'],
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive binary data with absolute ID',
+            'command': (
+                'vault_archive',
+                [shared_test_vault],
+                {
+                    'data': binary_data,
+                },
+            ),
+            'expected': {
+                'value': test_vault,
+                'summary': u'Archived data into vault "%s"' % test_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_vault, api.env.basedn),
+                    'cn': [test_vault],
+                    'vault_id': shared_test_vault,
+                    'description': [u'Test vault'],
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Retrieve binary data with absolute ID',
+            'command': (
+                'vault_retrieve',
+                [shared_test_vault],
+                {},
+            ),
+            'expected': {
+                'value': test_vault,
+                'summary': u'Retrieved data from vault "%s"' % test_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_vault, api.env.basedn),
+                    'cn': [test_vault],
+                    'vault_id': shared_test_vault,
+                    'description': [u'Test vault'],
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete test vault with absolute ID',
+            'command': (
+                'vault_del',
+                [shared_test_vault],
+                {},
+            ),
+            'expected': {
+                'value': [test_vault],
+                'summary': u'Deleted vault "%s"' % test_vault,
+                'result': {
+                    'failed': (),
+                },
+            },
+        },
+
     ]
diff --git a/ipatests/test_xmlrpc/test_vaultcontainer_plugin.py b/ipatests/test_xmlrpc/test_vaultcontainer_plugin.py
index 22e13769df19b40cd39a144df662bae8bbf53d9e..ca96bff4dcae8c62bc44245ba82c7bf4165754e5 100644
--- a/ipatests/test_xmlrpc/test_vaultcontainer_plugin.py
+++ b/ipatests/test_xmlrpc/test_vaultcontainer_plugin.py
@@ -24,9 +24,10 @@ Test the `ipalib/plugins/vaultcontainer.py` module.
 from ipalib import api, errors
 from xmlrpc_test import Declarative
 
-private_container = u'private_container'
-shared_container = u'shared_container'
-service_container = u'service_container'
+test_container = u'test_container'
+private_container = test_container
+shared_test_container = u'/shared/%s' % test_container
+service_test_container = u'/services/%s' % test_container
 
 base_container = u'base_container'
 child_container = u'child_container'
@@ -36,8 +37,8 @@ class test_vaultcontainer_plugin(Declarative):
 
     cleanup_commands = [
         ('vaultcontainer_del', [private_container], {'continue': True}),
-        ('vaultcontainer_del', [shared_container], {'parent_id': u'/shared/', 'continue': True}),
-        ('vaultcontainer_del', [service_container], {'parent_id': u'/services/', 'continue': True}),
+        ('vaultcontainer_del', [shared_test_container], {'continue': True}),
+        ('vaultcontainer_del', [service_test_container], {'continue': True}),
         ('vaultcontainer_del', [base_container], {'force': True, 'continue': True}),
     ]
 
@@ -200,19 +201,17 @@ class test_vaultcontainer_plugin(Declarative):
             'desc': 'Create shared container',
             'command': (
                 'vaultcontainer_add',
-                [shared_container],
-                {
-                    'parent_id': u'/shared/',
-                },
+                [shared_test_container],
+                {},
             ),
             'expected': {
-                'value': shared_container,
-                'summary': 'Added vault container "%s"' % shared_container,
+                'value': test_container,
+                'summary': 'Added vault container "%s"' % test_container,
                 'result': {
-                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (shared_container, api.env.basedn),
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_container, api.env.basedn),
                     'objectclass': (u'ipaVaultContainer', u'top'),
-                    'cn': [shared_container],
-                    'container_id': u'/shared/%s/' % shared_container,
+                    'cn': [test_container],
+                    'container_id': u'/shared/%s/' % test_container,
                     'owner_user': [u'admin'],
                 },
             },
@@ -222,10 +221,8 @@ class test_vaultcontainer_plugin(Declarative):
             'desc': 'Find shared containers',
             'command': (
                 'vaultcontainer_find',
-                [],
-                {
-                    'parent_id': u'/shared/',
-                },
+                [u'/shared/'],
+                { },
             ),
             'expected': {
                 'count': 1,
@@ -233,9 +230,9 @@ class test_vaultcontainer_plugin(Declarative):
                 'summary': u'1 vault container matched',
                 'result': [
                     {
-                        'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (shared_container, api.env.basedn),
-                        'cn': [shared_container],
-                        'container_id': u'/shared/%s/' % shared_container,
+                        'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_container, api.env.basedn),
+                        'cn': [test_container],
+                        'container_id': u'/shared/%s/' % test_container,
                     },
                 ],
             },
@@ -245,18 +242,16 @@ class test_vaultcontainer_plugin(Declarative):
             'desc': 'Show shared container',
             'command': (
                 'vaultcontainer_show',
-                [shared_container],
-                {
-                    'parent_id': u'/shared/',
-                },
+                [shared_test_container],
+                {},
             ),
             'expected': {
-                'value': shared_container,
+                'value': test_container,
                 'summary': None,
                 'result': {
-                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (shared_container, api.env.basedn),
-                    'cn': [shared_container],
-                    'container_id': u'/shared/%s/' % shared_container,
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_container, api.env.basedn),
+                    'cn': [test_container],
+                    'container_id': u'/shared/%s/' % test_container,
                     'owner_user': [u'admin'],
                 },
             },
@@ -266,18 +261,17 @@ class test_vaultcontainer_plugin(Declarative):
             'desc': 'Modify shared container',
             'command': (
                 'vaultcontainer_mod',
-                [shared_container],
+                [shared_test_container],
                 {
-                    'parent_id': u'/shared/',
                     'description': u'shared container',
                 },
             ),
             'expected': {
-                'value': shared_container,
-                'summary': 'Modified vault container "%s"' % shared_container,
+                'value': test_container,
+                'summary': 'Modified vault container "%s"' % test_container,
                 'result': {
-                    'cn': [shared_container],
-                    'container_id': u'/shared/%s/' % shared_container,
+                    'cn': [test_container],
+                    'container_id': u'/shared/%s/' % test_container,
                     'description': [u'shared container'],
                     'owner_user': [u'admin'],
                 },
@@ -288,14 +282,12 @@ class test_vaultcontainer_plugin(Declarative):
             'desc': 'Delete shared container',
             'command': (
                 'vaultcontainer_del',
-                [shared_container],
-                {
-                    'parent_id': u'/shared/',
-                },
+                [shared_test_container],
+                {},
             ),
             'expected': {
-                'value': [shared_container],
-                'summary': u'Deleted vault container "%s"' % shared_container,
+                'value': [test_container],
+                'summary': u'Deleted vault container "%s"' % test_container,
                 'result': {
                     'failed': (),
                 },
@@ -306,19 +298,17 @@ class test_vaultcontainer_plugin(Declarative):
             'desc': 'Create service container',
             'command': (
                 'vaultcontainer_add',
-                [service_container],
-                {
-                    'parent_id': u'/services/',
-                },
+                [service_test_container],
+                {},
             ),
             'expected': {
-                'value': service_container,
-                'summary': 'Added vault container "%s"' % service_container,
+                'value': test_container,
+                'summary': 'Added vault container "%s"' % test_container,
                 'result': {
-                    'dn': u'cn=%s,cn=services,cn=vaults,%s' % (service_container, api.env.basedn),
+                    'dn': u'cn=%s,cn=services,cn=vaults,%s' % (test_container, api.env.basedn),
                     'objectclass': (u'ipaVaultContainer', u'top'),
-                    'cn': [service_container],
-                    'container_id': u'/services/%s/' % service_container,
+                    'cn': [test_container],
+                    'container_id': u'/services/%s/' % test_container,
                     'owner_user': [u'admin'],
                 },
             },
diff --git a/ipatests/test_xmlrpc/test_vaultsecret_plugin.py b/ipatests/test_xmlrpc/test_vaultsecret_plugin.py
index cbfd231633e7c3c000e57d52d85b83f44f71df3c..d2a4e92507fbabbcc356dc889738f151521e896c 100644
--- a/ipatests/test_xmlrpc/test_vaultsecret_plugin.py
+++ b/ipatests/test_xmlrpc/test_vaultsecret_plugin.py
@@ -25,6 +25,7 @@ from ipalib import api, errors
 from xmlrpc_test import Declarative, fuzzy_string
 
 test_vault = u'test_vault'
+shared_test_vault = u'/shared/%s' % test_vault
 test_vaultsecret = u'test_vaultsecret'
 binary_data = '\x01\x02\x03\x04'
 text_data = u'secret'
@@ -33,6 +34,7 @@ class test_vaultsecret_plugin(Declarative):
 
     cleanup_commands = [
         ('vault_del', [test_vault], {'continue': True}),
+        ('vault_del', [shared_test_vault], {'continue': True}),
     ]
 
     tests = [
@@ -208,4 +210,117 @@ class test_vaultsecret_plugin(Declarative):
             },
         },
 
+        {
+            'desc': 'Create shared test vault',
+            'command': (
+                'vault_add',
+                [shared_test_vault],
+                {},
+            ),
+            'expected': {
+                'value': test_vault,
+                'summary': 'Added vault "%s"' % test_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=shared,cn=vaults,%s' % (test_vault, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [test_vault],
+                    'vault_id': u'/shared/%s' % test_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Create shared test vault secret with binary data',
+            'command': (
+                'vaultsecret_add',
+                [shared_test_vault, test_vaultsecret],
+                {
+                    'data': binary_data,
+                },
+            ),
+            'expected': {
+                'value': test_vaultsecret,
+                'summary': 'Added vault secret "%s"' % test_vaultsecret,
+                'result': {
+                    'secret_name': test_vaultsecret,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Find shared vault secrets',
+            'command': (
+                'vaultsecret_find',
+                [shared_test_vault],
+                {},
+            ),
+            'expected': {
+                'count': 1,
+                'truncated': False,
+                'summary': u'1 vault secret matched',
+                'result': [
+                    {
+                        'secret_name': test_vaultsecret,
+                        'data': binary_data,
+                    },
+                ],
+            },
+        },
+
+        {
+            'desc': 'Retrieve shared test vault secret',
+            'command': (
+                'vaultsecret_show',
+                [shared_test_vault, test_vaultsecret],
+                {},
+            ),
+            'expected': {
+                'value': test_vaultsecret,
+                'summary': None,
+                'result': {
+                    'secret_name': test_vaultsecret,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Modify shared test vault secret',
+            'command': (
+                'vaultsecret_mod',
+                [shared_test_vault, test_vaultsecret],
+                {
+                    'description': u'Test vault secret',
+                },
+            ),
+            'expected': {
+                'value': test_vaultsecret,
+                'summary': u'Modified vault secret "%s"' % test_vaultsecret,
+                'result': {
+                    'secret_name': test_vaultsecret,
+                    'description': u'Test vault secret',
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete shared test vault secret',
+            'command': (
+                'vaultsecret_del',
+                [shared_test_vault, test_vaultsecret],
+                {},
+            ),
+            'expected': {
+                'value': test_vaultsecret,
+                'summary': u'Deleted vault secret "%s"' % test_vaultsecret,
+                'result': {
+                    'failed': (),
+                },
+            },
+        },
+
     ]
-- 
1.9.0

-------------- next part --------------
>From 300ec72c4201e8effdda1ec48853b6c8f4f83f1b Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at redhat.com>
Date: Mon, 16 Mar 2015 05:08:56 -0400
Subject: [PATCH] Vault copy functionality.

The vault and vaultsecret plugins have been modified to provide a
functionality to copy data from another vault or vaultsecret. New
test cases have been added to verify this functionality.

https://fedorahosted.org/freeipa/ticket/3872
---
 API.txt                                            |  32 +-
 ipalib/plugins/vault.py                            | 274 ++++++-
 ipalib/plugins/vaultsecret.py                      | 441 ++++++++---
 ipatests/test_xmlrpc/test_vault_plugin.py          | 836 ++++++++++++++++++++-
 ipatests/test_xmlrpc/test_vaultcontainer_plugin.py |   1 +
 ipatests/test_xmlrpc/test_vaultsecret_plugin.py    | 401 ++++++++--
 6 files changed, 1780 insertions(+), 205 deletions(-)

diff --git a/API.txt b/API.txt
index 3f78493d986a292538dcca68133824bfb591149b..db488517c963244a7bfcdfbb86715a71a4cc3b73 100644
--- a/API.txt
+++ b/API.txt
@@ -4514,7 +4514,7 @@ output: Output('result', <type 'bool'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vault_add
-args: 1,20,3
+args: 1,26,3
 arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
@@ -4533,6 +4533,12 @@ option: Str('password_file?', cli_name='password_file')
 option: Str('public_key_file?', cli_name='public_key_file')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('source_password?', cli_name='source_password')
+option: Str('source_password_file?', cli_name='source_password_file')
+option: Bytes('source_private_key?', cli_name='source_private_key')
+option: Str('source_private_key_file?', cli_name='source_private_key_file')
+option: Str('source_secret_id?', cli_name='source_secret_id')
+option: Str('source_vault_id?', cli_name='source_vault_id')
 option: Str('text?', cli_name='text')
 option: Str('vault_id', attribute=False, cli_name='vault_id', multivalue=False, required=False)
 option: Str('version?', exclude='webui')
@@ -4566,7 +4572,7 @@ output: Output('completed', <type 'int'>, None)
 output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: vault_archive
-args: 1,15,3
+args: 1,21,3
 arg: Str('cn', attribute=True, cli_name='vault_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('container_id?', cli_name='container_id')
@@ -4580,6 +4586,12 @@ option: Str('password_file?', cli_name='password_file')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: Flag('rights', autofill=True, default=False)
 option: Str('session_key?', cli_name='session_key')
+option: Str('source_password?', cli_name='source_password')
+option: Str('source_password_file?', cli_name='source_password_file')
+option: Bytes('source_private_key?', cli_name='source_private_key')
+option: Str('source_private_key_file?', cli_name='source_private_key_file')
+option: Str('source_secret_id?', cli_name='source_secret_id')
+option: Str('source_vault_id?', cli_name='source_vault_id')
 option: Str('text?', cli_name='text')
 option: Str('vault_data?', cli_name='vault_data')
 option: Str('version?', exclude='webui')
@@ -4828,7 +4840,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: vaultsecret_add
-args: 2,12,3
+args: 2,18,3
 arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 arg: Str('secret_name', attribute=True, cli_name='secret', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
@@ -4841,6 +4853,12 @@ option: Str('password_file?', cli_name='password_file')
 option: Bytes('private_key?', cli_name='private_key')
 option: Str('private_key_file?', cli_name='private_key_file')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('source_password?', cli_name='source_password')
+option: Str('source_password_file?', cli_name='source_password_file')
+option: Bytes('source_private_key?', cli_name='source_private_key')
+option: Str('source_private_key_file?', cli_name='source_private_key_file')
+option: Str('source_secret_id?', cli_name='source_secret_id')
+option: Str('source_vault_id?', cli_name='source_vault_id')
 option: Str('text?', cli_name='text')
 option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
@@ -4882,7 +4900,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: vaultsecret_mod
-args: 2,12,3
+args: 2,18,3
 arg: Str('vaultcn', cli_name='vault', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-/]+$', primary_key=True, query=True, required=True)
 arg: Str('secret_name', attribute=True, cli_name='secret', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
@@ -4895,6 +4913,12 @@ option: Str('password_file?', cli_name='password_file')
 option: Bytes('private_key?', cli_name='private_key')
 option: Str('private_key_file?', cli_name='private_key_file')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('source_password?', cli_name='source_password')
+option: Str('source_password_file?', cli_name='source_password_file')
+option: Bytes('source_private_key?', cli_name='source_private_key')
+option: Str('source_private_key_file?', cli_name='source_private_key_file')
+option: Str('source_secret_id?', cli_name='source_secret_id')
+option: Str('source_vault_id?', cli_name='source_vault_id')
 option: Str('text?', cli_name='text')
 option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
index 38693d0710e000695cae21fb4db5dfb4c85b5c74..19309037bd670db822690dd66d03839e575ad500 100644
--- a/ipalib/plugins/vault.py
+++ b/ipalib/plugins/vault.py
@@ -434,6 +434,30 @@ class vault_add(LDAPCreate):
             cli_name='escrow_public_key_file',
             doc=_('File containing the escrow public key'),
         ),
+        Str('source_vault_id?',
+            cli_name='source_vault_id',
+            doc=_('Source vault ID'),
+        ),
+        Str('source_secret_id?',
+            cli_name='source_secret_id',
+            doc=_('Source secret ID'),
+        ),
+        Str('source_password?',
+            cli_name='source_password',
+            doc=_('Source vault password'),
+        ),
+        Str('source_password_file?',
+            cli_name='source_password_file',
+            doc=_('File containing the source vault password'),
+        ),
+        Bytes('source_private_key?',
+            cli_name='source_private_key',
+            doc=_('Source vault private key'),
+        ),
+        Str('source_private_key_file?',
+            cli_name='source_private_key_file',
+            doc=_('File containing the source vault private key'),
+        ),
     )
 
     msg_summary = _('Added vault "%(value)s"')
@@ -457,6 +481,12 @@ class vault_add(LDAPCreate):
         public_key_file = options.get('public_key_file')
         escrow_public_key = options.get('ipaescrowpublickey')
         escrow_public_key_file = options.get('escrow_public_key_file')
+        source_vault_id = options.get('source_vault_id')
+        source_secret_id = options.get('source_secret_id')
+        source_password = options.get('source_password')
+        source_password_file = options.get('source_password_file')
+        source_private_key = options.get('source_private_key')
+        source_private_key_file = options.get('source_private_key_file')
 
         # don't send these parameters to server
         if 'data' in options:
@@ -473,24 +503,127 @@ class vault_add(LDAPCreate):
             del options['public_key_file']
         if 'escrow_public_key_file' in options:
             del options['escrow_public_key_file']
+        if 'source_vault_id' in options:
+            del options['source_vault_id']
+        if 'source_secret_id' in options:
+            del options['source_secret_id']
+        if 'source_password' in options:
+            del options['source_password']
+        if 'source_password_file' in options:
+            del options['source_password_file']
+        if 'source_private_key' in options:
+            del options['source_private_key']
+        if 'source_private_key_file' in options:
+            del options['source_private_key_file']
 
         # get data
         if data:
-            if text or input_file:
+            if text or input_file or source_vault_id:
                 raise errors.MutuallyExclusiveError(
                     reason=_('Input data specified multiple times'))
 
         elif text:
-            if input_file:
+            if input_file or source_vault_id:
                 raise errors.MutuallyExclusiveError(
                     reason=_('Input data specified multiple times'))
 
             data = text.encode('utf-8')
 
         elif input_file:
+            if source_vault_id:
+                raise errors.MutuallyExclusiveError(
+                    reason=_('Input data specified multiple times'))
+
             with open(input_file, 'rb') as f:
                 data = f.read()
 
+        elif source_vault_id:
+
+            source_response = self.api.Command.vault_show(source_vault_id)
+            source_result = source_response['result']
+
+            if source_result.has_key('ipavaulttype'):
+                source_vault_type = source_result['ipavaulttype'][0]
+
+            if source_vault_type == 'standard':
+
+                if source_password:
+                    raise errors.ValidationError(name='source_password',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_password_file:
+                    raise errors.ValidationError(name='source_password_file',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_private_key:
+                    raise errors.ValidationError(name='source_private_key',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_private_key_file:
+                    raise errors.ValidationError(name='source_private_key_file',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+            elif source_vault_type == 'symmetric':
+
+                if source_private_key:
+                    raise errors.ValidationError(name='source_private_key',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_private_key_file:
+                    raise errors.ValidationError(name='source_private_key_file',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                # get source vault password
+                if source_password:
+                    pass
+
+                elif source_password_file:
+                    with open(source_password_file) as f:
+                        source_password = unicode(f.read().rstrip('\n'))
+
+                else:
+                    source_password = unicode(getpass.getpass('Source password: '))
+
+            elif source_vault_type == 'asymmetric':
+
+                if source_password:
+                    raise errors.ValidationError(name='source_password',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_password_file:
+                    raise errors.ValidationError(name='source_password_file',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                # get source vault private key
+                if source_private_key:
+                    pass
+
+                elif source_private_key_file:
+                    with open(source_private_key_file, 'rb') as f:
+                        source_private_key = f.read()
+
+                else:
+                    raise errors.ValidationError(name='source_private_key',
+                        error=_('Missing source vault private key'))
+
+            else:
+                raise errors.ValidationError(name='source_vault_type',
+                    error=_('Invalid source vault type'))
+
+            source_response = self.api.Command.vault_retrieve(
+                source_vault_id,
+                password=source_password,
+                private_key=source_private_key)
+
+            if source_secret_id:
+                source_json_data = self.api.Object.vaultsecret.parse_response(source_response)
+                source_secrets = source_json_data['secrets']
+                source_secret = self.obj.Object.vaultsecret.find(source_secrets, source_secret_id)
+                data = base64.b64decode(source_secret['data'])
+
+            else:
+                data = source_response['result']['data']
+
         else:
             data = ''
 
@@ -897,6 +1030,30 @@ class vault_archive(LDAPRetrieve):
             cli_name='encryption_key',
             doc=_('Base-64 encoded encryption key'),
         ),
+        Str('source_vault_id?',
+            cli_name='source_vault_id',
+            doc=_('Source vault ID'),
+        ),
+        Str('source_secret_id?',
+            cli_name='source_secret_id',
+            doc=_('Source secret ID'),
+        ),
+        Str('source_password?',
+            cli_name='source_password',
+            doc=_('Source vault password'),
+        ),
+        Str('source_password_file?',
+            cli_name='source_password_file',
+            doc=_('File containing the source vault password'),
+        ),
+        Bytes('source_private_key?',
+            cli_name='source_private_key',
+            doc=_('Source vault private key'),
+        ),
+        Str('source_private_key_file?',
+            cli_name='source_private_key_file',
+            doc=_('File containing the source vault private key'),
+        ),
     )
 
     msg_summary = _('Archived data into vault "%(value)s"')
@@ -937,6 +1094,12 @@ class vault_archive(LDAPRetrieve):
         password = options.get('password')
         password_file = options.get('password_file')
         encryption_key = options.get('encryption_key')
+        source_vault_id = options.get('source_vault_id')
+        source_secret_id = options.get('source_secret_id')
+        source_password = options.get('source_password')
+        source_password_file = options.get('source_password_file')
+        source_private_key = options.get('source_private_key')
+        source_private_key_file = options.get('source_private_key_file')
 
         # don't send these parameters to server
         if 'data' in options:
@@ -951,24 +1114,127 @@ class vault_archive(LDAPRetrieve):
             del options['password_file']
         if 'encryption_key' in options:
             del options['encryption_key']
+        if 'source_vault_id' in options:
+            del options['source_vault_id']
+        if 'source_secret_id' in options:
+            del options['source_secret_id']
+        if 'source_password' in options:
+            del options['source_password']
+        if 'source_password_file' in options:
+            del options['source_password_file']
+        if 'source_private_key' in options:
+            del options['source_private_key']
+        if 'source_private_key_file' in options:
+            del options['source_private_key_file']
 
         # get data
         if data:
-            if text or input_file:
+            if text or input_file or source_vault_id:
                 raise errors.MutuallyExclusiveError(
                     reason=_('Input data specified multiple times'))
 
         elif text:
-            if input_file:
+            if input_file or source_vault_id:
                 raise errors.MutuallyExclusiveError(
                     reason=_('Input data specified multiple times'))
 
             data = text.encode('utf-8')
 
         elif input_file:
+            if source_vault_id:
+                raise errors.MutuallyExclusiveError(
+                    reason=_('Input data specified multiple times'))
+
             with open(input_file, 'rb') as f:
                 data = f.read()
 
+        elif source_vault_id:
+
+            source_response = self.api.Command.vault_show(source_vault_id)
+            source_result = source_response['result']
+
+            if source_result.has_key('ipavaulttype'):
+                source_vault_type = source_result['ipavaulttype'][0]
+
+            if source_vault_type == 'standard':
+
+                if source_password:
+                    raise errors.ValidationError(name='source_password',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_password_file:
+                    raise errors.ValidationError(name='source_password_file',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_private_key:
+                    raise errors.ValidationError(name='source_private_key',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_private_key_file:
+                    raise errors.ValidationError(name='source_private_key_file',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+            elif source_vault_type == 'symmetric':
+
+                if source_private_key:
+                    raise errors.ValidationError(name='source_private_key',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_private_key_file:
+                    raise errors.ValidationError(name='source_private_key_file',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                # get source vault password
+                if source_password:
+                    pass
+
+                elif source_password_file:
+                    with open(source_password_file) as f:
+                        source_password = unicode(f.read().rstrip('\n'))
+
+                else:
+                    source_password = unicode(getpass.getpass('Source password: '))
+
+            elif source_vault_type == 'asymmetric':
+
+                if source_password:
+                    raise errors.ValidationError(name='source_password',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                if source_password_file:
+                    raise errors.ValidationError(name='source_password_file',
+                        error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                # get source vault private key
+                if source_private_key:
+                    pass
+
+                elif source_private_key_file:
+                    with open(source_private_key_file, 'rb') as f:
+                        source_private_key = f.read()
+
+                else:
+                    raise errors.ValidationError(name='source_private_key',
+                        error=_('Missing source vault private key'))
+
+            else:
+                raise errors.ValidationError(name='source_vault_type',
+                    error=_('Invalid source vault type'))
+
+            source_response = self.api.Command.vault_retrieve(
+                source_vault_id,
+                password=source_password,
+                private_key=source_private_key)
+
+            if source_secret_id:
+                source_json_data = self.api.Object.vaultsecret.parse_response(source_response)
+                source_secrets = source_json_data['secrets']
+                source_secret = self.obj.Object.vaultsecret.find(source_secrets, source_secret_id)
+                data = base64.b64decode(source_secret['data'])
+
+            else:
+                data = source_response['result']['data']
+
         else:
             data = ''
 
diff --git a/ipalib/plugins/vaultsecret.py b/ipalib/plugins/vaultsecret.py
index de15b014ca285387c7a1730fca3e110664a3ecf2..02f035f2de8b614b5b4019e65cede984f12854b9 100644
--- a/ipalib/plugins/vaultsecret.py
+++ b/ipalib/plugins/vaultsecret.py
@@ -97,6 +97,32 @@ class vaultsecret(LDAPObject):
         ),
     )
 
+    def find(self, secrets, secret_name):
+        """
+        Finds a secret with the given name in a list of secrets.
+        Raises an exception if the secret is not found.
+        """
+
+        for secret in secrets:
+            if secret['secret_name'] == secret_name:
+                return secret
+
+        raise errors.NotFound(reason=_('%s: vault secret not found' % secret_name))
+
+    def parse_response(self, response):
+        """
+        Returns JSON data from vault retrieval response.
+        """
+
+        vault_data = response['result']['data']
+
+        if vault_data:
+            return json.loads(vault_data)
+
+        return {
+            'secrets': []
+        }
+
 
 @register()
 class vaultsecret_add(LDAPRetrieve):
@@ -139,6 +165,30 @@ class vaultsecret_add(LDAPRetrieve):
             cli_name='private_key_file',
             doc=_('File containing the vault private key'),
         ),
+        Str('source_secret_id?',
+            cli_name='source_secret_id',
+            doc=_('Source secret ID'),
+        ),
+        Str('source_vault_id?',
+            cli_name='source_vault_id',
+            doc=_('Source vault ID'),
+        ),
+        Str('source_password?',
+            cli_name='source_password',
+            doc=_('Source vault password'),
+        ),
+        Str('source_password_file?',
+            cli_name='source_password_file',
+            doc=_('File containing the source vault password'),
+        ),
+        Bytes('source_private_key?',
+            cli_name='source_private_key',
+            doc=_('Source vault private key'),
+        ),
+        Str('source_private_key_file?',
+            cli_name='source_private_key_file',
+            doc=_('File containing the source vault private key'),
+        ),
     )
 
     msg_summary = _('Added vault secret "%(value)s"')
@@ -170,6 +220,12 @@ class vaultsecret_add(LDAPRetrieve):
         password_file = options.get('password_file')
         private_key = options.get('private_key')
         private_key_file = options.get('private_key_file')
+        source_secret_id = options.get('source_secret_id')
+        source_vault_id = options.get('source_vault_id')
+        source_password = options.get('source_password')
+        source_password_file = options.get('source_password_file')
+        source_private_key = options.get('source_private_key')
+        source_private_key_file = options.get('source_private_key_file')
 
         # don't send these parameters to server
         if 'data' in options:
@@ -186,8 +242,18 @@ class vaultsecret_add(LDAPRetrieve):
             del options['private_key']
         if 'private_key_file' in options:
             del options['private_key_file']
-        if 'source_secret' in options:
-            del options['source_secret']
+        if 'source_secret_id' in options:
+            del options['source_secret_id']
+        if 'source_vault_id' in options:
+            del options['source_vault_id']
+        if 'source_password' in options:
+            del options['source_password']
+        if 'source_password_file' in options:
+            del options['source_password_file']
+        if 'source_private_key' in options:
+            del options['source_private_key']
+        if 'source_private_key_file' in options:
+            del options['source_private_key_file']
 
         # type-specific initialization
         if vault_type == 'standard':
@@ -239,7 +305,7 @@ class vaultsecret_add(LDAPRetrieve):
                 raise errors.ValidationError(name='password_file',
                     error=_('Invalid parameter for %s vault' % vault_type))
 
-            # get vault public key
+            # get vault private key
             if private_key:
                 pass
 
@@ -262,42 +328,130 @@ class vaultsecret_add(LDAPRetrieve):
             password=password,
             private_key=private_key)
 
-        vault_data = response['result']['data']
-
-        if not vault_data:
-            json_data = {
-                'secrets': []
-            }
-
-        else:
-            json_data = json.loads(vault_data)
+        json_data = self.obj.parse_response(response)
 
         secrets = json_data['secrets']
 
         # get data
         if data:
-            if text or input_file:
+            if text or input_file or source_secret_id:
                 raise errors.MutuallyExclusiveError(
                     reason=_('Input data specified multiple times'))
 
         elif text:
-            if input_file:
+            if input_file or source_secret_id :
                 raise errors.MutuallyExclusiveError(
                     reason=_('Input data specified multiple times'))
 
             data = text.encode('utf-8')
 
         elif input_file:
+            if source_secret_id :
+                raise errors.MutuallyExclusiveError(
+                    reason=_('Input data specified multiple times'))
+
             with open(input_file, 'rb') as f:
                 data = f.read()
 
+        elif source_secret_id:
+
+            if source_vault_id:
+
+                source_response = self.api.Command.vault_show(source_vault_id)
+                source_result = source_response['result']
+
+                if source_result.has_key('ipavaulttype'):
+                    source_vault_type = source_result['ipavaulttype'][0]
+
+                if source_vault_type == 'standard':
+
+                    if source_password:
+                        raise errors.ValidationError(name='source_password',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_password_file:
+                        raise errors.ValidationError(name='source_password_file',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_private_key:
+                        raise errors.ValidationError(name='source_private_key',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_private_key_file:
+                        raise errors.ValidationError(name='source_private_key_file',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                elif source_vault_type == 'symmetric':
+
+                    if source_private_key:
+                        raise errors.ValidationError(name='source_private_key',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_private_key_file:
+                        raise errors.ValidationError(name='source_private_key_file',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    # get source vault password
+                    if source_password:
+                        pass
+
+                    elif source_password_file:
+                        with open(source_password_file) as f:
+                            source_password = unicode(f.read().rstrip('\n'))
+
+                    else:
+                        source_password = unicode(getpass.getpass('Source password: '))
+
+                elif source_vault_type == 'asymmetric':
+
+                    if source_password:
+                        raise errors.ValidationError(name='source_password',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_password_file:
+                        raise errors.ValidationError(name='source_password_file',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    # get source vault private key
+                    if source_private_key:
+                        pass
+
+                    elif source_private_key_file:
+                        with open(source_private_key_file, 'rb') as f:
+                            source_private_key = f.read()
+
+                    else:
+                        raise errors.ValidationError(name='source_private_key',
+                            error=_('Missing source vault private key'))
+
+                else:
+                    raise errors.ValidationError(name='source_vault_type',
+                        error=_('Invalid source vault type'))
+
+                source_response = self.api.Command.vault_retrieve(
+                    source_vault_id,
+                    password=source_password,
+                    private_key=source_private_key)
+
+                source_json_data = self.obj.parse_response(source_response)
+
+                source_secrets = source_json_data['secrets']
+
+            else:
+                source_secrets = secrets
+
+            source_secret = self.obj.find(source_secrets, source_secret_id)
+            data = base64.b64decode(source_secret['data'])
+
         else:
             data = ''
 
         # add new secret
-        for secret in secrets:
-            if secret['secret_name'] == secret_name:
-                raise errors.DuplicateEntry(message=_('vault secret with name "%s" already exists' % secret_name))
+        try:
+            self.obj.find(secrets, secret_name)
+            raise errors.DuplicateEntry(message=_('vault secret with name "%s" already exists' % secret_name))
+        except errors.NotFound:
+            pass
 
         # store encoded data for storage
         secret = {
@@ -442,7 +596,7 @@ class vaultsecret_del(LDAPRetrieve):
                 raise errors.ValidationError(name='password_file',
                     error=_('Invalid parameter for %s vault' % vault_type))
 
-            # get vault public key
+            # get vault private key
             if private_key:
                 pass
 
@@ -465,27 +619,12 @@ class vaultsecret_del(LDAPRetrieve):
             password=password,
             private_key=private_key)
 
-        vault_data = response['result']['data']
-
-        if not vault_data:
-            json_data = {
-                'secrets': []
-            }
-
-        else:
-            json_data = json.loads(vault_data)
+        json_data = self.obj.parse_response(response)
 
         secrets = json_data['secrets']
 
         # find the secret
-        secret = None
-        for s in secrets:
-            if s['secret_name'] == secret_name:
-                secret = s
-                break
-
-        if not secret:
-            raise errors.NotFound(reason=_('%s: vault secret not found' % secret_name))
+        secret = self.obj.find(secrets, secret_name)
 
         # delete secret
         secrets.remove(secret)
@@ -619,7 +758,7 @@ class vaultsecret_find(LDAPSearch):
                 raise errors.ValidationError(name='password_file',
                     error=_('Invalid parameter for %s vault' % vault_type))
 
-            # get vault public key
+            # get vault private key
             if private_key:
                 pass
 
@@ -641,15 +780,7 @@ class vaultsecret_find(LDAPSearch):
             password=password,
             private_key=private_key)
 
-        vault_data = response['result']['data']
-
-        if not vault_data:
-            json_data = {
-                'secrets': []
-            }
-
-        else:
-            json_data = json.loads(vault_data)
+        json_data = self.obj.parse_response(response)
 
         secrets = json_data['secrets']
 
@@ -708,6 +839,30 @@ class vaultsecret_mod(LDAPRetrieve):
             cli_name='private_key_file',
             doc=_('File containing the vault private key'),
         ),
+        Str('source_secret_id?',
+            cli_name='source_secret_id',
+            doc=_('Source secret ID'),
+        ),
+        Str('source_vault_id?',
+            cli_name='source_vault_id',
+            doc=_('Source vault ID'),
+        ),
+        Str('source_password?',
+            cli_name='source_password',
+            doc=_('Source vault password'),
+        ),
+        Str('source_password_file?',
+            cli_name='source_password_file',
+            doc=_('File containing the source vault password'),
+        ),
+        Bytes('source_private_key?',
+            cli_name='source_private_key',
+            doc=_('Source vault private key'),
+        ),
+        Str('source_private_key_file?',
+            cli_name='source_private_key_file',
+            doc=_('File containing the source vault private key'),
+        ),
     )
 
     msg_summary = _('Modified vault secret "%(value)s"')
@@ -739,6 +894,12 @@ class vaultsecret_mod(LDAPRetrieve):
         password_file = options.get('password_file')
         private_key = options.get('private_key')
         private_key_file = options.get('private_key_file')
+        source_secret_id = options.get('source_secret_id')
+        source_vault_id = options.get('source_vault_id')
+        source_password = options.get('source_password')
+        source_password_file = options.get('source_password_file')
+        source_private_key = options.get('source_private_key')
+        source_private_key_file = options.get('source_private_key_file')
 
         # don't send these parameters to server
         if 'data' in options:
@@ -755,6 +916,18 @@ class vaultsecret_mod(LDAPRetrieve):
             del options['private_key']
         if 'private_key_file' in options:
             del options['private_key_file']
+        if 'source_secret_id' in options:
+            del options['source_secret_id']
+        if 'source_vault_id' in options:
+            del options['source_vault_id']
+        if 'source_password' in options:
+            del options['source_password']
+        if 'source_password_file' in options:
+            del options['source_password_file']
+        if 'source_private_key' in options:
+            del options['source_private_key']
+        if 'source_private_key_file' in options:
+            del options['source_private_key_file']
 
         # type-specific initialization
         if vault_type == 'standard':
@@ -806,7 +979,7 @@ class vaultsecret_mod(LDAPRetrieve):
                 raise errors.ValidationError(name='password_file',
                     error=_('Invalid parameter for %s vault' % vault_type))
 
-            # get vault public key
+            # get vault private key
             if private_key:
                 pass
 
@@ -822,26 +995,6 @@ class vaultsecret_mod(LDAPRetrieve):
             raise errors.ValidationError(name='vault_type',
                 error=_('Invalid vault type'))
 
-        # get data
-        if data:
-            if text or input_file:
-                raise errors.MutuallyExclusiveError(
-                    reason=_('Input data specified multiple times'))
-
-        elif text:
-            if input_file:
-                raise errors.MutuallyExclusiveError(
-                    reason=_('Input data specified multiple times'))
-
-            data = text.encode('utf-8')
-
-        elif input_file:
-            with open(input_file, 'rb') as f:
-                data = f.read()
-
-        else:
-            pass
-
         # retrieve secrets
         response = self.api.Command.vault_retrieve(
             vault_name,
@@ -849,27 +1002,126 @@ class vaultsecret_mod(LDAPRetrieve):
             password=password,
             private_key=private_key)
 
-        vault_data = response['result']['data']
-
-        if not vault_data:
-            json_data = {
-                'secrets': []
-            }
-
-        else:
-            json_data = json.loads(vault_data)
+        json_data = self.obj.parse_response(response)
 
         secrets = json_data['secrets']
 
         # find the secret
-        secret = None
-        for s in secrets:
-            if s['secret_name'] == secret_name:
-                secret = s
-                break
-
-        if not secret:
-            raise errors.NotFound(reason=_('%s: vault secret not found' % secret_name))
+        secret = self.obj.find(secrets, secret_name)
+
+        # get data
+        if data:
+            if text or input_file or source_secret_id:
+                raise errors.MutuallyExclusiveError(
+                    reason=_('Input data specified multiple times'))
+
+        elif text:
+            if input_file or source_secret_id:
+                raise errors.MutuallyExclusiveError(
+                    reason=_('Input data specified multiple times'))
+
+            data = text.encode('utf-8')
+
+        elif input_file:
+            if source_secret_id:
+                raise errors.MutuallyExclusiveError(
+                    reason=_('Input data specified multiple times'))
+
+            with open(input_file, 'rb') as f:
+                data = f.read()
+
+        elif source_secret_id:
+
+            if source_vault_id:
+
+                source_response = self.api.Command.vault_show(source_vault_id)
+                source_result = source_response['result']
+
+                if source_result.has_key('ipavaulttype'):
+                    source_vault_type = source_result['ipavaulttype'][0]
+
+                if source_vault_type == 'standard':
+
+                    if source_password:
+                        raise errors.ValidationError(name='source_password',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_password_file:
+                        raise errors.ValidationError(name='source_password_file',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_private_key:
+                        raise errors.ValidationError(name='source_private_key',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_private_key_file:
+                        raise errors.ValidationError(name='source_private_key_file',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                elif source_vault_type == 'symmetric':
+
+                    if source_private_key:
+                        raise errors.ValidationError(name='source_private_key',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_private_key_file:
+                        raise errors.ValidationError(name='source_private_key_file',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    # get source vault password
+                    if source_password:
+                        pass
+
+                    elif source_password_file:
+                        with open(source_password_file) as f:
+                            source_password = unicode(f.read().rstrip('\n'))
+
+                    else:
+                        source_password = unicode(getpass.getpass('Source password: '))
+
+                elif source_vault_type == 'asymmetric':
+
+                    if source_password:
+                        raise errors.ValidationError(name='source_password',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    if source_password_file:
+                        raise errors.ValidationError(name='source_password_file',
+                            error=_('Invalid parameter for %s vault' % source_vault_type))
+
+                    # get source vault private key
+                    if source_private_key:
+                        pass
+
+                    elif source_private_key_file:
+                        with open(source_private_key_file, 'rb') as f:
+                            source_private_key = f.read()
+
+                    else:
+                        raise errors.ValidationError(name='source_private_key',
+                            error=_('Missing source vault private key'))
+
+                else:
+                    raise errors.ValidationError(name='source_vault_type',
+                        error=_('Invalid source vault type'))
+
+                source_response = self.api.Command.vault_retrieve(
+                    source_vault_id,
+                    password=source_password,
+                    private_key=source_private_key)
+
+                source_json_data = self.obj.parse_response(source_response)
+
+                source_secrets = source_json_data['secrets']
+
+            else:
+                source_secrets = secrets
+
+            source_secret = self.obj.find(source_secrets, source_secret_id)
+            data = base64.b64decode(source_secret['data'])
+
+        else:
+            pass
 
         # modify the secret
         if description:
@@ -1035,7 +1287,7 @@ class vaultsecret_show(LDAPRetrieve):
                 raise errors.ValidationError(name='password_file',
                     error=_('Invalid parameter for %s vault' % vault_type))
 
-            # get vault public key
+            # get vault private key
             if private_key:
                 pass
 
@@ -1058,28 +1310,11 @@ class vaultsecret_show(LDAPRetrieve):
             password=password,
             private_key=private_key)
 
-        vault_data = response['result']['data']
-
-        if not vault_data:
-            json_data = {
-                'secrets': []
-            }
-
-        else:
-            json_data = json.loads(vault_data)
+        json_data = self.obj.parse_response(response)
 
         secrets = json_data['secrets']
 
-        secret = None
-
-        # find the secret
-        for s in secrets:
-            if s['secret_name'] == secret_name:
-                secret = s
-                break
-
-        if not secret:
-            raise errors.NotFound(reason=_('%s: vault secret not found' % secret_name))
+        secret = self.obj.find(secrets, secret_name)
 
         # decode data for response
         secret['data'] = base64.b64decode(secret['data'])
diff --git a/ipatests/test_xmlrpc/test_vault_plugin.py b/ipatests/test_xmlrpc/test_vault_plugin.py
index 98e0b543d280d5bb36d5f9aae5c925019e79e962..b4d415d5cf71fdd1bb47d320aa2d76964e0f6309 100644
--- a/ipatests/test_xmlrpc/test_vault_plugin.py
+++ b/ipatests/test_xmlrpc/test_vault_plugin.py
@@ -25,11 +25,26 @@ from ipalib import api, errors
 from xmlrpc_test import Declarative, fuzzy_string
 
 test_vault = u'test_vault'
+
 binary_data = '\x01\x02\x03\x04'
 text_data = u'secret'
 
+test_secret = u'test_secret'
+standard_secrets_vault = u'standard_secrets_vault'
+symmetric_secrets_vault = u'symmetric_secrets_vault'
+asymmetric_secrets_vault = u'asymmetric_secrets_vault'
+
+standard_vault = u'standard_vault'
+standard_vault_copy = u'standard_vault_copy'
+standard_vault_copy2 = u'standard_vault_copy2'
+
 symmetric_vault = u'symmetric_vault'
+symmetric_vault_copy = u'symmetric_vault_copy'
+symmetric_vault_copy2 = u'symmetric_vault_copy2'
+
 asymmetric_vault = u'asymmetric_vault'
+asymmetric_vault_copy = u'asymmetric_vault_copy'
+asymmetric_vault_copy2 = u'asymmetric_vault_copy2'
 
 escrowed_symmetric_vault = u'escrowed_symmetric_vault'
 escrowed_asymmetric_vault = u'escrowed_asymmetric_vault'
@@ -37,6 +52,7 @@ escrowed_asymmetric_vault = u'escrowed_asymmetric_vault'
 shared_test_vault = u'/shared/%s' % test_vault
 
 password = u'password'
+other_password = u'other_password'
 
 public_key = """
 -----BEGIN PUBLIC KEY-----
@@ -80,7 +96,7 @@ kUlCMj24a8XsShzYTWBIyW2ngvGe3pQ9PfjkUdm0LGZjYITCBvgOKw==
 -----END RSA PRIVATE KEY-----
 """
 
-escrow_public_key = """
+other_public_key = """
 -----BEGIN PUBLIC KEY-----
 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7E/QLVyKjrgDctZ50U7
 rmtL7Ks1QLoccp9WvZJ6WI1rYd0fX5FySS4dI6QTNZc6qww8NeNuZtkoxT9m1wkk
@@ -92,7 +108,7 @@ TwIDAQAB
 -----END PUBLIC KEY-----
 """
 
-escrow_private_key = """
+other_private_key = """
 -----BEGIN RSA PRIVATE KEY-----
 MIIEpgIBAAKCAQEAv7E/QLVyKjrgDctZ50U7rmtL7Ks1QLoccp9WvZJ6WI1rYd0f
 X5FySS4dI6QTNZc6qww8NeNuZtkoxT9m1wkkRl/3wK7fWNLenH/+VHOaTQc20exg
@@ -122,42 +138,22 @@ veCYju6ok4ZWnMiH8MR1jgC39RWtjJZwynCuPXUP2/vZkoVf1tCZyz7dSm8TdS/2
 -----END RSA PRIVATE KEY-----
 """
 
-wrong_private_key = """
------BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAuQVrFCTFzc8EQ1TPR/fB2vZqcNFn7KVFYN5UuGSLm8JwdSyX
-aJHFdgmdWS5NHRQS7l4kd3/mn1pk4BuhmC1tO+tPM1gunCtFvgB5fBC6WJzytBEC
-y/ikvSIyOQ33/KCLdTQ/oFTIp3f5dUO62a3gjgQ9Y0Z9krRXYiB5uxMyHrr/zuxL
-qRqP7pQYIkROr5VqGUgFUjFcovLvzP2sabobEnctCKm+nIE4s31QR3Kqb4l31qmX
-TkbiGkoEkwoqHDuu0eH50bJwRFjjx3h+5U0bmoff1ipi+Aan5gZKoOOlnkc9d2dZ
-gPBHyth6+9N3V8jrjs+VMJ8j2GzI9pnVxD93awIDAQABAoIBAC5neny54FaHBmWw
-vrApJpi5Vubmzm7e4LCz8oGwzgcJ1FS/E1ZpwSGitbEpWLPjVgAs4m6KSJhM/qHq
-rDPTqOLvWJTjGAWZIMvPqKiCNYqGCqU44v+vY/n/uqLuqvTUe0WxDggzW4QNJibQ
-DuwLnRdhXtgoVNRXoNb+mClgXiCwjgX8j+A2lb6H4iQsQOLjLr4ifH13CqDgJk3k
-2BjI1wOeesxooNk3I7ZKdVL1iV/qaeysLvtWoEmoQvrn6Fbo4HAR04tQQXEG7GzM
-kZ+RVmHyn5b4tCMqnzubM+IYf7k9cJfgZIyaVxO8NSfwaJkWbQiGwzvUebRiHwPs
-cK+F4bkCgYEA9VKtHX6Kr9Ht6Ho0q5eZAzRrOqgB2Uj4duFAI40bQ1q9+p+VSoKR
-IqU6I16HO7mbgjlOySVg5RVIcp+9DUoNoK2KlGFswX6Q0f1J/v0CDavQoqOcpyFh
-F9Egtx+5VNhknDZ0GTNWNm6bTWZUHK6ZICXs/mZkn4rHdId2Xe0Ls38CgYEAwRLg
-GXSctadY0Qowy2YLe+I2EjsdXgE82TvOorytjFhOPVB/GsCOJ3IjyHJyGdpkm/85
-U0SUoeNXQHIgiMGG+xxQ/ErJhpHxiaQKHFow48x3k8R7tlO68Mfnl+BZ9goRgxio
-rFwiea2MtCl2j8szh9UJ1z57aG1L0PfUctd9QhUCgYEA3O2T0ZgANc6MvmwvushP
-mD9AwhZDc/bvK8A3Ds0o3EOAC5Bj1jI3mkfKT8f1aagBkAkkFql+1U+RawjILIug
-Mi+XOYFze94LddDxLp2Tl9Q/k/hcP3ckBVrkZ4Y+VVZ7ZOL1Myy0W1jIq6+X2Cy0
-4erFv2VfAP7uGNdVlcjAXOkCgYABT5x/77/Ep/89ZCFSsD2xuKZ/VzFq2v1LyFEt
-37QZ+NuHJQ3H47jTYb4GdWh67nWybXg5LYUI2F9WS7AW3aGKAPY30FYv+Lu4IIoF
-CUO9uDyznyjr4wOo8OKMsHRL7GOUDU3P5cxCIUCMVJ++eDXAXVz0vjLeUaerIpOp
-t/bcxQKBgQC4ehPJQH8WWG1FnfXh62AJBgb+yUDmp+4bTRtr/GENF9W62GN+g7or
-RQqtdO1WxoXsC2qbKT4ArwOpV877KlWuLfiJMe5JS8vU/n711DCFIIfkXpeXPUVc
-MszdQuc/FTSJ2DYsIwx7qq5c8mtargOjWRgZU22IgY9PKeIcitQjqw==
------END RSA PRIVATE KEY-----
-"""
-
 class test_vault_plugin(Declarative):
 
     cleanup_commands = [
         ('vault_del', [test_vault], {'continue': True}),
+        ('vault_del', [standard_secrets_vault], {'continue': True}),
+        ('vault_del', [symmetric_secrets_vault], {'continue': True}),
+        ('vault_del', [asymmetric_secrets_vault], {'continue': True}),
+        ('vault_del', [standard_vault], {'continue': True}),
+        ('vault_del', [standard_vault_copy], {'continue': True}),
+        ('vault_del', [standard_vault_copy2], {'continue': True}),
         ('vault_del', [symmetric_vault], {'continue': True}),
+        ('vault_del', [symmetric_vault_copy], {'continue': True}),
+        ('vault_del', [symmetric_vault_copy2], {'continue': True}),
         ('vault_del', [asymmetric_vault], {'continue': True}),
+        ('vault_del', [asymmetric_vault_copy], {'continue': True}),
+        ('vault_del', [asymmetric_vault_copy2], {'continue': True}),
         ('vault_del', [escrowed_symmetric_vault], {'continue': True}),
         ('vault_del', [escrowed_asymmetric_vault], {'continue': True}),
         ('vault_del', [shared_test_vault], {'continue': True}),
@@ -383,6 +379,120 @@ class test_vault_plugin(Declarative):
         },
 
         {
+            'desc': 'Create standard vault',
+            'command': (
+                'vault_add',
+                [standard_vault],
+                {
+                    'data': binary_data,
+                },
+            ),
+            'expected': {
+                'value': standard_vault,
+                'summary': 'Added vault "%s"' % standard_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [standard_vault],
+                    'vault_id': u'/users/admin/%s' % standard_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Create a copy of standard vault',
+            'command': (
+                'vault_add',
+                [standard_vault_copy],
+                {
+                    'source_vault_id': standard_vault,
+                },
+            ),
+            'expected': {
+                'value': standard_vault_copy,
+                'summary': 'Added vault "%s"' % standard_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault_copy, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [standard_vault_copy],
+                    'vault_id': u'/users/admin/%s' % standard_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy creation of standard vault',
+            'command': (
+                'vault_retrieve',
+                [standard_vault_copy],
+                {},
+            ),
+            'expected': {
+                'value': standard_vault_copy,
+                'summary': u'Retrieved data from vault "%s"' % standard_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault_copy, api.env.basedn),
+                    'cn': [standard_vault_copy],
+                    'vault_id': u'/users/admin/%s' % standard_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive a copy of standard vault',
+            'command': (
+                'vault_archive',
+                [standard_vault_copy],
+                {
+                    'source_vault_id': standard_vault,
+                },
+            ),
+            'expected': {
+                'value': standard_vault_copy,
+                'summary': u'Archived data into vault "%s"' % standard_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault_copy, api.env.basedn),
+                    'cn': [standard_vault_copy],
+                    'vault_id': u'/users/admin/%s' % standard_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy archival of standard vault',
+            'command': (
+                'vault_retrieve',
+                [standard_vault_copy],
+                {},
+            ),
+            'expected': {
+                'value': standard_vault_copy,
+                'summary': u'Retrieved data from vault "%s"' % standard_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault_copy, api.env.basedn),
+                    'cn': [standard_vault_copy],
+                    'vault_id': u'/users/admin/%s' % standard_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
             'desc': 'Create symmetric vault',
             'command': (
                 'vault_add',
@@ -440,13 +550,117 @@ class test_vault_plugin(Declarative):
                 'vault_retrieve',
                 [symmetric_vault],
                 {
-                    'password': u'wrong',
+                    'password': other_password,
                 },
             ),
             'expected': errors.AuthenticationError(message=u'Invalid credentials'),
         },
 
         {
+            'desc': 'Create a copy of symmetric vault',
+            'command': (
+                'vault_add',
+                [symmetric_vault_copy],
+                {
+                    'ipavaulttype': u'symmetric',
+                    'password': other_password,
+                    'source_vault_id': symmetric_vault,
+                    'source_password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy,
+                'summary': 'Added vault "%s"' % symmetric_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault_copy, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [symmetric_vault_copy],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy creation of symmetric vault',
+            'command': (
+                'vault_retrieve',
+                [symmetric_vault_copy],
+                {
+                    'password': other_password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy,
+                'summary': u'Retrieved data from vault "%s"' % symmetric_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault_copy, api.env.basedn),
+                    'cn': [symmetric_vault_copy],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive a copy of symmetric vault',
+            'command': (
+                'vault_archive',
+                [symmetric_vault_copy],
+                {
+                    'password': other_password,
+                    'source_vault_id': symmetric_vault,
+                    'source_password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy,
+                'summary': u'Archived data into vault "%s"' % symmetric_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault_copy, api.env.basedn),
+                    'cn': [symmetric_vault_copy],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy archival of symmetric vault',
+            'command': (
+                'vault_retrieve',
+                [symmetric_vault_copy],
+                {
+                    'password': other_password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy,
+                'summary': u'Retrieved data from vault "%s"' % symmetric_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault_copy, api.env.basedn),
+                    'cn': [symmetric_vault_copy],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
             'desc': 'Create asymmetric vault',
             'command': (
                 'vault_add',
@@ -506,13 +720,120 @@ class test_vault_plugin(Declarative):
                 'vault_retrieve',
                 [asymmetric_vault],
                 {
-                    'private_key': wrong_private_key,
+                    'private_key': other_private_key,
                 },
             ),
             'expected': errors.AuthenticationError(message=u'Invalid credentials'),
         },
 
         {
+            'desc': 'Create a copy of asymmetric vault',
+            'command': (
+                'vault_add',
+                [asymmetric_vault_copy],
+                {
+                    'ipavaulttype': u'asymmetric',
+                    'ipapublickey': other_public_key,
+                    'source_vault_id': asymmetric_vault,
+                    'source_private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy,
+                'summary': 'Added vault "%s"' % asymmetric_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault_copy, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [asymmetric_vault_copy],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [other_public_key],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy creation of asymmetric vault',
+            'command': (
+                'vault_retrieve',
+                [asymmetric_vault_copy],
+                {
+                    'private_key': other_private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy,
+                'summary': u'Retrieved data from vault "%s"' % asymmetric_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault_copy, api.env.basedn),
+                    'cn': [asymmetric_vault_copy],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [other_public_key],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive a copy of asymmetric vault',
+            'command': (
+                'vault_archive',
+                [asymmetric_vault_copy],
+                {
+                    'source_vault_id': asymmetric_vault,
+                    'source_private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy,
+                'summary': u'Archived data into vault "%s"' % asymmetric_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault_copy, api.env.basedn),
+                    'cn': [asymmetric_vault_copy],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [other_public_key],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy archival of asymmetric vault',
+            'command': (
+                'vault_retrieve',
+                [asymmetric_vault_copy],
+                {
+                    'private_key': other_private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy,
+                'summary': u'Retrieved data from vault "%s"' % asymmetric_vault_copy,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault_copy, api.env.basedn),
+                    'cn': [asymmetric_vault_copy],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault_copy,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [other_public_key],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
             'desc': 'Create escrowed symmetric vault',
             'command': (
                 'vault_add',
@@ -520,7 +841,7 @@ class test_vault_plugin(Declarative):
                 {
                     'ipavaulttype': u'symmetric',
                     'password': password,
-                    'ipaescrowpublickey': escrow_public_key,
+                    'ipaescrowpublickey': other_public_key,
                     'data': binary_data,
                 },
             ),
@@ -546,7 +867,7 @@ class test_vault_plugin(Declarative):
                 'vault_retrieve',
                 [escrowed_symmetric_vault],
                 {
-                    'escrow_private_key': escrow_private_key,
+                    'escrow_private_key': other_private_key,
                 },
             ),
             'expected': {
@@ -575,7 +896,7 @@ class test_vault_plugin(Declarative):
                 {
                     'ipavaulttype': u'asymmetric',
                     'ipapublickey': public_key,
-                    'ipaescrowpublickey': escrow_public_key,
+                    'ipaescrowpublickey': other_public_key,
                     'data': binary_data,
                 },
             ),
@@ -602,7 +923,7 @@ class test_vault_plugin(Declarative):
                 'vault_retrieve',
                 [escrowed_asymmetric_vault],
                 {
-                    'escrow_private_key': escrow_private_key,
+                    'escrow_private_key': other_private_key,
                 },
             ),
             'expected': {
@@ -772,4 +1093,443 @@ class test_vault_plugin(Declarative):
             },
         },
 
+        {
+            'desc': 'Create standard secrets vault',
+            'command': (
+                'vault_add',
+                [standard_secrets_vault],
+                {},
+            ),
+            'expected': {
+                'value': standard_secrets_vault,
+                'summary': 'Added vault "%s"' % standard_secrets_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_secrets_vault, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [standard_secrets_vault],
+                    'vault_id': u'/users/admin/%s' % standard_secrets_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Create secret in standard vault',
+            'command': (
+                'vaultsecret_add',
+                [standard_secrets_vault, test_secret],
+                {
+                    'data': binary_data,
+                },
+            ),
+            'expected': {
+                'value': test_secret,
+                'summary': 'Added vault secret "%s"' % test_secret,
+                'result': {
+                    'secret_name': test_secret,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create symmetric secrets vault',
+            'command': (
+                'vault_add',
+                [symmetric_secrets_vault],
+                {
+                    'ipavaulttype': u'symmetric',
+                    'password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_secrets_vault,
+                'summary': 'Added vault "%s"' % symmetric_secrets_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_secrets_vault, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [symmetric_secrets_vault],
+                    'vault_id': u'/users/admin/%s' % symmetric_secrets_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                },
+            },
+        },
+
+        {
+            'desc': 'Create secret in symmetric vault',
+            'command': (
+                'vaultsecret_add',
+                [symmetric_secrets_vault, test_secret],
+                {
+                    'data': binary_data,
+                    'password': password,
+                },
+            ),
+            'expected': {
+                'value': test_secret,
+                'summary': 'Added vault secret "%s"' % test_secret,
+                'result': {
+                    'secret_name': test_secret,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create asymmetric secrets vault',
+            'command': (
+                'vault_add',
+                [asymmetric_secrets_vault],
+                {
+                    'ipavaulttype': u'asymmetric',
+                    'ipapublickey': public_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_secrets_vault,
+                'summary': 'Added vault "%s"' % asymmetric_secrets_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_secrets_vault, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [asymmetric_secrets_vault],
+                    'vault_id': u'/users/admin/%s' % asymmetric_secrets_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [public_key],
+                },
+            },
+        },
+
+        {
+            'desc': 'Create secret in asymmetric vault',
+            'command': (
+                'vaultsecret_add',
+                [asymmetric_secrets_vault, test_secret],
+                {
+                    'private_key': private_key,
+                    'data': binary_data,
+                },
+            ),
+            'expected': {
+                'value': test_secret,
+                'summary': 'Added vault secret "%s"' % test_secret,
+                'result': {
+                    'secret_name': test_secret,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create a copy of secret from standard vault',
+            'command': (
+                'vault_add',
+                [standard_vault_copy2],
+                {
+                    'source_vault_id': standard_secrets_vault,
+                    'source_secret_id': test_secret,
+                },
+            ),
+            'expected': {
+                'value': standard_vault_copy2,
+                'summary': 'Added vault "%s"' % standard_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault_copy2, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [standard_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % standard_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy creation of secret from standard vault',
+            'command': (
+                'vault_retrieve',
+                [standard_vault_copy2],
+                {},
+            ),
+            'expected': {
+                'value': standard_vault_copy2,
+                'summary': u'Retrieved data from vault "%s"' % standard_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault_copy2, api.env.basedn),
+                    'cn': [standard_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % standard_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive a copy of secret from standard vault',
+            'command': (
+                'vault_archive',
+                [standard_vault_copy2],
+                {
+                    'source_vault_id': standard_secrets_vault,
+                    'source_secret_id': test_secret,
+                },
+            ),
+            'expected': {
+                'value': standard_vault_copy2,
+                'summary': u'Archived data into vault "%s"' % standard_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault_copy2, api.env.basedn),
+                    'cn': [standard_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % standard_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy archival of secret from standard vault',
+            'command': (
+                'vault_retrieve',
+                [standard_vault_copy2],
+                {},
+            ),
+            'expected': {
+                'value': standard_vault_copy2,
+                'summary': u'Retrieved data from vault "%s"' % standard_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (standard_vault_copy2, api.env.basedn),
+                    'cn': [standard_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % standard_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'standard'],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create a copy of secret from symmetric vault',
+            'command': (
+                'vault_add',
+                [symmetric_vault_copy2],
+                {
+                    'ipavaulttype': u'symmetric',
+                    'password': password,
+                    'source_vault_id': symmetric_secrets_vault,
+                    'source_secret_id': test_secret,
+                    'source_password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy2,
+                'summary': 'Added vault "%s"' % symmetric_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault_copy2, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [symmetric_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy creation of secret from symmetric vault',
+            'command': (
+                'vault_retrieve',
+                [symmetric_vault_copy2],
+                {
+                    'password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy2,
+                'summary': u'Retrieved data from vault "%s"' % symmetric_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault_copy2, api.env.basedn),
+                    'cn': [symmetric_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive a copy of secret from symmetric vault',
+            'command': (
+                'vault_archive',
+                [symmetric_vault_copy2],
+                {
+                    'password': password,
+                    'source_vault_id': symmetric_secrets_vault,
+                    'source_secret_id': test_secret,
+                    'source_password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy2,
+                'summary': u'Archived data into vault "%s"' % symmetric_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault_copy2, api.env.basedn),
+                    'cn': [symmetric_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy archival of secret from symmetric vault',
+            'command': (
+                'vault_retrieve',
+                [symmetric_vault_copy2],
+                {
+                    'password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy2,
+                'summary': u'Retrieved data from vault "%s"' % symmetric_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault_copy2, api.env.basedn),
+                    'cn': [symmetric_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create a copy of secret from asymmetric vault',
+            'command': (
+                'vault_add',
+                [asymmetric_vault_copy2],
+                {
+                    'ipavaulttype': u'asymmetric',
+                    'ipapublickey': public_key,
+                    'source_vault_id': asymmetric_secrets_vault,
+                    'source_secret_id': test_secret,
+                    'source_private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy2,
+                'summary': 'Added vault "%s"' % asymmetric_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault_copy2, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [asymmetric_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [public_key],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy creation of secret from asymmetric vault',
+            'command': (
+                'vault_retrieve',
+                [asymmetric_vault_copy2],
+                {
+                    'private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy2,
+                'summary': u'Retrieved data from vault "%s"' % asymmetric_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault_copy2, api.env.basedn),
+                    'cn': [asymmetric_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [public_key],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive a copy of secret from asymmetric vault',
+            'command': (
+                'vault_archive',
+                [asymmetric_vault_copy2],
+                {
+                    'source_vault_id': asymmetric_secrets_vault,
+                    'source_secret_id': test_secret,
+                    'source_private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy2,
+                'summary': u'Archived data into vault "%s"' % asymmetric_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault_copy2, api.env.basedn),
+                    'cn': [asymmetric_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [public_key],
+                },
+            },
+        },
+
+        {
+            'desc': 'Verify the copy archival of secret from asymmetric vault',
+            'command': (
+                'vault_retrieve',
+                [asymmetric_vault_copy2],
+                {
+                    'private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy2,
+                'summary': u'Retrieved data from vault "%s"' % asymmetric_vault_copy2,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault_copy2, api.env.basedn),
+                    'cn': [asymmetric_vault_copy2],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault_copy2,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [public_key],
+                    'nonce': fuzzy_string,
+                    'vault_data': fuzzy_string,
+                    'data': binary_data,
+                },
+            },
+        },
+
     ]
diff --git a/ipatests/test_xmlrpc/test_vaultcontainer_plugin.py b/ipatests/test_xmlrpc/test_vaultcontainer_plugin.py
index ca96bff4dcae8c62bc44245ba82c7bf4165754e5..26a52b857f943a371da80dd3181e1cdec55bb3ee 100644
--- a/ipatests/test_xmlrpc/test_vaultcontainer_plugin.py
+++ b/ipatests/test_xmlrpc/test_vaultcontainer_plugin.py
@@ -313,6 +313,7 @@ class test_vaultcontainer_plugin(Declarative):
                 },
             },
         },
+
         {
             'desc': 'Create base container',
             'command': (
diff --git a/ipatests/test_xmlrpc/test_vaultsecret_plugin.py b/ipatests/test_xmlrpc/test_vaultsecret_plugin.py
index d2a4e92507fbabbcc356dc889738f151521e896c..de08d306cfb292058acc4abd8708afcae117eaf8 100644
--- a/ipatests/test_xmlrpc/test_vaultsecret_plugin.py
+++ b/ipatests/test_xmlrpc/test_vaultsecret_plugin.py
@@ -25,15 +25,66 @@ from ipalib import api, errors
 from xmlrpc_test import Declarative, fuzzy_string
 
 test_vault = u'test_vault'
+symmetric_vault = u'symmetric_vault'
+asymmetric_vault = u'asymmetric_vault'
 shared_test_vault = u'/shared/%s' % test_vault
-test_vaultsecret = u'test_vaultsecret'
+
+test_secret = u'test_secret'
+test_secret_copy = u'test_secret_copy'
+
 binary_data = '\x01\x02\x03\x04'
 text_data = u'secret'
 
+password = u'password'
+
+public_key = """
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnT61EFxUOQgCJdM0tmw/
+pRRPDPGchTClnU1eBtiQD3ItKYf1+weMGwGOSJXPtkto7NlE7Qs8WHAr0UjyeBDe
+k/zeB6nSVdk47OdaW1AHrJL+44r238Jbm/+7VO5lTu6Z4N5p0VqoWNLi0Uh/CkqB
+tsxXaaAgjMp0AGq2U/aO/akeEYWQOYIdqUKVgAEKX5MmIA8tmbmoYIQ+B4Q3vX7N
+otG4eR6c2o9Fyjd+M4Gai5Ce0fSrigRvxAYi8xpRkQ5yQn5gf4WVrn+UKTfOIjLO
+pVThop+Xivcre3SpI0kt6oZPhBw9i8gbMnqifVmGFpVdhq+QVBqp+MVJvTbhRPG6
+3wIDAQAB
+-----END PUBLIC KEY-----
+"""
+
+private_key = """
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAnT61EFxUOQgCJdM0tmw/pRRPDPGchTClnU1eBtiQD3ItKYf1
++weMGwGOSJXPtkto7NlE7Qs8WHAr0UjyeBDek/zeB6nSVdk47OdaW1AHrJL+44r2
+38Jbm/+7VO5lTu6Z4N5p0VqoWNLi0Uh/CkqBtsxXaaAgjMp0AGq2U/aO/akeEYWQ
+OYIdqUKVgAEKX5MmIA8tmbmoYIQ+B4Q3vX7NotG4eR6c2o9Fyjd+M4Gai5Ce0fSr
+igRvxAYi8xpRkQ5yQn5gf4WVrn+UKTfOIjLOpVThop+Xivcre3SpI0kt6oZPhBw9
+i8gbMnqifVmGFpVdhq+QVBqp+MVJvTbhRPG63wIDAQABAoIBAQCD2bXnfxPcMnvi
+jaPwpvoDCPF0EBBHmk/0g5ApO2Qon3uBDJFUqbJwXrCY6o2d9MOJfnGONlKmcYA8
+X+d4h+SqwGjIkjxdYeSauS+Jy6Rzr1ptH/P8EjPQrfG9uJxYQDflV3nxYwwwVrx7
+8kccMPdteRB+8Bb7FzOHufMimmayCNFETnVT5CKH2PrYoPB+fr0itCipWOenDp33
+e73OV+K9U3rclmtHaoRxGohqByKfQRUkipjw4m+T3qfZZc5eN77RGW8J+oL1GVom
+fwtiH7N1HVte0Dmd13nhiASg355kjqRPcIMPsRHvXkOpgg5HRUTKG5elqAyvvm27
+Fzj1YdeRAoGBAMnE61+FYh8qCyEGe8r6RGjO8iuoyk1t+0gBWbmILLBiRnj4K8Tc
+k7HBG/pg3XCNbCuRwiLg8tk3VAAXzn6o+IJr3QnKbNCGa1lKfYU4mt11sBEyuL5V
+NpZcZ8IiPhMlGyDA9cFbTMKOE08RqbOIdxOmTizFt0R5sYZAwOjEvBIZAoGBAMeC
+N/P0bdrScFZGeS51wEdiWme/CO0IyGoqU6saI8L0dbmMJquiaAeIEjIKLqxH1RON
+axhsyk97e0PCcc5QK62Utf50UUAbL/v7CpIG+qdSRYDO4bVHSCkwF32N3pYh/iVU
+EsEBEkZiJi0dWa/0asDbsACutxcHda3RI5pi7oO3AoGAcbGNs/CUHt1xEfX2UaT+
+YVSjb2iYPlNH8gYYygvqqqVl8opdF3v3mYUoP8jPXrnCBzcF/uNk1HNx2O+RQxvx
+lIQ1NGwlLsdfvBvWaPhBg6LqSHadVVrs/IMrUGA9PEp/Y9B3arIIqeSnCrn4Nxsh
+higDCwWKRIKSPwVD7qXVGBkCgYEAu5/CASIRIeYgEXMLSd8hKcDcJo8o1MoauIT/
+1Hyrvw9pm0qrn2QHk3WrLvYWeJzBTTcEzZ6aEG+fN9UodA8/VGnzUc6QDsrCsKWh
+hj0cArlDdeSZrYLQ4TNCFCiUePqU6QQM8weP6TMqlejxTKF+t8qi1bF5rCWuzP1P
+D0UU7DcCgYAUvmEGckugS+FTatop8S/rmkcQ4Bf5M/YCZfsySavucDiHcBt0QtXt
+Swh0XdDsYS3W1yj2XqqsQ7R58KNaffCHjjulWFzb5IiuSvvdxzWtiXHisOpO36MJ
+kUlCMj24a8XsShzYTWBIyW2ngvGe3pQ9PfjkUdm0LGZjYITCBvgOKw==
+-----END RSA PRIVATE KEY-----
+"""
+
 class test_vaultsecret_plugin(Declarative):
 
     cleanup_commands = [
         ('vault_del', [test_vault], {'continue': True}),
+        ('vault_del', [symmetric_vault], {'continue': True}),
+        ('vault_del', [asymmetric_vault], {'continue': True}),
         ('vault_del', [shared_test_vault], {'continue': True}),
     ]
 
@@ -61,19 +112,19 @@ class test_vaultsecret_plugin(Declarative):
         },
 
         {
-            'desc': 'Create test vault secret with binary data',
+            'desc': 'Create secret with binary data',
             'command': (
                 'vaultsecret_add',
-                [test_vault, test_vaultsecret],
+                [test_vault, test_secret],
                 {
                     'data': binary_data,
                 },
             ),
             'expected': {
-                'value': test_vaultsecret,
-                'summary': 'Added vault secret "%s"' % test_vaultsecret,
+                'value': test_secret,
+                'summary': 'Added vault secret "%s"' % test_secret,
                 'result': {
-                    'secret_name': test_vaultsecret,
+                    'secret_name': test_secret,
                     'data': binary_data,
                 },
             },
@@ -83,10 +134,10 @@ class test_vaultsecret_plugin(Declarative):
             'desc': 'Create duplicate vault secret',
             'command': (
                 'vaultsecret_add',
-                [test_vault, test_vaultsecret],
+                [test_vault, test_secret],
                 {},
             ),
-            'expected': errors.DuplicateEntry(message=u'vault secret with name "%s" already exists' % test_vaultsecret),
+            'expected': errors.DuplicateEntry(message=u'vault secret with name "%s" already exists' % test_secret),
         },
 
         {
@@ -102,7 +153,7 @@ class test_vaultsecret_plugin(Declarative):
                 'summary': u'1 vault secret matched',
                 'result': [
                     {
-                        'secret_name': test_vaultsecret,
+                        'secret_name': test_secret,
                         'data': binary_data,
                     },
                 ],
@@ -110,52 +161,271 @@ class test_vaultsecret_plugin(Declarative):
         },
 
         {
-            'desc': 'Retrieve test vault secret',
+            'desc': 'Retrieve secret',
             'command': (
                 'vaultsecret_show',
-                [test_vault, test_vaultsecret],
+                [test_vault, test_secret],
                 {},
             ),
             'expected': {
-                'value': test_vaultsecret,
+                'value': test_secret,
                 'summary': None,
                 'result': {
-                    'secret_name': test_vaultsecret,
+                    'secret_name': test_secret,
                     'data': binary_data,
                 },
             },
         },
 
         {
-            'desc': 'Modify test vault secret',
+            'desc': 'Modify secret',
             'command': (
                 'vaultsecret_mod',
-                [test_vault, test_vaultsecret],
+                [test_vault, test_secret],
                 {
-                    'description': u'Test vault secret',
+                    'description': u'secret',
                 },
             ),
             'expected': {
-                'value': test_vaultsecret,
-                'summary': u'Modified vault secret "%s"' % test_vaultsecret,
+                'value': test_secret,
+                'summary': u'Modified vault secret "%s"' % test_secret,
                 'result': {
-                    'secret_name': test_vaultsecret,
-                    'description': u'Test vault secret',
+                    'secret_name': test_secret,
+                    'description': u'secret',
                     'data': binary_data,
                 },
             },
         },
 
         {
-            'desc': 'Delete test vault secret',
+            'desc': 'Create symmetric vault',
+            'command': (
+                'vault_add',
+                [symmetric_vault],
+                {
+                    'ipavaulttype': u'symmetric',
+                    'password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault,
+                'summary': 'Added vault "%s"' % symmetric_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (symmetric_vault, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [symmetric_vault],
+                    'vault_id': u'/users/admin/%s' % symmetric_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                },
+            },
+        },
+
+        {
+            'desc': 'Create secret in symmetric vault',
+            'command': (
+                'vaultsecret_add',
+                [symmetric_vault, test_secret],
+                {
+                    'password': password,
+                    'data': binary_data,
+                },
+            ),
+            'expected': {
+                'value': test_secret,
+                'summary': 'Added vault secret "%s"' % test_secret,
+                'result': {
+                    'secret_name': test_secret,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create a copy of secret from a standard vault',
+            'command': (
+                'vaultsecret_add',
+                [symmetric_vault, test_secret_copy],
+                {
+                    'source_vault_id': test_vault,
+                    'source_secret_id': test_secret,
+                    'password': password,
+                },
+            ),
+            'expected': {
+                'value': test_secret_copy,
+                'summary': 'Added vault secret "%s"' % test_secret_copy,
+                'result': {
+                    'secret_name': test_secret_copy,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Update a copy of secret from a standard vault',
+            'command': (
+                'vaultsecret_mod',
+                [symmetric_vault, test_secret_copy],
+                {
+                    'source_vault_id': test_vault,
+                    'source_secret_id': test_secret,
+                    'password': password,
+                },
+            ),
+            'expected': {
+                'value': test_secret_copy,
+                'summary': 'Modified vault secret "%s"' % test_secret_copy,
+                'result': {
+                    'secret_name': test_secret_copy,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create asymmetric vault',
+            'command': (
+                'vault_add',
+                [asymmetric_vault],
+                {
+                    'ipavaulttype': u'asymmetric',
+                    'ipapublickey': public_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault,
+                'summary': 'Added vault "%s"' % asymmetric_vault,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s' % (asymmetric_vault, api.env.basedn),
+                    'objectclass': (u'ipaVault', u'top'),
+                    'cn': [asymmetric_vault],
+                    'vault_id': u'/users/admin/%s' % asymmetric_vault,
+                    'owner_user': [u'admin'],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'ipapublickey': [public_key],
+                },
+            },
+        },
+
+        {
+            'desc': 'Create secret in asymmetric vault',
+            'command': (
+                'vaultsecret_add',
+                [asymmetric_vault, test_secret],
+                {
+                    'private_key': private_key,
+                    'data': binary_data,
+                },
+            ),
+            'expected': {
+                'value': test_secret,
+                'summary': 'Added vault secret "%s"' % test_secret,
+                'result': {
+                    'secret_name': test_secret,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create a copy of secret from a symmetric vault',
+            'command': (
+                'vaultsecret_add',
+                [asymmetric_vault, test_secret_copy],
+                {
+                    'private_key': private_key,
+                    'source_vault_id': symmetric_vault,
+                    'source_secret_id': test_secret,
+                    'source_password': password,
+                },
+            ),
+            'expected': {
+                'value': test_secret_copy,
+                'summary': 'Added vault secret "%s"' % test_secret_copy,
+                'result': {
+                    'secret_name': test_secret_copy,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Update a copy of secret from a symmetric vault',
+            'command': (
+                'vaultsecret_mod',
+                [asymmetric_vault, test_secret_copy],
+                {
+                    'private_key': private_key,
+                    'source_vault_id': symmetric_vault,
+                    'source_secret_id': test_secret,
+                    'source_password': password,
+                },
+            ),
+            'expected': {
+                'value': test_secret_copy,
+                'summary': 'Modified vault secret "%s"' % test_secret_copy,
+                'result': {
+                    'secret_name': test_secret_copy,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Create a copy of secret from an asymmetric vault',
+            'command': (
+                'vaultsecret_add',
+                [test_vault, test_secret_copy],
+                {
+                    'source_vault_id': asymmetric_vault,
+                    'source_secret_id': test_secret,
+                    'source_private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': test_secret_copy,
+                'summary': 'Added vault secret "%s"' % test_secret_copy,
+                'result': {
+                    'secret_name': test_secret_copy,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Update a copy of secret from an asymmetric vault',
+            'command': (
+                'vaultsecret_mod',
+                [test_vault, test_secret_copy],
+                {
+                    'source_vault_id': asymmetric_vault,
+                    'source_secret_id': test_secret,
+                    'source_private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': test_secret_copy,
+                'summary': 'Modified vault secret "%s"' % test_secret_copy,
+                'result': {
+                    'secret_name': test_secret_copy,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete secret',
             'command': (
                 'vaultsecret_del',
-                [test_vault, test_vaultsecret],
+                [test_vault, test_secret],
                 {},
             ),
             'expected': {
-                'value': test_vaultsecret,
-                'summary': u'Deleted vault secret "%s"' % test_vaultsecret,
+                'value': test_secret,
+                'summary': u'Deleted vault secret "%s"' % test_secret,
                 'result': {
                     'failed': (),
                 },
@@ -166,45 +436,45 @@ class test_vaultsecret_plugin(Declarative):
             'desc': 'Delete non-existent vault secret',
             'command': (
                 'vaultsecret_del',
-                [test_vault, test_vaultsecret],
+                [test_vault, test_secret],
                 {},
             ),
-            'expected': errors.NotFound(reason=u'%s: vault secret not found' % test_vaultsecret),
+            'expected': errors.NotFound(reason=u'%s: vault secret not found' % test_secret),
         },
 
         {
-            'desc': 'Create test vault secret with text data',
+            'desc': 'Create secret with text data',
             'command': (
                 'vaultsecret_add',
-                [test_vault, test_vaultsecret],
+                [test_vault, test_secret],
                 {
                     'text': text_data,
                 },
             ),
             'expected': {
-                'value': test_vaultsecret,
-                'summary': 'Added vault secret "%s"' % test_vaultsecret,
+                'value': test_secret,
+                'summary': 'Added vault secret "%s"' % test_secret,
                 'result': {
-                    'secret_name': test_vaultsecret,
+                    'secret_name': test_secret,
                     'data': text_data.encode('utf-8'),
                 },
             },
         },
 
         {
-            'desc': 'Retrieve test vault secret as text',
+            'desc': 'Retrieve secret as text',
             'command': (
                 'vaultsecret_show',
-                [test_vault, test_vaultsecret],
+                [test_vault, test_secret],
                 {
                     'show_text': True,
                 },
             ),
             'expected': {
-                'value': test_vaultsecret,
+                'value': test_secret,
                 'summary': None,
                 'result': {
-                    'secret_name': test_vaultsecret,
+                    'secret_name': test_secret,
                     'text': text_data,
                 },
             },
@@ -232,19 +502,19 @@ class test_vaultsecret_plugin(Declarative):
         },
 
         {
-            'desc': 'Create shared test vault secret with binary data',
+            'desc': 'Create shared secret with binary data',
             'command': (
                 'vaultsecret_add',
-                [shared_test_vault, test_vaultsecret],
+                [shared_test_vault, test_secret],
                 {
                     'data': binary_data,
                 },
             ),
             'expected': {
-                'value': test_vaultsecret,
-                'summary': 'Added vault secret "%s"' % test_vaultsecret,
+                'value': test_secret,
+                'summary': 'Added vault secret "%s"' % test_secret,
                 'result': {
-                    'secret_name': test_vaultsecret,
+                    'secret_name': test_secret,
                     'data': binary_data,
                 },
             },
@@ -263,7 +533,7 @@ class test_vaultsecret_plugin(Declarative):
                 'summary': u'1 vault secret matched',
                 'result': [
                     {
-                        'secret_name': test_vaultsecret,
+                        'secret_name': test_secret,
                         'data': binary_data,
                     },
                 ],
@@ -271,52 +541,71 @@ class test_vaultsecret_plugin(Declarative):
         },
 
         {
-            'desc': 'Retrieve shared test vault secret',
+            'desc': 'Retrieve shared secret',
             'command': (
                 'vaultsecret_show',
-                [shared_test_vault, test_vaultsecret],
+                [shared_test_vault, test_secret],
                 {},
             ),
             'expected': {
-                'value': test_vaultsecret,
+                'value': test_secret,
                 'summary': None,
                 'result': {
-                    'secret_name': test_vaultsecret,
+                    'secret_name': test_secret,
                     'data': binary_data,
                 },
             },
         },
 
         {
-            'desc': 'Modify shared test vault secret',
+            'desc': 'Modify shared secret',
             'command': (
                 'vaultsecret_mod',
-                [shared_test_vault, test_vaultsecret],
+                [shared_test_vault, test_secret],
                 {
-                    'description': u'Test vault secret',
+                    'description': u'secret',
                 },
             ),
             'expected': {
-                'value': test_vaultsecret,
-                'summary': u'Modified vault secret "%s"' % test_vaultsecret,
+                'value': test_secret,
+                'summary': u'Modified vault secret "%s"' % test_secret,
                 'result': {
-                    'secret_name': test_vaultsecret,
-                    'description': u'Test vault secret',
+                    'secret_name': test_secret,
+                    'description': u'secret',
                     'data': binary_data,
                 },
             },
         },
 
         {
-            'desc': 'Delete shared test vault secret',
+            'desc': 'Create a copy of secret in the same vault',
+            'command': (
+                'vaultsecret_add',
+                [shared_test_vault, test_secret_copy],
+                {
+                    'source_secret_id': test_secret,
+                },
+            ),
+            'expected': {
+                'value': test_secret_copy,
+                'summary': 'Added vault secret "%s"' % test_secret_copy,
+                'result': {
+                    'secret_name': test_secret_copy,
+                    'data': binary_data,
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete shared secret',
             'command': (
                 'vaultsecret_del',
-                [shared_test_vault, test_vaultsecret],
+                [shared_test_vault, test_secret],
                 {},
             ),
             'expected': {
-                'value': test_vaultsecret,
-                'summary': u'Deleted vault secret "%s"' % test_vaultsecret,
+                'value': test_secret,
+                'summary': u'Deleted vault secret "%s"' % test_secret,
                 'result': {
                     'failed': (),
                 },
-- 
1.9.0



More information about the Freeipa-devel mailing list