Added vault-archive and vault-retrieve commands.

New commands have been added to archive and retrieve
data into and from a vault, also to retrieve the
transport certificate.

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

Reviewed-By: Jan Cholasta <jcholast@redhat.com>
This commit is contained in:
Endi S. Dewata 2015-06-05 08:49:39 +00:00 committed by Jan Cholasta
parent e01095dfb3
commit df1bd39a43
5 changed files with 634 additions and 4 deletions

65
API.txt
View File

@ -5146,6 +5146,36 @@ option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None) output: PrimaryKey('value', None, None)
command: vault_archive
args: 1,8,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?')
option: Str('in?')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('service?')
option: Flag('shared?', autofill=True, default=False)
option: Str('user?')
option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None)
command: vault_archive_encrypted
args: 1,10,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: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False)
option: Bytes('nonce')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('service?')
option: Bytes('session_key')
option: Flag('shared?', autofill=True, default=False)
option: Str('user?')
option: Bytes('vault_data')
option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None)
command: vault_del command: vault_del
args: 1,5,3 args: 1,5,3
arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True) arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
@ -5192,6 +5222,32 @@ option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None) output: PrimaryKey('value', None, None)
command: vault_retrieve
args: 1,7,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: Str('out?')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('service?')
option: Flag('shared?', autofill=True, default=False)
option: Str('user?')
option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None)
command: vault_retrieve_encrypted
args: 1,7,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: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('service?')
option: Bytes('session_key')
option: Flag('shared?', autofill=True, default=False)
option: Str('user?')
option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None)
command: vault_show command: vault_show
args: 1,7,3 args: 1,7,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) arg: Str('cn', attribute=True, cli_name='name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, required=True)
@ -5205,6 +5261,15 @@ option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None) output: PrimaryKey('value', None, None)
command: vaultconfig_show
args: 0,4,3
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('transport_out?')
option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: PrimaryKey('value', None, None)
capability: messages 2.52 capability: messages 2.52
capability: optional_uid_params 2.54 capability: optional_uid_params 2.54
capability: permissions2 2.69 capability: permissions2 2.69

View File

@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
# # # #
######################################################## ########################################################
IPA_API_VERSION_MAJOR=2 IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=125 IPA_API_VERSION_MINOR=126
# Last change: derny - migration now accepts scope as argument # Last change: edewata - added vault-archive and vault-retrieve

View File

