summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ACI.txt8
-rw-r--r--API.txt62
-rw-r--r--install/updates/40-certprofile.update9
-rw-r--r--install/updates/40-delegation.update8
-rw-r--r--install/updates/Makefile.am1
-rw-r--r--ipalib/constants.py1
-rw-r--r--ipalib/plugins/certprofile.py253
-rw-r--r--ipapython/dogtag.py29
-rw-r--r--ipaserver/plugins/dogtag.py176
9 files changed, 534 insertions, 13 deletions
diff --git a/ACI.txt b/ACI.txt
index 1821696fd..543d8da69 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -22,6 +22,14 @@ dn: cn=automount,dc=ipa,dc=example
aci: (targetattr = "automountmapname || description")(targetfilter = "(objectclass=automountmap)")(version 3.0;acl "permission:System: Modify Automount Maps";allow (write) groupdn = "ldap:///cn=System: Modify Automount Maps,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=automount,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=automountmap)")(version 3.0;acl "permission:System: Remove Automount Maps";allow (delete) groupdn = "ldap:///cn=System: Remove Automount Maps,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=certprofiles,cn=ca,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Delete Certificate Profile";allow (delete) groupdn = "ldap:///cn=System: Delete Certificate Profile,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=certprofiles,cn=ca,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Import Certificate Profile";allow (add) groupdn = "ldap:///cn=System: Import Certificate Profile,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=certprofiles,cn=ca,dc=ipa,dc=example
+aci: (targetattr = "cn || description || ipacertprofilestoreissued")(targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Modify Certificate Profile";allow (write) groupdn = "ldap:///cn=System: Modify Certificate Profile,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=certprofiles,cn=ca,dc=ipa,dc=example
+aci: (targetattr = "cn || createtimestamp || description || entryusn || ipacertprofilestoreissued || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipacertprofile)")(version 3.0;acl "permission:System: Read Certificate Profiles";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=ipaconfig,cn=etc,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || entryusn || ipacertificatesubjectbase || ipaconfigstring || ipacustomfields || ipadefaultemaildomain || ipadefaultloginshell || ipadefaultprimarygroup || ipagroupobjectclasses || ipagroupsearchfields || ipahomesrootdir || ipakrbauthzdata || ipamaxusernamelength || ipamigrationenabled || ipapwdexpadvnotify || ipasearchrecordslimit || ipasearchtimelimit || ipaselinuxusermapdefault || ipaselinuxusermaporder || ipauserauthtype || ipauserobjectclasses || ipausersearchfields || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaguiconfig)")(version 3.0;acl "permission:System: Read Global Configuration";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=costemplates,cn=accounts,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index 6520f2c42..81aca14af 100644
--- a/API.txt
+++ b/API.txt
@@ -509,6 +509,68 @@ args: 1,1,1
arg: Str('request_id')
option: Str('version?', exclude='webui')
output: Output('result', None, None)
+command: certprofile_del
+args: 1,2,3
+arg: Str('cn', attribute=True, cli_name='id', multivalue=True, primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
+command: certprofile_find
+args: 1,9,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='id', multivalue=False, primary_key=True, query=True, required=False)
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
+option: Bool('ipacertprofilestoreissued', attribute=True, autofill=False, cli_name='store', default=True, multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: certprofile_import
+args: 1,6,3
+arg: Str('cn', attribute=True, cli_name='id', multivalue=False, primary_key=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=True)
+option: File('file', cli_name='file')
+option: Bool('ipacertprofilestoreissued', attribute=True, cli_name='store', default=True, multivalue=False, required=True)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: certprofile_mod
+args: 1,10,3
+arg: Str('cn', attribute=True, cli_name='id', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False)
+option: Bool('ipacertprofilestoreissued', attribute=True, autofill=False, cli_name='store', default=True, multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('rename', cli_name='rename', multivalue=False, primary_key=True, required=False)
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: certprofile_show
+args: 1,4,3
+arg: Str('cn', attribute=True, cli_name='id', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
command: compat_is_enabled
args: 0,1,1
option: Str('version?', exclude='webui')
diff --git a/install/updates/40-certprofile.update b/install/updates/40-certprofile.update
new file mode 100644
index 000000000..6b0a81d0f
--- /dev/null
+++ b/install/updates/40-certprofile.update
@@ -0,0 +1,9 @@
+dn: cn=ca,$SUFFIX
+default: objectClass: nsContainer
+default: objectClass: top
+default: cn: ca
+
+dn: cn=certprofiles,cn=ca,$SUFFIX
+default: objectClass: nsContainer
+default: objectClass: top
+default: cn: certprofiles
diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update
index 975929bd7..bc0736c5b 100644
--- a/install/updates/40-delegation.update
+++ b/install/updates/40-delegation.update
@@ -237,3 +237,11 @@ default:ipapermissiontype: SYSTEM
dn: cn=config
add:aci: (version 3.0;acl "permission:Add Configuration Sub-Entries";allow (add) groupdn = "ldap:///cn=Add Configuration Sub-Entries,cn=permissions,cn=pbac,$SUFFIX";)
+
+# CA Administrators
+dn: cn=CA Administrator,cn=privileges,cn=pbac,$SUFFIX
+default:objectClass: nestedgroup
+default:objectClass: groupofnames
+default:objectClass: top
+default:cn: CA Administrator
+default:description: CA Administrator
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 4e2da05d6..fc6bd624e 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -32,6 +32,7 @@ app_DATA = \
40-replication.update \
40-dns.update \
40-automember.update \
+ 40-certprofile.update \
40-otp.update \
40-vault.update \
45-roles.update \
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 95dec54a5..96396a236 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -118,6 +118,7 @@ DEFAULT_CONFIG = (
('container_radiusproxy', DN(('cn', 'radiusproxy'))),
('container_views', DN(('cn', 'views'), ('cn', 'accounts'))),
('container_masters', DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'))),
+ ('container_certprofile', DN(('cn', 'certprofiles'), ('cn', 'ca'))),
# Ports, hosts, and URIs:
('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
diff --git a/ipalib/plugins/certprofile.py b/ipalib/plugins/certprofile.py
new file mode 100644
index 000000000..1a2d14388
--- /dev/null
+++ b/ipalib/plugins/certprofile.py
@@ -0,0 +1,253 @@
+#
+# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
+#
+
+import re
+
+from ipalib import api, Bool, File, Str
+from ipalib import output
+from ipalib.plugable import Registry
+from ipalib.plugins.virtual import VirtualCommand
+from ipalib.plugins.baseldap import (
+ LDAPObject, LDAPSearch, LDAPCreate,
+ LDAPDelete, LDAPUpdate, LDAPRetrieve)
+from ipalib import ngettext
+from ipalib.text import _
+
+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 \\
+ --file UserCert.profile --summary "User Certificates" \\
+ --store=false
+
+ Delete a certificate profile:
+ ipa certprofile-del ShortLivedUserCert
+
+ Show information about a profile:
+ ipa certprofile-show ShortLivedUserCert
+
+ Search for profiles that do not store certificates:
+ ipa certprofile-find --store=false
+
+""")
+
+
+register = Registry()
+
+
+def ca_enabled_check():
+ """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).
+
+ """
+ if not api.Command.ca_is_enabled()['result']:
+ raise errors.NotFound(reason=_('CA is not configured'))
+
+
+profile_id_pattern = re.compile('^[a-zA-Z]\w*$')
+
+
+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'
+ ]
+ rdn_is_primary_key = True
+ 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'),
+ ),
+ 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):
+ ca_enabled_check()
+ return super(certprofile_find, self).execute(*args, **kwargs)
+
+
+@register()
+class certprofile_show(LDAPRetrieve):
+ __doc__ = _("Display the properties of a Certificate Profile.")
+
+ def execute(self, *args, **kwargs):
+ ca_enabled_check()
+ return super(certprofile_show, self).execute(*args, **kwargs)
+
+
+@register()
+class certprofile_import(LDAPCreate):
+ __doc__ = _("Import a Certificate Profile.")
+ msg_summary = _('Imported profile "%(value)s"')
+ takes_options = (
+ File('file',
+ label=_('Filename'),
+ cli_name='file',
+ flags=('virtual_attribute',),
+ ),
+ )
+
+ PROFILE_ID_PATTERN = re.compile('^profileId=([a-zA-Z]\w*)', re.MULTILINE)
+
+ def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options):
+ ca_enabled_check()
+
+ match = self.PROFILE_ID_PATTERN.search(options['file'])
+ if match is None:
+ raise errors.ValidationError(name='file',
+ error=_("Profile ID is not present in profile data"))
+ elif keys[0] != match.group(1):
+ raise errors.ValidationError(name='file',
+ error=_("Profile ID '%(cli_value)s' does not match profile data '%(file_value)s'")
+ % {'cli_value': keys[0], 'file_value': match.group(1)}
+ )
+ return dn
+
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ """Import the profile into Dogtag and enable it.
+
+ If the operation succeeds, update the LDAP entry to 'enabled'.
+ If the operation fails, remove the LDAP entry.
+ """
+ try:
+ with self.api.Backend.ra_certprofile as profile_api:
+ profile_api.create_profile(options['file'])
+ profile_api.enable_profile(keys[0])
+ except:
+ # 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"')
+
+ def execute(self, *args, **kwargs):
+ ca_enabled_check()
+ return super(certprofile_del, self).execute(*args, **kwargs)
+
+ 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.")
+ msg_summary = _('Modified Certificate Profile "%(value)s')
+
+ def execute(self, *args, **kwargs):
+ ca_enabled_check()
+ return super(certprofile_mod, self).execute(*args, **kwargs)
diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
index c74b8736a..11311cf7b 100644
--- a/ipapython/dogtag.py
+++ b/ipapython/dogtag.py
@@ -233,9 +233,12 @@ def ca_status(ca_host=None, use_proxy=True):
return _parse_ca_status(body)
-def https_request(host, port, url, secdir, password, nickname, **kw):
+def https_request(host, port, url, secdir, password, nickname,
+ method='POST', headers=None, body=None, **kw):
"""
+ :param method: HTTP request method (defalut: 'POST')
:param url: The path (not complete URL!) to post to.
+ :param body: The request body (encodes kw if None)
:param kw: Keyword arguments to encode into POST body.
:return: (http_status, http_reason_phrase, http_headers, http_body)
as (integer, unicode, dict, str)
@@ -254,9 +257,11 @@ def https_request(host, port, url, secdir, password, nickname, **kw):
nickname, password, nss.get_default_certdb())
return conn
- body = urlencode(kw)
+ if body is None:
+ body = urlencode(kw)
return _httplib_request(
- 'https', host, port, url, connection_factory, body)
+ 'https', host, port, url, connection_factory, body,
+ method=method, headers=headers)
def http_request(host, port, url, **kw):
@@ -288,11 +293,13 @@ def unauthenticated_https_request(host, port, url, **kw):
def _httplib_request(
- protocol, host, port, path, connection_factory, request_body):
+ protocol, host, port, path, connection_factory, request_body,
+ method='POST', headers=None):
"""
:param request_body: Request body
:param connection_factory: Connection class to use. Will be called
with the host and port arguments.
+ :param method: HTTP request method (default: 'POST')
Perform a HTTP(s) request.
"""
@@ -301,13 +308,17 @@ def _httplib_request(
uri = '%s://%s%s' % (protocol, ipautil.format_netloc(host, port), path)
root_logger.debug('request %r', uri)
root_logger.debug('request body %r', request_body)
+
+ headers = headers or {}
+ if (
+ method == 'POST'
+ and 'content-type' not in (str(k).lower() for k in headers.viewkeys())
+ ):
+ headers['content-type'] = 'application/x-www-form-urlencoded'
+
try:
conn = connection_factory(host, port)
- conn.request(
- 'POST', uri,
- body=request_body,
- headers={'Content-type': 'application/x-www-form-urlencoded'},
- )
+ conn.request(method, uri, body=request_body, headers=headers)
res = conn.getresponse()
http_status = res.status
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index 52bdb0d42..9654123b1 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -4,8 +4,9 @@
# Jason Gerard DeRose <jderose@redhat.com>
# Rob Crittenden <rcritten@@redhat.com>
# John Dennis <jdennis@redhat.com>
+# Fraser Tweedale <ftweedal@redhat.com>
#
-# Copyright (C) 2014 Red Hat
+# Copyright (C) 2014, 2015 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
@@ -238,17 +239,21 @@ digits and nothing else follows.
'''
import datetime
+import json
from lxml import etree
+import os
import tempfile
import time
import urllib2
+import pki
from pki.client import PKIConnection
import pki.crypto as cryptoutil
from pki.kra import KRAClient
from ipalib import Backend
from ipapython.dn import DN
+import ipapython.cookie
import ipapython.dogtag
from ipapython import ipautil
from ipaserver.install.certs import CertDB
@@ -1262,13 +1267,12 @@ def select_any_master(ldap2, service='CA'):
#-------------------------------------------------------------------------------
-from ipalib import api, SkipPluginModule
+from ipalib import api, errors, SkipPluginModule
if api.env.ra_plugin != 'dogtag':
# In this case, abort loading this plugin module...
raise SkipPluginModule(reason='dogtag not selected as RA plugin')
import os, random
from ipaserver.plugins import rabase
-from ipalib.errors import CertificateOperationError
from ipalib.constants import TYPE_ERROR
from ipalib.util import cachedproperty
from ipapython import dogtag
@@ -1318,7 +1322,7 @@ class ra(rabase.rabase):
err_msg = u'%s (%s)' % (err_msg, detail)
self.error('%s.%s(): %s', self.fullname, func_name, err_msg)
- raise CertificateOperationError(error=err_msg)
+ raise errors.CertificateOperationError(error=err_msg)
@cachedproperty
def ca_host(self):
@@ -1923,3 +1927,167 @@ class kra(Backend):
return KRAClient(connection, crypto)
api.register(kra)
+
+
+class RestClient(Backend):
+ """Simple Dogtag REST client to be subclassed by other backends.
+
+ This class is a context manager. Authenticated calls must be
+ executed in a ``with`` suite::
+
+ class ra_certprofile(RestClient):
+ path = 'profile'
+ ...
+
+ api.register(ra_certprofile)
+
+ with api.Backend.ra_certprofile as profile_api:
+ # REST client is now logged in
+ profile_api.create_profile(...)
+
+ """
+ path = None
+
+ @staticmethod
+ def _parse_dogtag_error(body):
+ try:
+ return pki.PKIException.from_json(json.loads(body))
+ except:
+ return None
+
+ def __init__(self):
+ if api.env.in_tree:
+ self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
+ self.pwd_file = self.sec_dir + os.sep + '.pwd'
+ else:
+ self.sec_dir = paths.HTTPD_ALIAS_DIR
+ self.pwd_file = paths.ALIAS_PWDFILE_TXT
+ self.noise_file = self.sec_dir + os.sep + '.noise'
+ self.ipa_key_size = "2048"
+ self.ipa_certificate_nickname = "ipaCert"
+ self.ca_certificate_nickname = "caCert"
+ try:
+ f = open(self.pwd_file, "r")
+ self.password = f.readline().strip()
+ f.close()
+ except IOError:
+ self.password = ''
+ super(RestClient, self).__init__()
+
+ # session cookie
+ self.cookie = None
+
+ @cachedproperty
+ def ca_host(self):
+ """
+ :return: host
+ as str
+
+ Select our CA host.
+ """
+ ldap2 = self.api.Backend.ldap2
+ if host_has_service(api.env.ca_host, ldap2, "CA"):
+ return api.env.ca_host
+ if api.env.host != api.env.ca_host:
+ if host_has_service(api.env.host, ldap2, "CA"):
+ return api.env.host
+ host = select_any_master(ldap2)
+ if host:
+ return host
+ else:
+ return api.env.ca_host
+
+ def __enter__(self):
+ """Log into the REST API"""
+ if self.cookie is not None:
+ return
+ status, status_text, resp_headers, resp_body = dogtag.https_request(
+ self.ca_host, self.env.ca_agent_port, '/ca/rest/account/login',
+ self.sec_dir, self.password, self.ipa_certificate_nickname,
+ method='GET'
+ )
+ cookies = ipapython.cookie.Cookie.parse(resp_headers.get('set-cookie', ''))
+ if status != 200 or len(cookies) == 0:
+ raise errors.RemoteRetrieveError(reason=_('Failed to authenticate to CA REST API'))
+ self.cookie = str(cookies[0])
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ """Log out of the REST API"""
+ dogtag.https_request(
+ self.ca_host, self.env.ca_agent_port, '/ca/rest/account/logout',
+ self.sec_dir, self.password, self.ipa_certificate_nickname,
+ method='GET'
+ )
+ self.cookie = None
+
+ def _ssldo(self, method, path, headers=None, body=None):
+ """
+ :param url: The URL to post to.
+ :param kw: Keyword arguments to encode into POST body.
+ :return: (http_status, http_reason_phrase, http_headers, http_body)
+ as (integer, unicode, dict, str)
+
+ Perform an HTTPS request
+ """
+ if self.cookie is None:
+ raise errors.RemoteRetrieveError(
+ reason=_("REST API is not logged in."))
+
+ headers = headers or {}
+ headers['Cookie'] = self.cookie
+
+ resource = os.path.join('/ca/rest', self.path, path)
+
+ # perform main request
+ status, status_text, resp_headers, resp_body = dogtag.https_request(
+ self.ca_host, self.env.ca_agent_port, resource,
+ self.sec_dir, self.password, self.ipa_certificate_nickname,
+ method=method, headers=headers, body=body
+ )
+ if status < 200 or status >= 300:
+ explanation = self._parse_dogtag_error(resp_body) or ''
+ raise errors.RemoteRetrieveError(
+ reason=_('Non-2xx response from CA REST API: %(status)d %(status_text)s. %(explanation)s')
+ % {'status': status, 'status_text': status_text, 'explanation': explanation}
+ )
+ return (status, status_text, resp_headers, resp_body)
+
+
+class ra_certprofile(RestClient):
+ """
+ Profile management backend plugin.
+ """
+ path = 'profiles'
+
+ def create_profile(self, profile_data):
+ """
+ Import the profile into Dogtag
+ """
+ self._ssldo('POST', 'raw',
+ headers={
+ 'Content-type': 'application/xml',
+ 'Accept': 'application/json',
+ },
+ body=profile_data
+ )
+
+ def enable_profile(self, profile_id):
+ """
+ Enable the profile in Dogtag
+ """
+ self._ssldo('POST', profile_id + '?action=enable')
+
+ def disable_profile(self, profile_id):
+ """
+ Enable the profile in Dogtag
+ """
+ self._ssldo('POST', profile_id + '?action=disable')
+
+ def delete_profile(self, profile_id):
+ """
+ Delete the profile from Dogtag
+ """
+ self._ssldo('DELETE', profile_id, headers={'Accept': 'application/json'})
+
+api.register(ra_certprofile)