diff options
author | Jan Cholasta <jcholast@redhat.com> | 2016-04-28 10:30:05 +0200 |
---|---|---|
committer | Jan Cholasta <jcholast@redhat.com> | 2016-06-03 09:00:34 +0200 |
commit | 6e44557b601f769d23ee74555a72e8b5cc62c0c9 (patch) | |
tree | eedd3e054b0709341b9f58c190ea54f999f7d13a /ipaserver/plugins/vault.py | |
parent | ec841e5d7ab29d08de294b3fa863a631cd50e30a (diff) | |
download | freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.tar.gz freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.tar.xz freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.zip |
ipalib: move server-side plugins to ipaserver
Move the remaining plugin code from ipalib.plugins to ipaserver.plugins.
Remove the now unused ipalib.plugins package.
https://fedorahosted.org/freeipa/ticket/4739
Reviewed-By: David Kupka <dkupka@redhat.com>
Diffstat (limited to 'ipaserver/plugins/vault.py')
-rw-r--r-- | ipaserver/plugins/vault.py | 1215 |
1 files changed, 1215 insertions, 0 deletions
diff --git a/ipaserver/plugins/vault.py b/ipaserver/plugins/vault.py new file mode 100644 index 000000000..05db63cdc --- /dev/null +++ b/ipaserver/plugins/vault.py @@ -0,0 +1,1215 @@ +# 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/>. + +from ipalib.frontend import Command, Object +from ipalib import api, errors +from ipalib import Bytes, Flag, Str, StrEnum +from ipalib import output +from ipalib.crud import PKQuery, Retrieve +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 .baseuser import split_principal +from .service import normalize_principal +from ipalib import _, ngettext +from ipapython.dn import DN + +if api.env.in_server: + import pki.account + import pki.key + +__doc__ = _(""" +Vaults +""") + _(""" +Manage vaults. +""") + _(""" +Vault is a secure place to store a secret. +""") + _(""" +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 = ( + Str( + 'service?', + 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.itervalues(): + 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 = getattr(context, 'principal') + + if principal.startswith('host/'): + raise errors.NotImplementedError( + reason=_('Host is not supported')) + + (name, realm) = split_principal(principal) + if '/' in name: + service = principal + else: + user = name + + 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 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 = getattr(context, 'principal') + + if principal.startswith('host/'): + raise errors.NotImplementedError( + reason=_('Host is not supported')) + + (name, realm) = split_principal(principal) + if '/' in name: + service = principal + else: + user = name + + 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): + + 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 = getattr(context, 'principal') + (name, realm) = split_principal(principal) + if '/' in name: + owner_dn = self.api.Object.service.get_dn(name) + else: + owner_dn = self.api.Object.user.get_dn(name) + + parent_dn = DN(*dn[1:]) + + try: + self.obj.create_container(parent_dn, owner_dn) + except errors.DuplicateEntry as e: + 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) + + 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() +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): + + 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'), + ), + ) + + +@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')) + + 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_internal(PKQuery): + + 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'), + ), + ) + + 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') + + # retrieve vault info + vault = self.api.Command.vault_show(*args, **options)['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() + + response = { + 'value': args[-1], + 'result': {}, + } + + response['summary'] = self.msg_summary % response + + return response + + +@register() +class vault_retrieve_internal(PKQuery): + + NO_CLI = True + + takes_options = vault_options + ( + Bytes( + 'session_key', + doc=_('Session key wrapped with transport certificate'), + ), + ) + + 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') + + # retrieve vault info + vault = self.api.Command.vault_show(*args, **options)['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) + + 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): + NO_CLI = True + + has_output = output.standard_value + + def execute(self, *args, **options): + base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), + self.api.env.basedn) + filter = '(&(objectClass=ipaConfigObject)(cn=KRA))' + try: + self.api.Backend.ldap2.find_entries( + base_dn=base_dn, filter=filter, attrs_list=[]) + except errors.NotFound: + result = False + else: + result = True + return dict(result=result, value=pkey_to_value(None, options)) |