[Freeipa-devel] [PATCH] 375 Added mechanism to copy vault secrets.
Endi Sukma Dewata
edewata at redhat.com
Sun Aug 16 15:29:24 UTC 2015
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
-------------- next part --------------
From 604c206e861b35fc1ae30c7cd68a03e52fd83845 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 ++-
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
More information about the Freeipa-devel
mailing list