@ -17,16 +17,33 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import json
import os
import sys
import tempfile
import nss.nss as nss
import krbV
from ipalib.frontend import Command, Object, Local
from ipalib import api, errors from ipalib import api, errors
from ipalib import Str, Flag from ipalib import Bytes, Str, Flag
from ipalib import output from ipalib import output
from ipalib.crud import PKQuery, Retrieve, Update
from ipalib.plugable import Registry from ipalib.plugable import Registry
from ipalib.plugins.baseldap import LDAPObject, LDAPCreate, LDAPDelete,\ from ipalib.plugins.baseldap import LDAPObject, LDAPCreate, LDAPDelete,\
LDAPSearch, LDAPUpdate, LDAPRetrieve LDAPSearch, LDAPUpdate, LDAPRetrieve
from ipalib.request import context from ipalib.request import context
from ipalib.plugins.user import split_principal from ipalib.plugins.user import split_principal
from ipalib import _, ngettext from ipalib import _, ngettext
from ipaplatform.paths import paths
from ipapython.dn import DN from ipapython.dn import DN
from ipapython.nsslib import current_dbdir
if api.env.in_server:
import pki.account
import pki.key
__doc__ = _(""" __doc__ = _("""
Vaults Vaults
@ -94,6 +111,33 @@ EXAMPLES:
""") + _(""" """) + _("""
Delete a user vault: Delete a user vault:
ipa vault-del <name> --user <username> ipa vault-del <name> --user <username>
""") + _("""
Display vault configuration:
ipa vault-config
""") + _("""
Archive data into private vault:
ipa vault-archive <name> --in <input file>
""") + _("""
Archive data into service vault:
ipa vault-archive <name> --service <service name> --in <input file>
""") + _("""
Archive data into shared vault:
ipa vault-archive <name> --shared --in <input file>
""") + _("""
Archive data into user vault:
ipa vault-archive <name> --user <username> --in <input file>
""") + _("""
Retrieve data from private vault:
ipa vault-retrieve <name> --out <output file>
""") + _("""
Retrieve data from service vault:
ipa vault-retrieve <name> --service <service name> --out <output file>
""") + _("""
Retrieve data from shared vault:
ipa vault-retrieve <name> --shared --out <output file>
""") + _("""
Retrieve data from user vault:
ipa vault-retrieve <name> --user <user name> --out <output file>
""") """)
register = Registry() register = Registry()
@ -243,6 +287,26 @@ class vault(LDAPObject):
for entry in entries: for entry in entries:
self.backend.add_entry(entry) self.backend.add_entry(entry)
def get_key_id(self, dn):
"""
Generates a client key ID to archive/retrieve data in KRA.
"""
# TODO: create container_dn after object initialization then reuse it
container_dn = DN(self.container_dn, self.api.env.basedn)
# make sure the DN is a vault DN
if not dn.endswith(container_dn, 1):
raise ValueError('Invalid vault DN: %s' % dn)
# construct the vault ID from the bottom up
id = u''
for rdn in dn[:-len(container_dn)]:
name = rdn['cn']
id = u'/' + name + id
return 'ipa:' + id
@register() @register()
class vault_add(LDAPCreate): class vault_add(LDAPCreate):
@ -256,6 +320,10 @@ class vault_add(LDAPCreate):
**options): **options):
assert isinstance(dn, DN) assert isinstance(dn, DN)
if not self.api.env.enable_kra:
raise errors.InvocationError(
format=_('KRA service is not enabled'))
try: try:
parent_dn = DN(*dn[1:]) parent_dn = DN(*dn[1:])
self.obj.create_container(parent_dn) self.obj.create_container(parent_dn)
@ -273,6 +341,38 @@ class vault_del(LDAPDelete):
msg_summary = _('Deleted vault "%(value)s"') msg_summary = _('Deleted vault "%(value)s"')
def pre_callback(self, ldap, dn, *keys, **options):
assert isinstance(dn, DN)
if not self.api.env.enable_kra:
raise errors.InvocationError(
format=_('KRA service is not enabled'))
return dn
def post_callback(self, ldap, dn, *args, **options):
assert isinstance(dn, DN)
kra_client = self.api.Backend.kra.get_client()
kra_account = pki.account.AccountClient(kra_client.connection)
kra_account.login()
client_key_id = self.obj.get_key_id(dn)
# deactivate vault record in KRA
response = kra_client.keys.list_keys(
client_key_id, pki.key.KeyClient.KEY_STATUS_ACTIVE)
for key_info in response.key_infos:
kra_client.keys.modify_key_status(
key_info.get_key_id(),
pki.key.KeyClient.KEY_STATUS_INACTIVE)
kra_account.logout()
return True
@register() @register()
class vault_find(LDAPSearch): class vault_find(LDAPSearch):
@ -290,6 +390,10 @@ class vault_find(LDAPSearch):
**options): **options):
assert isinstance(base_dn, DN) assert isinstance(base_dn, DN)
if not self.api.env.enable_kra:
raise errors.InvocationError(
format=_('KRA service is not enabled'))
base_dn = self.obj.get_dn(*args, **options) base_dn = self.obj.get_dn(*args, **options)
return (filter, base_dn, scope) return (filter, base_dn, scope)
@ -313,9 +417,399 @@ class vault_mod(LDAPUpdate):
msg_summary = _('Modified vault "%(value)s"') msg_summary = _('Modified vault "%(value)s"')
def pre_callback(self, ldap, dn, entry_attrs, attrs_list,
*keys, **options):
assert isinstance(dn, DN)
if not self.api.env.enable_kra:
raise errors.InvocationError(
format=_('KRA service is not enabled'))
return dn
@register() @register()
class vault_show(LDAPRetrieve): class vault_show(LDAPRetrieve):
__doc__ = _('Display information about a vault.') __doc__ = _('Display information about a vault.')
takes_options = LDAPRetrieve.takes_options + vault_options takes_options = LDAPRetrieve.takes_options + vault_options
def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
assert isinstance(dn, DN)
if not self.api.env.enable_kra:
raise errors.InvocationError(
format=_('KRA service is not enabled'))
return dn
@register()
class vaultconfig(Object):
__doc__ = _('Vault configuration')
takes_params = (
Bytes(
'transport_cert',
label=_('Transport Certificate'),
),
)
@register()
class vaultconfig_show(Retrieve):
__doc__ = _('Show vault configuration.')
takes_options = (
Str(
'transport_out?',
doc=_('Output file to store the transport certificate'),
),
)
def forward(self, *args, **options):
file = options.get('transport_out')
# don't send these parameters to server
if 'transport_out' in options:
del options['transport_out']
response = super(vaultconfig_show, self).forward(*args, **options)
if file:
with open(file, 'w') as f:
f.write(response['result']['transport_cert'])
return response
def execute(self, *args, **options):
if not self.api.env.enable_kra:
raise errors.InvocationError(
format=_('KRA service is not enabled'))
kra_client = self.api.Backend.kra.get_client()
transport_cert = kra_client.system_certs.get_transport_cert()
return {
'result': {
'transport_cert': transport_cert.binary
},
'value': None,
}
@register()
class vault_archive(PKQuery, Local):
__doc__ = _('Archive data into a vault.')
takes_options = vault_options + (
Bytes(
'data?',
doc=_('Binary data to archive'),
),
Str( # TODO: use File parameter
'in?',
doc=_('File containing data to archive'),
),
)
has_output = output.standard_entry
msg_summary = _('Archived data into vault "%(value)s"')
def forward(self, *args, **options):
data = options.get('data')
input_file = options.get('in')
# don't send these parameters to server
if 'data' in options:
del options['data']
if 'in' in options:
del options['in']
# get data
if data and input_file:
raise errors.MutuallyExclusiveError(
reason=_('Input data specified multiple times'))
if input_file:
with open(input_file, 'rb') as f:
data = f.read()
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())
# initialize NSS database
current_dbdir = paths.IPA_NSSDB_DIR
nss.nss_init(current_dbdir)
# retrieve transport certificate
config = self.api.Command.vaultconfig_show()
transport_cert_der = config['result']['transport_cert']
nss_transport_cert = nss.Certificate(transport_cert_der)
# generate session key
mechanism = nss.CKM_DES3_CBC_PAD
slot = nss.get_best_slot(mechanism)
key_length = slot.get_best_key_length(mechanism)
session_key = slot.key_gen(mechanism, None, key_length)
# wrap session key with transport certificate
public_key = nss_transport_cert.subject_public_key_info.public_key
wrapped_session_key = nss.pub_wrap_sym_key(mechanism,
public_key,
session_key)
options['session_key'] = wrapped_session_key.data
nonce_length = nss.get_iv_length(mechanism)
nonce = nss.generate_random(nonce_length)
options['nonce'] = nonce
vault_data = {}
vault_data[u'data'] = base64.b64encode(data).decode('utf-8')
json_vault_data = json.dumps(vault_data)
# wrap vault_data with session key
iv_si = nss.SecItem(nonce)
iv_param = nss.param_from_iv(mechanism, iv_si)
encoding_ctx = nss.create_context_by_sym_key(mechanism,
nss.CKA_ENCRYPT,
session_key,
iv_param)
wrapped_vault_data = encoding_ctx.cipher_op(json_vault_data)\
+ encoding_ctx.digest_final()
options['vault_data'] = wrapped_vault_data
response = self.api.Command.vault_archive_encrypted(*args, **options)
response['result'] = {}
del response['summary']
return response
@register()
class vault_archive_encrypted(Update):
NO_CLI = True
takes_options = vault_options + (
Bytes(
'session_key',
doc=_('Session key wrapped with transport certificate'),
),
Bytes(
'vault_data',
doc=_('Vault data encrypted with session key'),
),
Bytes(
'nonce',
doc=_('Nonce'),
),
)
def execute(self, *args, **options):
if not self.api.env.enable_kra:
raise errors.InvocationError(
format=_('KRA service is not enabled'))
wrapped_vault_data = options.pop('vault_data')
nonce = options.pop('nonce')
wrapped_session_key = options.pop('session_key')
# retrieve vault info
result = self.api.Command.vault_show(*args, **options)
vault = result['result']
# connect to KRA
kra_client = self.api.Backend.kra.get_client()
kra_account = pki.account.AccountClient(kra_client.connection)
kra_account.login()
client_key_id = self.obj.get_key_id(vault['dn'])
# deactivate existing vault record in KRA
response = kra_client.keys.list_keys(
client_key_id,
pki.key.KeyClient.KEY_STATUS_ACTIVE)
for key_info in response.key_infos:
kra_client.keys.modify_key_status(
key_info.get_key_id(),
pki.key.KeyClient.KEY_STATUS_INACTIVE)
# forward wrapped data to KRA
kra_client.keys.archive_encrypted_data(
client_key_id,
pki.key.KeyClient.PASS_PHRASE_TYPE,
wrapped_vault_data,
wrapped_session_key,
None,
nonce,
)
kra_account.logout()
return result
@register()
class vault_retrieve(PKQuery, Local):
__doc__ = _('Retrieve a data from a vault.')
takes_options = vault_options + (
Str(
'out?',
doc=_('File to store retrieved data'),
),
)
has_output = output.standard_entry
has_output_params = (
Bytes(
'data',
label=_('Data'),
),
)
msg_summary = _('Retrieved data from vault "%(value)s"')
def forward(self, *args, **options):
output_file = options.get('out')
# don't send these parameters to server
if 'out' in options:
del options['out']
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())
# initialize NSS database
current_dbdir = paths.IPA_NSSDB_DIR
nss.nss_init(current_dbdir)
# retrieve transport certificate
config = self.api.Command.vaultconfig_show()
transport_cert_der = config['result']['transport_cert']
nss_transport_cert = nss.Certificate(transport_cert_der)
# generate session key
mechanism = nss.CKM_DES3_CBC_PAD
slot = nss.get_best_slot(mechanism)
key_length = slot.get_best_key_length(mechanism)
session_key = slot.key_gen(mechanism, None, key_length)
# wrap session key with transport certificate
public_key = nss_transport_cert.subject_public_key_info.public_key
wrapped_session_key = nss.pub_wrap_sym_key(mechanism,
public_key,
session_key)
# send retrieval request to server
options['session_key'] = wrapped_session_key.data
response = self.api.Command.vault_retrieve_encrypted(*args, **options)
result = response['result']
nonce = result['nonce']
# unwrap data with session key
wrapped_vault_data = result['vault_data']
iv_si = nss.SecItem(nonce)
iv_param = nss.param_from_iv(mechanism, iv_si)
decoding_ctx = nss.create_context_by_sym_key(mechanism,
nss.CKA_DECRYPT,
session_key,
iv_param)
json_vault_data = decoding_ctx.cipher_op(wrapped_vault_data)\
+ decoding_ctx.digest_final()
vault_data = json.loads(json_vault_data)
data = base64.b64decode(vault_data[u'data'].encode('utf-8'))
if output_file:
with open(output_file, 'w') as f:
f.write(data)
response['result'] = {'data': data}
del response['summary']
return response
@register()
class vault_retrieve_encrypted(Retrieve):
NO_CLI = True
takes_options = vault_options + (
Bytes(
'session_key',
doc=_('Session key wrapped with transport certificate'),
),
)
def execute(self, *args, **options):
if not self.api.env.enable_kra:
raise errors.InvocationError(
format=_('KRA service is not enabled'))
wrapped_session_key = options.pop('session_key')
# retrieve vault info
result = self.api.Command.vault_show(*args, **options)
vault = result['result']
# connect to KRA
kra_client = self.api.Backend.kra.get_client()
kra_account = pki.account.AccountClient(kra_client.connection)
kra_account.login()
client_key_id = self.obj.get_key_id(vault['dn'])
# find vault record in KRA
response = kra_client.keys.list_keys(
client_key_id,
pki.key.KeyClient.KEY_STATUS_ACTIVE)
if not len(response.key_infos):
raise errors.NotFound(reason=_('No archived data.'))
key_info = response.key_infos[0]
# retrieve encrypted data from KRA
key = kra_client.keys.retrieve_key(
key_info.get_key_id(),
wrapped_session_key)
vault['vault_data'] = key.encrypted_data
vault['nonce'] = key.nonce_data
kra_account.logout()
return result

