The vault-add and vault-archive commands have been modified to
optionally retrieve a secret from a source vault, then re-archive
the secret into the new/existing target vault.

https://fedorahosted.org/freeipa/ticket/5223

--
Endi S. Dewata
From 604c206e861b35fc1ae30c7cd68a03e52fd83845 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edew...@redhat.com>
Date: Sat, 15 Aug 2015 16:17:47 +0200
Subject: [PATCH] Added mechanism to copy vault secrets.

The vault-add and vault-archive commands have been modified to
optionally retrieve a secret from a source vault, then re-archive
the secret into the new/existing target vault.

https://fedorahosted.org/freeipa/ticket/5223
---
 API.txt                                   |  20 ++-
 ipalib/plugins/vault.py                   | 195 ++++++++++++++++++++----------
 ipatests/test_xmlrpc/test_vault_plugin.py | 152 ++++++++++++++++++++++-
 3 files changed, 297 insertions(+), 70 deletions(-)

diff --git a/API.txt b/API.txt
index 
26f05cf9e1e27ec4f714bb34174e17972961bda2..d86a40742728ddb9cf8db9358166f49f70a8bc00
 100644
--- a/API.txt
+++ b/API.txt
@@ -5397,7 +5397,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,14,3
+args: 1,22,3
 arg: Str('cn', attribute=True, cli_name='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')
