freeipa/ipaserver/plugins/vault.py
Christian Heimes 40c362e1ee Support AES for KRA archival wrapping
The vault plugin has used TripleDES (des-ede3-cbc) as default wrapping
algorithm since the plugin was introduced. Allow use of AES-128-CBC as
alternative wrapping algorithm for transport of secrets.

Fixes: https://pagure.io/freeipa/issue/6524

Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
2022-03-16 12:07:01 +02:00

1270 lines
38 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
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:
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):
__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_DEFAULT_ALGO,
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
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
@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"')
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
@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))