View File

@ -22,12 +22,15 @@ Test the `ipalib/plugins/vault.py` module.
""" """
from ipalib import api, errors from ipalib import api, errors
from xmlrpc_test import Declarative, fuzzy_string from xmlrpc_test import Declarative
vault_name = u'test_vault' vault_name = u'test_vault'
service_name = u'HTTP/server.example.com' service_name = u'HTTP/server.example.com'
user_name = u'testuser' user_name = u'testuser'
# binary data from \x00 to \xff
secret = ''.join(map(chr, xrange(0, 256)))
class test_vault_plugin(Declarative): class test_vault_plugin(Declarative):
@ -442,4 +445,71 @@ class test_vault_plugin(Declarative):
}, },
}, },
{
'desc': 'Create vault for archival',
'command': (
'vault_add',
[vault_name],
{},
),
'expected': {
'value': vault_name,
'summary': 'Added vault "%s"' % vault_name,
'result': {
'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s'
% (vault_name, api.env.basedn),
'objectclass': [u'top', u'ipaVault'],
'cn': [vault_name],
},
},
},
{
'desc': 'Archive secret',
'command': (
'vault_archive',
[vault_name],
{
'data': secret,
},
),
'expected': {
'value': vault_name,
'summary': 'Archived data into vault "%s"' % vault_name,
'result': {},
},
},
{
'desc': 'Retrieve secret',
'command': (
'vault_retrieve',
[vault_name],
{},
),
'expected': {
'value': vault_name,
'summary': 'Retrieved data from vault "%s"' % vault_name,
'result': {
'data': secret,
},
},
},
{
'desc': 'Delete vault for archival',
'command': (
'vault_del',
[vault_name],
{},
),
'expected': {
'value': [vault_name],
'summary': u'Deleted vault "%s"' % vault_name,
'result': {
'failed': (),
},
},
},
] ]

View File

@ -62,6 +62,7 @@ class IPATypeChecker(TypeChecker):
'unittest.case': ['assertEqual', 'assertRaises'], 'unittest.case': ['assertEqual', 'assertRaises'],
'nose.tools': ['assert_equal', 'assert_raises'], 'nose.tools': ['assert_equal', 'assert_raises'],
'datetime.tzinfo': ['houroffset', 'minoffset', 'utcoffset', 'dst'], 'datetime.tzinfo': ['houroffset', 'minoffset', 'utcoffset', 'dst'],
'nss.nss.subject_public_key_info': ['public_key'],
# IPA classes # IPA classes
'ipalib.base.NameSpace': ['add', 'mod', 'del', 'show', 'find'], 'ipalib.base.NameSpace': ['add', 'mod', 'del', 'show', 'find'],