2015-04-30 03:55:29 -05:00
|
|
|
#
|
|
|
|
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
|
|
|
#
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
2016-06-01 08:58:47 -05:00
|
|
|
from ipalib import api, Bool, Str
|
2015-04-30 03:55:29 -05:00
|
|
|
from ipalib.plugable import Registry
|
2016-04-20 08:41:34 -05:00
|
|
|
from .baseldap import (
|
2015-04-30 03:55:29 -05:00
|
|
|
LDAPObject, LDAPSearch, LDAPCreate,
|
|
|
|
LDAPDelete, LDAPUpdate, LDAPRetrieve)
|
2015-07-23 10:48:56 -05:00
|
|
|
from ipalib.request import context
|
2015-04-30 03:55:29 -05:00
|
|
|
from ipalib import ngettext
|
|
|
|
from ipalib.text import _
|
2015-08-13 01:32:54 -05:00
|
|
|
from ipapython.dogtag import INCLUDED_PROFILES
|
2015-07-02 03:09:31 -05:00
|
|
|
from ipapython.version import API_VERSION
|
2015-04-30 03:55:29 -05:00
|
|
|
|
|
|
|
from ipalib import errors
|
|
|
|
|
|
|
|
|
|
|
|
__doc__ = _("""
|
|
|
|
Manage Certificate Profiles
|
|
|
|
|
|
|
|
Certificate Profiles are used by Certificate Authority (CA) in the signing of
|
|
|
|
certificates to determine if a Certificate Signing Request (CSR) is acceptable,
|
|
|
|
and if so what features and extensions will be present on the certificate.
|
|
|
|
|
|
|
|
The Certificate Profile format is the property-list format understood by the
|
|
|
|
Dogtag or Red Hat Certificate System CA.
|
|
|
|
|
|
|
|
PROFILE ID SYNTAX:
|
|
|
|
|
|
|
|
A Profile ID is a string without spaces or punctuation starting with a letter
|
|
|
|
and followed by a sequence of letters, digits or underscore ("_").
|
|
|
|
|
|
|
|
EXAMPLES:
|
|
|
|
|
|
|
|
Import a profile that will not store issued certificates:
|
|
|
|
ipa certprofile-import ShortLivedUserCert \\
|
2015-06-29 09:28:25 -05:00
|
|
|
--file UserCert.profile --desc "User Certificates" \\
|
2015-04-30 03:55:29 -05:00
|
|
|
--store=false
|
|
|
|
|
|
|
|
Delete a certificate profile:
|
|
|
|
ipa certprofile-del ShortLivedUserCert
|
|
|
|
|
|
|
|
Show information about a profile:
|
|
|
|
ipa certprofile-show ShortLivedUserCert
|
|
|
|
|
2015-07-23 22:07:10 -05:00
|
|
|
Save profile configuration to a file:
|
|
|
|
ipa certprofile-show caIPAserviceCert --out caIPAserviceCert.cfg
|
|
|
|
|
2015-04-30 03:55:29 -05:00
|
|
|
Search for profiles that do not store certificates:
|
|
|
|
ipa certprofile-find --store=false
|
|
|
|
|
2015-07-23 22:07:10 -05:00
|
|
|
PROFILE CONFIGURATION FORMAT:
|
|
|
|
|
|
|
|
The profile configuration format is the raw property-list format
|
|
|
|
used by Dogtag Certificate System. The XML format is not supported.
|
|
|
|
|
2021-01-19 14:35:41 -06:00
|
|
|
The following restrictions apply to profiles managed by IPA:
|
2015-07-23 22:07:10 -05:00
|
|
|
|
|
|
|
- When importing a profile the "profileId" field, if present, must
|
|
|
|
match the ID given on the command line.
|
|
|
|
|
|
|
|
- The "classId" field must be set to "caEnrollImpl"
|
|
|
|
|
|
|
|
- The "auth.instance_id" field must be set to "raCertAuth"
|
|
|
|
|
|
|
|
- The "certReqInputImpl" input class and "certOutputImpl" output
|
|
|
|
class must be used.
|
|
|
|
|
2015-04-30 03:55:29 -05:00
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
register = Registry()
|
|
|
|
|
|
|
|
|
2016-12-18 22:31:14 -06:00
|
|
|
def ca_enabled_check(_api):
|
2015-04-30 03:55:29 -05:00
|
|
|
"""Raise NotFound if CA is not enabled.
|
|
|
|
|
|
|
|
This function is defined in multiple plugins to avoid circular imports
|
|
|
|
(cert depends on certprofile, so we cannot import cert here).
|
|
|
|
|
|
|
|
"""
|
2016-12-18 22:31:14 -06:00
|
|
|
if not _api.Command.ca_is_enabled()['result']:
|
2015-04-30 03:55:29 -05:00
|
|
|
raise errors.NotFound(reason=_('CA is not configured'))
|
|
|
|
|
|
|
|
|
2018-09-24 03:49:45 -05:00
|
|
|
profile_id_pattern = re.compile(r'^[a-zA-Z]\w*$')
|
2015-04-30 03:55:29 -05:00
|
|
|
|
|
|
|
|
|
|
|
def validate_profile_id(ugettext, value):
|
|
|
|
"""Ensure profile ID matches form required by CA."""
|
|
|
|
if profile_id_pattern.match(value) is None:
|
|
|
|
return _('invalid Profile ID')
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@register()
|
|
|
|
class certprofile(LDAPObject):
|
|
|
|
"""
|
|
|
|
Certificate Profile object.
|
|
|
|
"""
|
|
|
|
container_dn = api.env.container_certprofile
|
|
|
|
object_name = _('Certificate Profile')
|
|
|
|
object_name_plural = _('Certificate Profiles')
|
|
|
|
object_class = ['ipacertprofile']
|
|
|
|
default_attributes = [
|
|
|
|
'cn', 'description', 'ipacertprofilestoreissued'
|
|
|
|
]
|
|
|
|
search_attributes = [
|
|
|
|
'cn', 'description', 'ipacertprofilestoreissued'
|
|
|
|
]
|
|
|
|
label = _('Certificate Profiles')
|
|
|
|
label_singular = _('Certificate Profile')
|
|
|
|
|
|
|
|
takes_params = (
|
|
|
|
Str('cn', validate_profile_id,
|
|
|
|
primary_key=True,
|
|
|
|
cli_name='id',
|
|
|
|
label=_('Profile ID'),
|
|
|
|
doc=_('Profile ID for referring to this profile'),
|
|
|
|
),
|
2016-06-29 23:37:16 -05:00
|
|
|
Str('config',
|
|
|
|
label=_('Profile configuration'),
|
|
|
|
flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
|
|
|
|
),
|
2015-04-30 03:55:29 -05:00
|
|
|
Str('description',
|
|
|
|
required=True,
|
|
|
|
cli_name='desc',
|
|
|
|
label=_('Profile description'),
|
|
|
|
doc=_('Brief description of this profile'),
|
|
|
|
),
|
|
|
|
Bool('ipacertprofilestoreissued',
|
|
|
|
default=True,
|
|
|
|
cli_name='store',
|
|
|
|
label=_('Store issued certificates'),
|
|
|
|
doc=_('Whether to store certs issued using this profile'),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
permission_filter_objectclasses = ['ipacertprofile']
|
|
|
|
managed_permissions = {
|
|
|
|
'System: Read Certificate Profiles': {
|
|
|
|
'replaces_global_anonymous_aci': True,
|
|
|
|
'ipapermbindruletype': 'all',
|
|
|
|
'ipapermright': {'read', 'search', 'compare'},
|
|
|
|
'ipapermdefaultattr': {
|
|
|
|
'cn',
|
|
|
|
'description',
|
|
|
|
'ipacertprofilestoreissued',
|
|
|
|
'objectclass',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'System: Import Certificate Profile': {
|
|
|
|
'ipapermright': {'add'},
|
|
|
|
'replaces': [
|
|
|
|
'(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Import Certificate Profile";allow (add) groupdn = "ldap:///cn=Import Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)',
|
|
|
|
],
|
|
|
|
'default_privileges': {'CA Administrator'},
|
|
|
|
},
|
|
|
|
'System: Delete Certificate Profile': {
|
|
|
|
'ipapermright': {'delete'},
|
|
|
|
'replaces': [
|
|
|
|
'(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete Certificate Profile";allow (delete) groupdn = "ldap:///cn=Delete Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)',
|
|
|
|
],
|
|
|
|
'default_privileges': {'CA Administrator'},
|
|
|
|
},
|
|
|
|
'System: Modify Certificate Profile': {
|
|
|
|
'ipapermright': {'write'},
|
|
|
|
'ipapermdefaultattr': {
|
|
|
|
'cn',
|
|
|
|
'description',
|
|
|
|
'ipacertprofilestoreissued',
|
|
|
|
},
|
|
|
|
'replaces': [
|
|
|
|
'(targetattr = "cn || description || ipacertprofilestoreissued")(target = "ldap:///cn=*,cn=certprofiles,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify Certificate Profile";allow (write) groupdn = "ldap:///cn=Modify Certificate Profile,cn=permissions,cn=pbac,$SUFFIX";)',
|
|
|
|
],
|
|
|
|
'default_privileges': {'CA Administrator'},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@register()
|
|
|
|
class certprofile_find(LDAPSearch):
|
|
|
|
__doc__ = _("Search for Certificate Profiles.")
|
|
|
|
msg_summary = ngettext(
|
|
|
|
'%(count)d profile matched', '%(count)d profiles matched', 0
|
|
|
|
)
|
|
|
|
|
|
|
|
def execute(self, *args, **kwargs):
|
2016-12-18 22:31:14 -06:00
|
|
|
ca_enabled_check(self.api)
|
2015-04-30 03:55:29 -05:00
|
|
|
return super(certprofile_find, self).execute(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
@register()
|
|
|
|
class certprofile_show(LDAPRetrieve):
|
|
|
|
__doc__ = _("Display the properties of a Certificate Profile.")
|
|
|
|
|
2015-07-02 02:31:31 -05:00
|
|
|
takes_options = LDAPRetrieve.takes_options + (
|
|
|
|
Str('out?',
|
|
|
|
doc=_('Write profile configuration to file'),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
def execute(self, *keys, **options):
|
2016-12-18 22:31:14 -06:00
|
|
|
ca_enabled_check(self.api)
|
2015-07-02 02:31:31 -05:00
|
|
|
result = super(certprofile_show, self).execute(*keys, **options)
|
|
|
|
|
|
|
|
if 'out' in options:
|
|
|
|
with self.api.Backend.ra_certprofile as profile_api:
|
|
|
|
result['result']['config'] = profile_api.read_profile(keys[0])
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
2015-04-30 03:55:29 -05:00
|
|
|
|
|
|
|
@register()
|
|
|
|
class certprofile_import(LDAPCreate):
|
|
|
|
__doc__ = _("Import a Certificate Profile.")
|
|
|
|
msg_summary = _('Imported profile "%(value)s"')
|
|
|
|
takes_options = (
|
2016-06-01 08:58:47 -05:00
|
|
|
Str(
|
|
|
|
'file',
|
2015-07-23 11:22:19 -05:00
|
|
|
label=_('Filename of a raw profile. The XML format is not supported.'),
|
2015-04-30 03:55:29 -05:00
|
|
|
cli_name='file',
|
|
|
|
flags=('virtual_attribute',),
|
2016-06-01 08:58:47 -05:00
|
|
|
noextrawhitespace=False,
|
2015-04-30 03:55:29 -05:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2018-09-24 03:49:45 -05:00
|
|
|
PROFILE_ID_PATTERN = re.compile(r'^profileId=([a-zA-Z]\w*)', re.MULTILINE)
|
2015-04-30 03:55:29 -05:00
|
|
|
|
|
|
|
def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options):
|
2016-12-18 22:31:14 -06:00
|
|
|
ca_enabled_check(self.api)
|
2015-07-23 10:48:56 -05:00
|
|
|
context.profile = options['file']
|
2015-04-30 03:55:29 -05:00
|
|
|
|
2018-04-18 02:10:10 -05:00
|
|
|
matches = self.PROFILE_ID_PATTERN.findall(options['file'])
|
|
|
|
if len(matches) == 0:
|
2015-07-23 10:48:56 -05:00
|
|
|
# no profileId found, use CLI value as profileId.
|
|
|
|
context.profile = u'profileId=%s\n%s' % (keys[0], context.profile)
|
2018-04-18 02:10:10 -05:00
|
|
|
elif len(matches) > 1:
|
|
|
|
raise errors.ValidationError(
|
|
|
|
name='file',
|
|
|
|
error=_(
|
|
|
|
"Profile data specifies profileId multiple times: "
|
|
|
|
"%(values)s"
|
|
|
|
) % dict(values=matches)
|
|
|
|
)
|
|
|
|
elif keys[0] != matches[0]:
|
|
|
|
raise errors.ValidationError(
|
|
|
|
name='file',
|
|
|
|
error=_(
|
|
|
|
"Profile ID '%(cli_value)s' "
|
|
|
|
"does not match profile data '%(file_value)s'"
|
|
|
|
) % dict(cli_value=keys[0], file_value=matches[0])
|
2015-04-30 03:55:29 -05:00
|
|
|
)
|
|
|
|
return dn
|
|
|
|
|
|
|
|
|
|
|
|
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
|
|
|
"""Import the profile into Dogtag and enable it.
|
|
|
|
|
|
|
|
If the operation fails, remove the LDAP entry.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
with self.api.Backend.ra_certprofile as profile_api:
|
2015-07-23 10:48:56 -05:00
|
|
|
profile_api.create_profile(context.profile)
|
2015-04-30 03:55:29 -05:00
|
|
|
profile_api.enable_profile(keys[0])
|
2020-04-30 08:17:52 -05:00
|
|
|
except BaseException:
|
2015-04-30 03:55:29 -05:00
|
|
|
# something went wrong ; delete entry
|
|
|
|
ldap.delete_entry(dn)
|
|
|
|
raise
|
|
|
|
|
|
|
|
return dn
|
|
|
|
|
|
|
|
|
|
|
|
@register()
|
|
|
|
class certprofile_del(LDAPDelete):
|
|
|
|
__doc__ = _("Delete a Certificate Profile.")
|
|
|
|
msg_summary = _('Deleted profile "%(value)s"')
|
|
|
|
|
2015-08-13 01:32:54 -05:00
|
|
|
def pre_callback(self, ldap, dn, *keys, **options):
|
2016-12-18 22:31:14 -06:00
|
|
|
ca_enabled_check(self.api)
|
2015-08-13 01:32:54 -05:00
|
|
|
|
2015-08-12 05:25:30 -05:00
|
|
|
if keys[0] in [p.profile_id for p in INCLUDED_PROFILES]:
|
2015-08-13 01:32:54 -05:00
|
|
|
raise errors.ValidationError(name='profile_id',
|
|
|
|
error=_("Predefined profile '%(profile_id)s' cannot be deleted")
|
|
|
|
% {'profile_id': keys[0]}
|
|
|
|
)
|
|
|
|
|
|
|
|
return dn
|
2015-04-30 03:55:29 -05:00
|
|
|
|
|
|
|
def post_callback(self, ldap, dn, *keys, **options):
|
|
|
|
with self.api.Backend.ra_certprofile as profile_api:
|
|
|
|
profile_api.disable_profile(keys[0])
|
|
|
|
profile_api.delete_profile(keys[0])
|
|
|
|
return dn
|
|
|
|
|
|
|
|
|
|
|
|
@register()
|
|
|
|
class certprofile_mod(LDAPUpdate):
|
|
|
|
__doc__ = _("Modify Certificate Profile configuration.")
|
2015-06-19 04:57:21 -05:00
|
|
|
msg_summary = _('Modified Certificate Profile "%(value)s"')
|
2015-04-30 03:55:29 -05:00
|
|
|
|
2015-07-02 03:09:31 -05:00
|
|
|
takes_options = LDAPUpdate.takes_options + (
|
2016-06-01 08:58:47 -05:00
|
|
|
Str(
|
|
|
|
'file?',
|
2015-07-02 03:09:31 -05:00
|
|
|
label=_('File containing profile configuration'),
|
|
|
|
cli_name='file',
|
|
|
|
flags=('virtual_attribute',),
|
2016-06-01 08:58:47 -05:00
|
|
|
noextrawhitespace=False,
|
2015-07-02 03:09:31 -05:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
|
2016-12-18 22:31:14 -06:00
|
|
|
ca_enabled_check(self.api)
|
2015-07-09 10:17:21 -05:00
|
|
|
# Once a profile id is set it cannot be changed
|
2015-09-01 20:04:34 -05:00
|
|
|
if 'cn' in entry_attrs:
|
2015-08-24 19:25:10 -05:00
|
|
|
raise errors.ProtectedEntryError(label='certprofile', key=keys[0],
|
|
|
|
reason=_('Certificate profiles cannot be renamed'))
|
2015-07-02 03:09:31 -05:00
|
|
|
if 'file' in options:
|
2016-11-14 22:02:54 -06:00
|
|
|
# ensure operator has permission to update a certprofile
|
|
|
|
if not ldap.can_write(dn, 'ipacertprofilestoreissued'):
|
|
|
|
raise errors.ACIError(info=_(
|
|
|
|
"Insufficient privilege to modify a certificate profile."))
|
|
|
|
|
2015-07-02 03:09:31 -05:00
|
|
|
with self.api.Backend.ra_certprofile as profile_api:
|
|
|
|
profile_api.disable_profile(keys[0])
|
|
|
|
try:
|
|
|
|
profile_api.update_profile(keys[0], options['file'])
|
|
|
|
finally:
|
|
|
|
profile_api.enable_profile(keys[0])
|
|
|
|
|
|
|
|
return dn
|
|
|
|
|
|
|
|
def execute(self, *keys, **options):
|
|
|
|
try:
|
|
|
|
return super(certprofile_mod, self).execute(*keys, **options)
|
|
|
|
except errors.EmptyModlist:
|
|
|
|
if 'file' in options:
|
|
|
|
# The profile data in Dogtag was updated.
|
|
|
|
# Do not fail; return result of certprofile-show instead
|
|
|
|
return self.api.Command.certprofile_show(keys[0],
|
|
|
|
version=API_VERSION)
|
|
|
|
else:
|
|
|
|
# This case is actually an error; re-raise
|
|
|
|
raise
|