mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 07:33:27 -06:00
4cc6b9cd17
If a vault operation fails, the error message just says "InternalError". This commit improves error handling of key archival and retrieval calls by catching the PKIException error and raising it as an IPA error. Related: https://pagure.io/freeipa/issue/9191 Signed-off-by: Francisco Trivino <ftrivino@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
1284 lines
39 KiB
Python
1284 lines
39 KiB
Python
# 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
|
|
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 <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'),
|
|
),
|
|
)
|
|
|
|
|
|
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))
|