Source code for ipaserver.plugins.vault

# Authors:
#   Endi S. Dewata <edewata@redhat.com>
#
# 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 <http://www.gnu.org/licenses/>.

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

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 <user>|--service <service>|--shared]
""") + _("""
 Add a standard vault:
   ipa vault-add <name>
       [--user <user>|--service <service>|--shared]
       --type standard
""") + _("""
 Add a symmetric vault:
   ipa vault-add <name>
       [--user <user>|--service <service>|--shared]
       --type symmetric --password-file password.txt
""") + _("""
 Add an asymmetric vault:
   ipa vault-add <name>
       [--user <user>|--service <service>|--shared]
       --type asymmetric --public-key-file public.pem
""") + _("""
 Show a vault:
   ipa vault-show <name>
       [--user <user>|--service <service>|--shared]
""") + _("""
 Modify vault description:
   ipa vault-mod <name>
       [--user <user>|--service <service>|--shared]
       --desc <description>
""") + _("""
 Modify vault type:
   ipa vault-mod <name>
       [--user <user>|--service <service>|--shared]
       --type <type>
       [old password/private key]
       [new password/public key]
""") + _("""
 Modify symmetric vault password:
   ipa vault-mod <name>
       [--user <user>|--service <service>|--shared]
       --change-password
   ipa vault-mod <name>
       [--user <user>|--service <service>|--shared]
       --old-password <old password>
       --new-password <new password>
   ipa vault-mod <name>
       [--user <user>|--service <service>|--shared]
       --old-password-file <old password file>
       --new-password-file <new password file>
""") + _("""
 Modify asymmetric vault keys:
   ipa vault-mod <name>
       [--user <user>|--service <service>|--shared]
       --private-key-file <old private key file>
       --public-key-file <new public key file>
""") + _("""
 Delete a vault:
   ipa vault-del <name>
       [--user <user>|--service <service>|--shared]
""") + _("""
 Display vault configuration:
   ipa vaultconfig-show
""") + _("""
 Archive data into standard vault:
   ipa vault-archive <name>
       [--user <user>|--service <service>|--shared]
       --in <input file>
""") + _("""
 Archive data into symmetric vault:
   ipa vault-archive <name>
       [--user <user>|--service <service>|--shared]
       --in <input file>
       --password-file password.txt
""") + _("""
 Archive data into asymmetric vault:
   ipa vault-archive <name>
       [--user <user>|--service <service>|--shared]
       --in <input file>
""") + _("""
 Retrieve data from standard vault:
   ipa vault-retrieve <name>
       [--user <user>|--service <service>|--shared]
       --out <output file>
""") + _("""
 Retrieve data from symmetric vault:
   ipa vault-retrieve <name>
       [--user <user>|--service <service>|--shared]
       --out <output file>
       --password-file password.txt
""") + _("""
 Retrieve data from asymmetric vault:
   ipa vault-retrieve <name>
       [--user <user>|--service <service>|--shared]
       --out <output file> --private-key-file private.pem
""") + _("""
 Add vault owners:
   ipa vault-add-owner <name>
       [--user <user>|--service <service>|--shared]
       [--users <users>]  [--groups <groups>] [--services <services>]
""") + _("""
 Delete vault owners:
   ipa vault-remove-owner <name>
       [--user <user>|--service <service>|--shared]
       [--users <users>] [--groups <groups>] [--services <services>]
""") + _("""
 Add vault members:
   ipa vault-add-member <name>
       [--user <user>|--service <service>|--shared]
       [--users <users>] [--groups <groups>] [--services <services>]
""") + _("""
 Delete vault members:
   ipa vault-remove-member <name>
       [--user <user>|--service <service>|--shared]
       [--users <users>] [--groups <groups>] [--services <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'),
    ),
)


[docs]class VaultModMember(LDAPModMember):
[docs] 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
[docs]@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'}, ), )
[docs] 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']
[docs]@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
[docs]@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
[docs] def execute(self, *keys, **options): keys = keys + (u'',) return super(vaultcontainer_del, self).execute(*keys, **options)
[docs]@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'), ), )
[docs]@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'), ), )
[docs]@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)
[docs] 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)
[docs] 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)
[docs] 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']
[docs]@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
[docs]@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: 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
[docs]@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
[docs]@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
[docs]@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
[docs]@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'} ) )
[docs]@register() class vaultconfig_show(Retrieve): __doc__ = _('Show vault configuration.') takes_options = ( Str( 'transport_out?', doc=_('Output file to store the transport certificate'), ), )
[docs] 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, }
[docs]@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_DEFAULT_ALGO, autofill=True, ), ) has_output = output.standard_entry msg_summary = _('Archived data into vault "%(value)s"')
[docs] 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 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, ) kra_account.logout() response = { 'value': args[-1], 'result': {}, } response['summary'] = self.msg_summary % response return response
[docs]@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_DEFAULT_ALGO, autofill=True, ), ) has_output = output.standard_entry msg_summary = _('Retrieved data from vault "%(value)s"')
[docs] 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 key = kra_client.keys.retrieve_key( key_info.get_key_id(), wrapped_session_key) 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
[docs]@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'), ), )
[docs]@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'), ), )
[docs]@register() class vault_add_member(VaultModMember, LDAPAddMember): __doc__ = _('Add members to a vault.') takes_options = LDAPAddMember.takes_options + vault_options
[docs]@register() class vault_remove_member(VaultModMember, LDAPRemoveMember): __doc__ = _('Remove members from a vault.') takes_options = LDAPRemoveMember.takes_options + vault_options
[docs]@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
[docs] 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))