# Authors: # Endi S. Dewata # # Copyright (C) 2015 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import six from ipalib.frontend import Command, Object from ipalib import api, errors from ipalib import Bytes, Flag, Str, StrEnum from ipalib import output from ipalib.constants import ( VAULT_WRAPPING_SUPPORTED_ALGOS, VAULT_WRAPPING_DEFAULT_ALGO, VAULT_WRAPPING_3DES, VAULT_WRAPPING_AES128_CBC, ) from ipalib.crud import PKQuery, Retrieve from ipalib.parameters import Principal from ipalib.plugable import Registry from .baseldap import LDAPObject, LDAPCreate, LDAPDelete,\ LDAPSearch, LDAPUpdate, LDAPRetrieve, LDAPAddMember, LDAPRemoveMember,\ LDAPModMember, pkey_to_value from ipalib.request import context from .service import normalize_principal, validate_realm from ipalib import _, ngettext from ipapython import kerberos from ipapython.dn import DN from ipaserver.masters import is_service_enabled if api.env.in_server: import pki.account import pki.key from pki.crypto import DES_EDE3_CBC_OID from pki.crypto import AES_128_CBC_OID from pki import PKIException if six.PY3: unicode = str __doc__ = _(""" Vaults """) + _(""" Manage vaults. """) + _(""" Vault is a secure place to store a secret. One vault can only store one secret. When archiving a secret in a vault, the existing secret (if any) is overwritten. """) + _(""" Based on the ownership there are three vault categories: * user/private vault * service vault * shared vault """) + _(""" User vaults are vaults owned used by a particular user. Private vaults are vaults owned the current user. Service vaults are vaults owned by a service. Shared vaults are owned by the admin but they can be used by other users or services. """) + _(""" Based on the security mechanism there are three types of vaults: * standard vault * symmetric vault * asymmetric vault """) + _(""" Standard vault uses a secure mechanism to transport and store the secret. The secret can only be retrieved by users that have access to the vault. """) + _(""" Symmetric vault is similar to the standard vault, but it pre-encrypts the secret using a password before transport. The secret can only be retrieved using the same password. """) + _(""" Asymmetric vault is similar to the standard vault, but it pre-encrypts the secret using a public key before transport. The secret can only be retrieved using the private key. """) + _(""" EXAMPLES: """) + _(""" List vaults: ipa vault-find [--user |--service |--shared] """) + _(""" Add a standard vault: ipa vault-add [--user |--service |--shared] --type standard """) + _(""" Add a symmetric vault: ipa vault-add [--user |--service |--shared] --type symmetric --password-file password.txt """) + _(""" Add an asymmetric vault: ipa vault-add [--user |--service |--shared] --type asymmetric --public-key-file public.pem """) + _(""" Show a vault: ipa vault-show [--user |--service |--shared] """) + _(""" Modify vault description: ipa vault-mod [--user |--service |--shared] --desc """) + _(""" Modify vault type: ipa vault-mod [--user |--service |--shared] --type [old password/private key] [new password/public key] """) + _(""" Modify symmetric vault password: ipa vault-mod [--user |--service |--shared] --change-password ipa vault-mod [--user |--service |--shared] --old-password --new-password ipa vault-mod [--user |--service |--shared] --old-password-file --new-password-file """) + _(""" Modify asymmetric vault keys: ipa vault-mod [--user |--service |--shared] --private-key-file --public-key-file """) + _(""" Delete a vault: ipa vault-del [--user |--service |--shared] """) + _(""" Display vault configuration: ipa vaultconfig-show """) + _(""" Archive data into standard vault: ipa vault-archive [--user |--service |--shared] --in """) + _(""" Archive data into symmetric vault: ipa vault-archive [--user |--service |--shared] --in --password-file password.txt """) + _(""" Archive data into asymmetric vault: ipa vault-archive [--user |--service |--shared] --in """) + _(""" Retrieve data from standard vault: ipa vault-retrieve [--user |--service |--shared] --out """) + _(""" Retrieve data from symmetric vault: ipa vault-retrieve [--user |--service |--shared] --out --password-file password.txt """) + _(""" Retrieve data from asymmetric vault: ipa vault-retrieve [--user |--service |--shared] --out --private-key-file private.pem """) + _(""" Add vault owners: ipa vault-add-owner [--user |--service |--shared] [--users ] [--groups ] [--services ] """) + _(""" Delete vault owners: ipa vault-remove-owner [--user |--service |--shared] [--users ] [--groups ] [--services ] """) + _(""" Add vault members: ipa vault-add-member [--user |--service |--shared] [--users ] [--groups ] [--services ] """) + _(""" Delete vault members: ipa vault-remove-member [--user |--service |--shared] [--users ] [--groups ] [--services ] """) register = Registry() vault_options = ( Principal( 'service?', validate_realm, doc=_('Service name of the service vault'), normalizer=normalize_principal, ), Flag( 'shared?', doc=_('Shared vault'), ), Str( 'username?', cli_name='user', doc=_('Username of the user vault'), ), ) class VaultModMember(LDAPModMember): def get_options(self): for param in super(VaultModMember, self).get_options(): if param.name == 'service' and param not in vault_options: param = param.clone_rename('services') yield param def get_member_dns(self, **options): if 'services' in options: options['service'] = options.pop('services') else: options.pop('service', None) return super(VaultModMember, self).get_member_dns(**options) def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): for fail in failed.values(): fail['services'] = fail.pop('service', []) self.obj.get_container_attribute(entry_attrs, options) return completed, dn @register() class vaultcontainer(LDAPObject): __doc__ = _(""" Vault Container object. """) container_dn = api.env.container_vault object_name = _('vaultcontainer') object_name_plural = _('vaultcontainers') object_class = ['ipaVaultContainer'] permission_filter_objectclasses = ['ipaVaultContainer'] attribute_members = { 'owner': ['user', 'group', 'service'], } label = _('Vault Containers') label_singular = _('Vault Container') managed_permissions = { 'System: Read Vault Containers': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'description', 'owner', }, 'default_privileges': {'Vault Administrators'}, }, 'System: Add Vault Containers': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'add'}, 'default_privileges': {'Vault Administrators'}, }, 'System: Delete Vault Containers': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'delete'}, 'default_privileges': {'Vault Administrators'}, }, 'System: Modify Vault Containers': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'description', }, 'default_privileges': {'Vault Administrators'}, }, 'System: Manage Vault Container Ownership': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'owner', }, 'default_privileges': {'Vault Administrators'}, }, } takes_params = ( Str( 'owner_user?', label=_('Owner users'), ), Str( 'owner_group?', label=_('Owner groups'), ), Str( 'owner_service?', label=_('Owner services'), ), Str( 'owner?', label=_('Failed owners'), ), Str( 'service?', label=_('Vault service'), flags={'virtual_attribute'}, ), Flag( 'shared?', label=_('Shared vault'), flags={'virtual_attribute'}, ), Str( 'username?', label=_('Vault user'), flags={'virtual_attribute'}, ), ) def get_dn(self, *keys, **options): """ Generates vault DN from parameters. """ service = options.get('service') shared = options.get('shared') user = options.get('username') count = (bool(service) + bool(shared) + bool(user)) if count > 1: raise errors.MutuallyExclusiveError( reason=_('Service, shared and user options ' + 'cannot be specified simultaneously')) parent_dn = super(vaultcontainer, self).get_dn(*keys, **options) if not count: principal = kerberos.Principal(getattr(context, 'principal')) if principal.is_host: raise errors.NotImplementedError( reason=_('Host is not supported')) elif principal.is_service: service = unicode(principal) else: user = principal.username if service: dn = DN(('cn', service), ('cn', 'services'), parent_dn) elif shared: dn = DN(('cn', 'shared'), parent_dn) elif user: dn = DN(('cn', user), ('cn', 'users'), parent_dn) else: raise RuntimeError return dn def get_container_attribute(self, entry, options): if options.get('raw', False): return container_dn = DN(self.container_dn, self.api.env.basedn) if entry.dn.endswith(DN(('cn', 'services'), container_dn)): entry['service'] = entry.dn[0]['cn'] elif entry.dn.endswith(DN(('cn', 'shared'), container_dn)): entry['shared'] = True elif entry.dn.endswith(DN(('cn', 'users'), container_dn)): entry['username'] = entry.dn[0]['cn'] @register() class vaultcontainer_show(LDAPRetrieve): __doc__ = _('Display information about a vault container.') takes_options = LDAPRetrieve.takes_options + vault_options has_output_params = LDAPRetrieve.has_output_params def pre_callback(self, ldap, dn, attrs_list, *keys, **options): assert isinstance(dn, DN) if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): self.obj.get_container_attribute(entry_attrs, options) return dn @register() class vaultcontainer_del(LDAPDelete): __doc__ = _('Delete a vault container.') takes_options = LDAPDelete.takes_options + vault_options msg_summary = _('Deleted vault container') subtree_delete = False def pre_callback(self, ldap, dn, *keys, **options): assert isinstance(dn, DN) if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) return dn def execute(self, *keys, **options): keys = keys + (u'',) return super(vaultcontainer_del, self).execute(*keys, **options) @register() class vaultcontainer_add_owner(VaultModMember, LDAPAddMember): __doc__ = _('Add owners to a vault container.') takes_options = LDAPAddMember.takes_options + vault_options member_attributes = ['owner'] member_param_label = _('owner %s') member_count_out = ('%i owner added.', '%i owners added.') has_output = ( output.Entry('result'), output.Output( 'failed', type=dict, doc=_('Owners that could not be added'), ), output.Output( 'completed', type=int, doc=_('Number of owners added'), ), ) @register() class vaultcontainer_remove_owner(VaultModMember, LDAPRemoveMember): __doc__ = _('Remove owners from a vault container.') takes_options = LDAPRemoveMember.takes_options + vault_options member_attributes = ['owner'] member_param_label = _('owner %s') member_count_out = ('%i owner removed.', '%i owners removed.') has_output = ( output.Entry('result'), output.Output( 'failed', type=dict, doc=_('Owners that could not be removed'), ), output.Output( 'completed', type=int, doc=_('Number of owners removed'), ), ) @register() class vault(LDAPObject): __doc__ = _(""" Vault object. """) container_dn = api.env.container_vault object_name = _('vault') object_name_plural = _('vaults') object_class = ['ipaVault'] permission_filter_objectclasses = ['ipaVault'] default_attributes = [ 'cn', 'description', 'ipavaulttype', 'ipavaultsalt', 'ipavaultpublickey', 'owner', 'member', ] search_display_attributes = [ 'cn', 'description', 'ipavaulttype', ] attribute_members = { 'owner': ['user', 'group', 'service'], 'member': ['user', 'group', 'service'], } label = _('Vaults') label_singular = _('Vault') managed_permissions = { 'System: Read Vaults': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'description', 'ipavaulttype', 'ipavaultsalt', 'ipavaultpublickey', 'owner', 'member', 'memberuser', 'memberhost', }, 'default_privileges': {'Vault Administrators'}, }, 'System: Add Vaults': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'add'}, 'default_privileges': {'Vault Administrators'}, }, 'System: Delete Vaults': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'delete'}, 'default_privileges': {'Vault Administrators'}, }, 'System: Modify Vaults': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'description', 'ipavaulttype', 'ipavaultsalt', 'ipavaultpublickey', }, 'default_privileges': {'Vault Administrators'}, }, 'System: Manage Vault Ownership': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'owner', }, 'default_privileges': {'Vault Administrators'}, }, 'System: Manage Vault Membership': { 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN(api.env.container_vault, api.env.basedn), 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'member', }, 'default_privileges': {'Vault Administrators'}, }, } takes_params = ( Str( 'cn', cli_name='name', label=_('Vault name'), primary_key=True, pattern='^[a-zA-Z0-9_.-]+$', pattern_errmsg='may only include letters, numbers, _, ., and -', maxlength=255, ), Str( 'description?', cli_name='desc', label=_('Description'), 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, ), Bytes( 'ipavaultsalt?', cli_name='salt', label=_('Salt'), doc=_('Vault salt'), flags=['no_search'], ), Bytes( 'ipavaultpublickey?', cli_name='public_key', label=_('Public key'), doc=_('Vault public key'), flags=['no_search'], ), Str( 'owner_user?', label=_('Owner users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'owner_group?', label=_('Owner groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'owner_service?', label=_('Owner services'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'owner?', label=_('Failed owners'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'service?', label=_('Vault service'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Flag( 'shared?', label=_('Shared vault'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'username?', label=_('Vault user'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), ) def _translate_algorithm(self, name): if name is None: name = VAULT_WRAPPING_DEFAULT_ALGO if name not in VAULT_WRAPPING_SUPPORTED_ALGOS: msg = _("{algo} is not a supported vault wrapping algorithm") raise errors.ValidationError(msg.format(algo=name)) if name == VAULT_WRAPPING_3DES: return DES_EDE3_CBC_OID elif name == VAULT_WRAPPING_AES128_CBC: return AES_128_CBC_OID else: # unreachable raise ValueError(name) def get_dn(self, *keys, **options): """ Generates vault DN from parameters. """ service = options.get('service') shared = options.get('shared') user = options.get('username') count = (bool(service) + bool(shared) + bool(user)) if count > 1: raise errors.MutuallyExclusiveError( reason=_('Service, shared, and user options ' + 'cannot be specified simultaneously')) # TODO: create container_dn after object initialization then reuse it container_dn = DN(self.container_dn, self.api.env.basedn) dn = super(vault, self).get_dn(*keys, **options) assert dn.endswith(container_dn) rdns = DN(*dn[:-len(container_dn)]) if not count: principal = kerberos.Principal(getattr(context, 'principal')) if principal.is_host: raise errors.NotImplementedError( reason=_('Host is not supported')) elif principal.is_service: service = unicode(principal) else: user = principal.username if service: parent_dn = DN(('cn', service), ('cn', 'services'), container_dn) elif shared: parent_dn = DN(('cn', 'shared'), container_dn) elif user: parent_dn = DN(('cn', user), ('cn', 'users'), container_dn) else: raise RuntimeError return DN(rdns, parent_dn) def create_container(self, dn, owner_dn): """ Creates vault container and its parents. """ # TODO: create container_dn after object initialization then reuse it container_dn = DN(self.container_dn, self.api.env.basedn) entries = [] while dn: assert dn.endswith(container_dn) rdn = dn[0] entry = self.backend.make_entry( dn, { 'objectclass': ['ipaVaultContainer'], 'cn': rdn['cn'], 'owner': [owner_dn], }) # if entry can be added, return try: self.backend.add_entry(entry) break except errors.NotFound: pass # otherwise, create parent entry first dn = DN(*dn[1:]) entries.insert(0, entry) # then create the entries again for entry in entries: 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 def get_container_attribute(self, entry, options): if options.get('raw', False): return container_dn = DN(self.container_dn, self.api.env.basedn) if entry.dn.endswith(DN(('cn', 'services'), container_dn)): entry['service'] = entry.dn[1]['cn'] elif entry.dn.endswith(DN(('cn', 'shared'), container_dn)): entry['shared'] = True elif entry.dn.endswith(DN(('cn', 'users'), container_dn)): entry['username'] = entry.dn[1]['cn'] @register() class vault_add_internal(LDAPCreate): __doc__ = _('Add a vault.') NO_CLI = True takes_options = LDAPCreate.takes_options + vault_options msg_summary = _('Added vault "%(value)s"') def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) principal = kerberos.Principal(getattr(context, 'principal')) if principal.is_service: owner_dn = self.api.Object.service.get_dn(unicode(principal)) else: owner_dn = self.api.Object.user.get_dn(principal.username) parent_dn = DN(*dn[1:]) try: self.obj.create_container(parent_dn, owner_dn) except (errors.DuplicateEntry, errors.ACIError): pass # vault should be owned by the creator entry_attrs['owner'] = owner_dn return dn def post_callback(self, ldap, dn, entry, *keys, **options): self.obj.get_container_attribute(entry, options) return dn @register() class vault_del(LDAPDelete): __doc__ = _('Delete a vault.') takes_options = LDAPDelete.takes_options + vault_options msg_summary = _('Deleted vault "%(value)s"') def pre_callback(self, ldap, dn, *keys, **options): assert isinstance(dn, DN) if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) return dn def post_callback(self, ldap, dn, *args, **options): assert isinstance(dn, DN) with self.api.Backend.kra.get_client() as kra_client: # pylint: disable=used-before-assignment kra_account = pki.account.AccountClient(kra_client.connection) # pylint: enable=used-before-assignment 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() class vault_find(LDAPSearch): __doc__ = _('Search for vaults.') takes_options = LDAPSearch.takes_options + vault_options + ( Flag( 'services?', doc=_('List all service vaults'), ), Flag( 'users?', doc=_('List all user vaults'), ), ) has_output_params = LDAPSearch.has_output_params msg_summary = ngettext( '%(count)d vault matched', '%(count)d vaults matched', 0, ) def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options): assert isinstance(base_dn, DN) if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) if options.get('users') or options.get('services'): mutex = ['service', 'services', 'shared', 'username', 'users'] count = sum(bool(options.get(option)) for option in mutex) if count > 1: raise errors.MutuallyExclusiveError( reason=_('Service(s), shared, and user(s) options ' + 'cannot be specified simultaneously')) scope = ldap.SCOPE_SUBTREE container_dn = DN(self.obj.container_dn, self.api.env.basedn) if options.get('services'): base_dn = DN(('cn', 'services'), container_dn) else: base_dn = DN(('cn', 'users'), container_dn) else: base_dn = self.obj.get_dn(None, **options) return filter, base_dn, scope def post_callback(self, ldap, entries, truncated, *args, **options): for entry in entries: self.obj.get_container_attribute(entry, options) return truncated def exc_callback(self, args, options, exc, call_func, *call_args, **call_kwargs): if call_func.__name__ == 'find_entries': if isinstance(exc, errors.NotFound): # ignore missing containers since they will be created # automatically on vault creation. raise errors.EmptyResult(reason=str(exc)) raise exc @register() class vault_mod_internal(LDAPUpdate): __doc__ = _('Modify a vault.') NO_CLI = True takes_options = LDAPUpdate.takes_options + vault_options 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.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): self.obj.get_container_attribute(entry_attrs, options) return dn @register() class vault_show(LDAPRetrieve): __doc__ = _('Display information about a vault.') takes_options = LDAPRetrieve.takes_options + vault_options has_output_params = LDAPRetrieve.has_output_params def pre_callback(self, ldap, dn, attrs_list, *keys, **options): assert isinstance(dn, DN) if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): self.obj.get_container_attribute(entry_attrs, options) return dn @register() class vaultconfig(Object): __doc__ = _('Vault configuration') takes_params = ( Bytes( 'transport_cert', label=_('Transport Certificate'), ), Str( 'kra_server_server*', label=_('IPA KRA servers'), doc=_('IPA servers configured as key recovery agents'), flags={'virtual_attribute', 'no_create', 'no_update'} ) ) @register() class vaultconfig_show(Retrieve): __doc__ = _('Show vault configuration.') takes_options = ( Str( 'transport_out?', doc=_('Output file to store the transport certificate'), ), ) def execute(self, *args, **options): if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) config = dict( wrapping_supported_algorithms=VAULT_WRAPPING_SUPPORTED_ALGOS, wrapping_default_algorithm=VAULT_WRAPPING_DEFAULT_ALGO, ) with self.api.Backend.kra.get_client() as kra_client: transport_cert = kra_client.system_certs.get_transport_cert() config['transport_cert'] = transport_cert.binary self.api.Object.config.show_servroles_attributes( config, "KRA server", **options) return { 'result': config, 'value': None, } @register() class vault_archive_internal(PKQuery): __doc__ = _('Archive data into a vault.') 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'), ), StrEnum( 'wrapping_algo?', doc=_('Key wrapping algorithm'), values=VAULT_WRAPPING_SUPPORTED_ALGOS, default=VAULT_WRAPPING_3DES, autofill=True, ), ) has_output = output.standard_entry msg_summary = _('Archived data into vault "%(value)s"') def execute(self, *args, **options): if not self.api.Command.kra_is_enabled()['result']: 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') wrapping_algo = options.pop('wrapping_algo', None) algorithm_oid = self.obj._translate_algorithm(wrapping_algo) # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] # connect to KRA with self.api.Backend.kra.get_client() as kra_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 try: kra_client.keys.archive_encrypted_data( client_key_id, pki.key.KeyClient.PASS_PHRASE_TYPE, wrapped_vault_data, wrapped_session_key, algorithm_oid=algorithm_oid, nonce_iv=nonce, ) except PKIException as e: kra_account.logout() raise errors.EncodingError( message=_("Unable to archive key: %s") % e) finally: kra_account.logout() response = { 'value': args[-1], 'result': {}, } response['summary'] = self.msg_summary % response return response @register() class vault_retrieve_internal(PKQuery): __doc__ = _('Retrieve data from a vault.') NO_CLI = True takes_options = vault_options + ( Bytes( 'session_key', doc=_('Session key wrapped with transport certificate'), ), StrEnum( 'wrapping_algo?', doc=_('Key wrapping algorithm'), values=VAULT_WRAPPING_SUPPORTED_ALGOS, default=VAULT_WRAPPING_3DES, autofill=True, ), ) has_output = output.standard_entry msg_summary = _('Retrieved data from vault "%(value)s"') def execute(self, *args, **options): if not self.api.Command.kra_is_enabled()['result']: raise errors.InvocationError( format=_('KRA service is not enabled')) wrapped_session_key = options.pop('session_key') wrapping_algo = options.pop('wrapping_algo', None) algorithm_oid = self.obj._translate_algorithm(wrapping_algo) # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] # connect to KRA with self.api.Backend.kra.get_client() as kra_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] # XXX hack kra_client.keys.encrypt_alg_oid = algorithm_oid # retrieve encrypted data from KRA try: key = kra_client.keys.retrieve_key( key_info.get_key_id(), wrapped_session_key) except PKIException as e: kra_account.logout() raise errors.EncodingError( message=_("Unable to retrieve key: %s") % e) finally: kra_account.logout() response = { 'value': args[-1], 'result': { 'vault_data': key.encrypted_data, 'nonce': key.nonce_data, }, } response['summary'] = self.msg_summary % response return response @register() class vault_add_owner(VaultModMember, LDAPAddMember): __doc__ = _('Add owners to a vault.') takes_options = LDAPAddMember.takes_options + vault_options member_attributes = ['owner'] member_param_label = _('owner %s') member_count_out = ('%i owner added.', '%i owners added.') has_output = ( output.Entry('result'), output.Output( 'failed', type=dict, doc=_('Owners that could not be added'), ), output.Output( 'completed', type=int, doc=_('Number of owners added'), ), ) @register() class vault_remove_owner(VaultModMember, LDAPRemoveMember): __doc__ = _('Remove owners from a vault.') takes_options = LDAPRemoveMember.takes_options + vault_options member_attributes = ['owner'] member_param_label = _('owner %s') member_count_out = ('%i owner removed.', '%i owners removed.') has_output = ( output.Entry('result'), output.Output( 'failed', type=dict, doc=_('Owners that could not be removed'), ), output.Output( 'completed', type=int, doc=_('Number of owners removed'), ), ) @register() class vault_add_member(VaultModMember, LDAPAddMember): __doc__ = _('Add members to a vault.') takes_options = LDAPAddMember.takes_options + vault_options @register() class vault_remove_member(VaultModMember, LDAPRemoveMember): __doc__ = _('Remove members from a vault.') takes_options = LDAPRemoveMember.takes_options + vault_options @register() class kra_is_enabled(Command): __doc__ = _('Checks if any of the servers has the KRA service enabled') NO_CLI = True has_output = output.standard_value def execute(self, *args, **options): result = is_service_enabled('KRA', conn=self.api.Backend.ldap2) return dict(result=result, value=pkey_to_value(None, options))