@@ -5411,6 +5411,14 @@ option: Flag('raw', autofill=True, cli_name='raw', 
default=False, exclude='webui
 option: Str('service?')
 option: Str('setattr*', cli_name='setattr', exclude='webui')
 option: Flag('shared?', autofill=True, default=False)
+option: Str('source_password?')
+option: Str('source_password_file?')
+option: Bytes('source_private_key?')
+option: Str('source_private_key_file?')
+option: Str('source_service?')
+option: Flag('source_shared?', autofill=True, default=False)
+option: Str('source_user?')
+option: Str('source_vault?')
 option: Str('username?', cli_name='user')
 option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an 
LDAP entry', domain='ipa', localedir=None))
@@ -5466,7 +5474,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,11,3
+args: 1,19,3
 arg: Str('cn', attribute=True, cli_name='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: Bytes('data?')
@@ -5477,6 +5485,14 @@ option: Str('password_file?', cli_name='password_file')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
 option: Str('service?')
 option: Flag('shared?', autofill=True, default=False)
+option: Str('source_password?')
+option: Str('source_password_file?')
+option: Bytes('source_private_key?')
+option: Str('source_private_key_file?')
+option: Str('source_service?')
+option: Flag('source_shared?', autofill=True, default=False)
+option: Str('source_user?')
+option: Str('source_vault?')
 option: Str('username?', cli_name='user')
 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 
c9eb4378b5c40d4182a70b72ae785740492ac9cb..278ca5e9113396f3de6217ef0e4eaa9da7ddce9a
 100644
--- a/ipalib/plugins/vault.py
+++ b/ipalib/plugins/vault.py
@@ -252,6 +252,74 @@ vault_options = (
     ),
 )
 
+source_vault_options = (
+    Str(
+        'source_vault?',
+        doc=_('Name of the source service vault'),
+    ),
+    Str(
+        'source_service?',
+        doc=_('Service name of the source service vault'),
+    ),
+    Flag(
+        'source_shared?',
+        doc=_('Source shared vault'),
+    ),
+    Str(
+        'source_user?',
+        doc=_('Username of the source user vault'),
+    ),
+    Str(
+        'source_password?',
+        doc=_('Source vault password'),
+    ),
+    Str(  # TODO: use File parameter
+        'source_password_file?',
+        doc=_('File containing the source vault password'),
+    ),
+    Bytes(
+        'source_private_key?',
+        doc=_('Source vault private key'),
+    ),
+    Str(  # TODO: use File parameter
+        'source_private_key_file?',
+        doc=_('File containing the source vault private key'),
+    ),
+)
+
+vault_add_options = (
+    Str(
+        'description?',
+        cli_name='desc',
+        doc=_('Vault description'),
+    ),
+    Str(
+        'ipavaulttype?',
+        cli_name='type',
+        doc=_('Vault type'),
+    ),
+    Str(
+        'password?',
+        cli_name='password',
+        doc=_('Vault password'),
+    ),
+    Str(  # TODO: use File parameter
+        'password_file?',
+        cli_name='password_file',
+        doc=_('File containing the vault password'),
+    ),
+    Bytes(
+        'ipavaultpublickey?',
+        cli_name='public_key',
+        doc=_('Vault public key'),
+    ),
+    Str(  # TODO: use File parameter
+        'public_key_file?',
+        cli_name='public_key_file',
+        doc=_('File containing the vault public key'),
+    ),
+)
+
 
 @register()
 class vault(LDAPObject):
@@ -546,56 +614,27 @@ class vault(LDAPObject):
 class vault_add(PKQuery, Local):
     __doc__ = _('Create a new vault.')
 
-    takes_options = LDAPCreate.takes_options + vault_options + (
-        Str(
-            'description?',
-            cli_name='desc',
-            doc=_('Vault description'),
-        ),
-        Str(
-            'ipavaulttype?',
-            cli_name='type',
-            doc=_('Vault type'),
-        ),
-        Str(
-            'password?',
-            cli_name='password',
-            doc=_('Vault password'),
-        ),
-        Str(  # TODO: use File parameter
-            'password_file?',
-            cli_name='password_file',
-            doc=_('File containing the vault password'),
-        ),
-        Bytes(
-            'ipavaultpublickey?',
-            cli_name='public_key',
-            doc=_('Vault public key'),
-        ),
-        Str(  # TODO: use File parameter
-            'public_key_file?',
-            cli_name='public_key_file',
-            doc=_('File containing the vault public key'),
-        ),
-    )
+    takes_options = LDAPCreate.takes_options + vault_options + \
+        source_vault_options + vault_add_options
 
     has_output = output.standard_entry
 
     def forward(self, *args, **options):
 
         vault_type = options.get('ipavaulttype', u'standard')
-        password = options.get('password')
-        password_file = options.get('password_file')
+        password = options.pop('password', None)
+        password_file = options.pop('password_file', None)
         public_key = options.get('ipavaultpublickey')
-        public_key_file = options.get('public_key_file')
+        public_key_file = options.pop('public_key_file', None)
 
-        # don't send these parameters to server
-        if 'password' in options:
-            del options['password']
-        if 'password_file' in options:
-            del options['password_file']
-        if 'public_key_file' in options:
-            del options['public_key_file']
+        source_vault = options.pop('source_vault', None)
+        source_service = options.pop('source_service', None)
+        source_shared = options.pop('source_shared', None)
+        source_user = options.pop('source_user', None)
+        source_password = options.pop('source_password', None)
+        source_password_file = options.pop('source_password_file', None)
+        source_private_key = options.pop('source_private_key', None)
+        source_private_key_file = options.pop('source_private_key_file', None)
 
         if vault_type != u'symmetric' and (password or password_file):
             raise errors.MutuallyExclusiveError(
@@ -672,6 +711,7 @@ class vault_add(PKQuery, Local):
         opts = options.copy()
         if 'description' in opts:
             del opts['description']
+
         if 'ipavaulttype' in opts:
             del opts['ipavaulttype']
 
@@ -682,7 +722,17 @@ class vault_add(PKQuery, Local):
         elif vault_type == u'asymmetric':
             del opts['ipavaultpublickey']
 
-        # archive blank data
+        opts['source_vault'] = source_vault
+        opts['source_service'] = source_service
+        opts['source_shared'] = source_shared
+        opts['source_user'] = source_user
+
+        opts['source_password'] = source_password
+        opts['source_password_file'] = source_password_file
+        opts['source_private_key'] = source_private_key
+        opts['source_private_key_file'] = source_private_key_file
+
+        # archive initial data
         self.api.Command.vault_archive(*args, **opts)
 
         return response
@@ -918,7 +968,7 @@ class vault_mod(PKQuery, Local):
             pass
 
         elif change_password or \
-            new_password or new_password_file or salt:
+                new_password or new_password_file or salt:
             vault_type = u'symmetric'
 
         elif new_public_key or new_public_key_file:
@@ -1093,7 +1143,7 @@ class vaultconfig_show(Retrieve):
 class vault_archive(PKQuery, Local):
     __doc__ = _('Archive data into a vault.')
 
-    takes_options = vault_options + (
+    takes_options = vault_options + source_vault_options + (
         Bytes(
             'data?',
             doc=_('Binary data to archive'),
@@ -1124,23 +1174,29 @@ class vault_archive(PKQuery, Local):
 
         name = args[-1]
 
-        data = options.get('data')
-        input_file = options.get('in')
+        data = options.pop('data', None)
+        input_file = options.pop('in', None)
 
-        password = options.get('password')
-        password_file = options.get('password_file')
+        source_vault = options.pop('source_vault', None)
+        source_service = options.pop('source_service', None)
+        source_shared = options.pop('source_shared', None)
+        source_user = options.pop('source_user', None)
+        source_password = options.pop('source_password', None)
+        source_password_file = options.pop('source_password_file', None)
+        source_private_key = options.pop('source_private_key', None)
+        source_private_key_file = options.pop('source_private_key_file', None)
+
+        password = options.pop('password', None)
+        password_file = options.pop('password_file', None)
 
         override_password = options.pop('override_password', False)
 
-        # don't send these parameters to server
-        if 'data' in options:
-            del options['data']
-        if 'in' in options:
-            del options['in']
-        if 'password' in options:
-            del options['password']
-        if 'password_file' in options:
-            del options['password_file']
+        if self.api.env.in_server:
+            backend = self.api.Backend.ldap2
+        else:
+            backend = self.api.Backend.rpcclient
+        if not backend.isconnected():
+            backend.connect(ccache=krbV.default_context().default_ccache())
 
         # get data
         if data and input_file:
@@ -1150,16 +1206,25 @@ class vault_archive(PKQuery, Local):
         elif input_file:
             data = validated_read('in', input_file, mode='rb')
 
+        elif source_vault:
+            opts = {}
+
+            opts['service'] = source_service
+            opts['shared'] = source_shared
+            opts['username'] = source_user
+
+            opts['password'] = source_password
+            opts['password_file'] = source_password_file
+            opts['private_key'] = source_private_key
+            opts['private_key_file'] = source_private_key_file
+
+            # retrieve data from source vault
+            response = self.api.Command.vault_retrieve(source_vault, **opts)
+            data = response['result']['data']
+
         elif not data:
             data = ''
 
-        if self.api.env.in_server:
-            backend = self.api.Backend.ldap2
-        else:
-            backend = self.api.Backend.rpcclient
-        if not backend.isconnected():
-            backend.connect(ccache=krbV.default_context().default_ccache())
-
         # retrieve vault info
         vault = self.api.Command.vault_show(*args, **options)['result']
 
diff --git a/ipatests/test_xmlrpc/test_vault_plugin.py 
b/ipatests/test_xmlrpc/test_vault_plugin.py
index 
077d9411b27a771e2c30c13215ffc08cfc6f7787..6d8a7241f6f073f713c2aeedf507be55a72de691
 100644
--- a/ipatests/test_xmlrpc/test_vault_plugin.py
+++ b/ipatests/test_xmlrpc/test_vault_plugin.py
@@ -33,6 +33,10 @@ standard_vault_name = u'standard_test_vault'
 symmetric_vault_name = u'symmetric_test_vault'
 asymmetric_vault_name = u'asymmetric_test_vault'
 
+standard_vault_copy_name = u'standard_test_vault_copy'
+symmetric_vault_copy_name = u'symmetric_test_vault_copy'
+asymmetric_vault_copy_name = u'asymmetric_test_vault_copy'
+
 # binary data from \x00 to \xff
 secret = ''.join(map(chr, xrange(0, 256)))
 
@@ -147,6 +151,9 @@ class test_vault_plugin(Declarative):
         ('vault_del', [standard_vault_name], {'continue': True}),
         ('vault_del', [symmetric_vault_name], {'continue': True}),
         ('vault_del', [asymmetric_vault_name], {'continue': True}),
+        ('vault_del', [standard_vault_copy_name], {'continue': True}),
+        ('vault_del', [symmetric_vault_copy_name], {'continue': True}),
+        ('vault_del', [asymmetric_vault_copy_name], {'continue': True}),
     ]
 
     tests = [
@@ -634,6 +641,52 @@ class test_vault_plugin(Declarative):
         },
 
         {
+            'desc': 'Copy standard vault to symmetric vault',
+            'command': (
+                'vault_add',
+                [standard_vault_copy_name],
+                {
+                    'ipavaulttype': u'symmetric',
+                    'password': password,
+                    'source_vault': standard_vault_name,
+                },
+            ),
+            'expected': {
+                'value': standard_vault_copy_name,
+                'summary': u'Added vault "%s"' % standard_vault_copy_name,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,cn=kra,%s'
+                          % (standard_vault_copy_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [standard_vault_copy_name],
+                    'ipavaulttype': [u'symmetric'],
+                    'ipavaultsalt': [fuzzy_string],
+                    'owner_user': [u'admin'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Retrieve secret from standard vault copied to '
+                    'symmetric vault',
+            'command': (
+                'vault_retrieve',
+                [standard_vault_copy_name],
+                {
+                    'password': password,
+                },
+            ),
+            'expected': {
+                'value': standard_vault_copy_name,
+                'summary': 'Retrieved data from vault "%s"'
+                           % standard_vault_copy_name,
+                'result': {
+                    'data': secret,
+                },
+            },
+        },
+
+        {
             'desc': 'Change standard vault to symmetric vault',
             'command': (
                 'vault_mod',
@@ -656,7 +709,8 @@ class test_vault_plugin(Declarative):
         },
 
         {
-            'desc': 'Retrieve secret from standard vault converted to 
symmetric vault',
+            'desc': 'Retrieve secret from standard vault converted to '
+                    'symmetric vault',
             'command': (
                 'vault_retrieve',
                 [standard_vault_name],
@@ -737,6 +791,53 @@ class test_vault_plugin(Declarative):
         },
 
         {
+            'desc': 'Copy symmetric vault to asymmetric vault',
+            'command': (
+                'vault_add',
+                [symmetric_vault_copy_name],
+                {
+                    'ipavaulttype': u'asymmetric',
+                    'ipavaultpublickey': public_key,
+                    'source_vault': symmetric_vault_name,
+                    'source_password': password,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy_name,
+                'summary': u'Added vault "%s"' % symmetric_vault_copy_name,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,cn=kra,%s'
+                          % (symmetric_vault_copy_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [symmetric_vault_copy_name],
+                    'ipavaulttype': [u'asymmetric'],
+                    'ipavaultpublickey': [public_key],
+                    'owner_user': [u'admin'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Retrieve secret from symmetric vault copied to '
+                    'asymmetric vault',
+            'command': (
+                'vault_retrieve',
+                [symmetric_vault_copy_name],
+                {
+                    'private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': symmetric_vault_copy_name,
+                'summary': 'Retrieved data from vault "%s"'
+                           % symmetric_vault_copy_name,
+                'result': {
+                    'data': secret,
+                },
+            },
+        },
+
+        {
             'desc': 'Change symmetric vault password',
             'command': (
                 'vault_mod',
@@ -801,7 +902,8 @@ class test_vault_plugin(Declarative):
         },
 
         {
-            'desc': 'Retrieve secret from symmetric vault converted to 
asymmetric vault',
+            'desc': 'Retrieve secret from symmetric vault converted to '
+                    'asymmetric vault',
             'command': (
                 'vault_retrieve',
                 [symmetric_vault_name],
@@ -881,6 +983,49 @@ class test_vault_plugin(Declarative):
         },
 
         {
+            'desc': 'Copy asymmetric vault to standard vault',
+            'command': (
+                'vault_add',
+                [asymmetric_vault_copy_name],
+                {
+                    'ipavaulttype': u'standard',
+                    'source_vault': asymmetric_vault_name,
+                    'source_private_key': private_key,
+                },
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy_name,
+                'summary': u'Added vault "%s"' % asymmetric_vault_copy_name,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,cn=kra,%s'
+                          % (asymmetric_vault_copy_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [asymmetric_vault_copy_name],
+                    'ipavaulttype': [u'standard'],
+                    'owner_user': [u'admin'],
+                },
+            },
+        },
+
+        {
+            'desc': 'Retrieve secret from asymmetric vault copied to '
+                    'standard vault',
+            'command': (
+                'vault_retrieve',
+                [asymmetric_vault_copy_name],
+                {},
+            ),
+            'expected': {
+                'value': asymmetric_vault_copy_name,
+                'summary': 'Retrieved data from vault "%s"'
+                           % asymmetric_vault_copy_name,
+                'result': {
+                    'data': secret,
+                },
+            },
+        },
+
+        {
             'desc': 'Change asymmetric vault keys',
             'command': (
                 'vault_mod',
@@ -943,7 +1088,8 @@ class test_vault_plugin(Declarative):
         },
 
         {
-            'desc': 'Retrieve secret from asymmetric vault converted to 
standard vault',
+            'desc': 'Retrieve secret from asymmetric vault converted to '
+                    'standard vault',
             'command': (
                 'vault_retrieve',
                 [asymmetric_vault_name],
-- 
2.4.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to