mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
parent
e01095dfb3
commit
df1bd39a43
65
API.txt
65
API.txt
@ -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
|
||||||
|
4
VERSION
4
VERSION
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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': (),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -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'],
|
||||||
|
Loading…
Reference in New Issue
Block a user