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" <[email protected]> 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
