[Freeipa-devel] [PATCH] 375 Added mechanism to copy vault secrets.

Endi Sukma Dewata edewata at redhat.com
Wed Aug 26 23:47:58 UTC 2015


On 8/20/2015 2:08 AM, Endi Sukma Dewata wrote:
> On 8/19/2015 4:20 AM, Martin Basti wrote:
>> On 08/16/2015 05:29 PM, Endi Sukma Dewata wrote:
>>> 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
>>>
>>>
>>>
>> I cannot apply this patch.
>
> Rebased. It depends on patch #371-2.

Rebased due to other changes in vault.

-- 
Endi S. Dewata
-------------- next part --------------
>From 676b2043a390e6e68772837cf46e222aeda9da78 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata at 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 ++-
 VERSION                                   |   4 +-
 ipalib/plugins/vault.py                   | 213 ++++++++++++++++++++----------
 ipatests/test_xmlrpc/test_vault_plugin.py | 143 ++++++++++++++++++++
 4 files changed, 306 insertions(+), 74 deletions(-)

diff --git a/API.txt b/API.txt
index afd5017bee2bc1eed54497ccd504b92619ff7a58..c883271af4ff84f82c623208567f114265c3ce60 100644
--- a/API.txt
+++ b/API.txt
@@ -5405,7 +5405,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')
@@ -5419,6 +5419,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))
@@ -5474,7 +5482,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?')
@@ -5485,6 +5493,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/VERSION b/VERSION
index d3073e52ee022cc08b74953222a5040929ded60f..e3cfaa91f03fc6f4d9f5084809a8f74af333c8ef 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=154
-# Last change: pvoborni - change default vault type to 'symmetric'
+IPA_API_VERSION_MINOR=155
+# Last change: edewata - Added mechanism to copy vault secrets.
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
index ff6c22c646e9784b2fa1a6464f0749cb1ce86b50..a625746ab067d915e71504e971eefb0d0222ff77 100644
--- a/ipalib/plugins/vault.py
+++ b/ipalib/plugins/vault.py
@@ -255,6 +255,78 @@ 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'),
+    ),
+    StrEnum(
+        'ipavaulttype?',
+        cli_name='type',
+        label=_('Type'),
+        doc=_('Vault type'),
+        values=(u'standard', u'symmetric', u'asymmetric', ),
+        default=u'symmetric',
+        autofill=True,
+    ),
+    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):
@@ -573,60 +645,28 @@ 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'),
-        ),
-        StrEnum(
-            'ipavaulttype?',
-            cli_name='type',
-            label=_('Type'),
-            doc=_('Vault type'),
-            values=(u'standard', u'symmetric', u'asymmetric', ),
-            default=u'symmetric',
-            autofill=True,
-        ),
-        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')
-        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)
         public_key = options.get('ipavaultpublickey')
-        public_key_file = options.get('public_key_file')
-
-        # 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']
+        public_key_file = options.pop('public_key_file', None)
 
         if vault_type != u'symmetric' and (password or password_file):
             raise errors.MutuallyExclusiveError(
@@ -726,7 +766,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
@@ -1179,7 +1229,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'),
@@ -1210,26 +1260,40 @@ 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()
 
         # get data
-        if data and input_file:
+        counter = 0
+        if data:
+            counter = counter + 1
+        if input_file:
+            counter = counter + 1
+        if source_vault:
+            counter = counter + 1
+
+        if counter > 1:
             raise errors.MutuallyExclusiveError(
                 reason=_('Input data specified multiple times'))
 
@@ -1254,16 +1318,25 @@ class vault_archive(PKQuery, Local):
                     % {'limit': MAX_VAULT_DATA_SIZE})
             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']
+
         else:
             data = ''
 
-        if self.api.env.in_server:
-            backend = self.api.Backend.ldap2
-        else:
-            backend = self.api.Backend.rpcclient
-        if not backend.isconnected():
-            backend.connect()
-
         # 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 495512dac687afaee0c94c620c2b504df424c246..d69bd6725f01255f621d50aff558c2a2718e0e60 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 = [
@@ -641,6 +648,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',
@@ -745,6 +798,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',
@@ -890,6 +990,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',
-- 
2.4.3



More information about the Freeipa-devel mailing list