summaryrefslogtreecommitdiffstats
path: root/ipaserver
diff options
context:
space:
mode:
authorFraser Tweedale <ftweedal@redhat.com>2015-05-14 01:46:06 -0400
committerJan Cholasta <jcholast@redhat.com>2016-06-15 07:13:38 +0200
commit3d4db834caa0688bcefc0092b7978402b783eaf3 (patch)
treee9fddc70bdff260ce738dc9458938851453d86c0 /ipaserver
parent8135651abb857fbe489a1de8aacad3747d7d5cc9 (diff)
downloadfreeipa-3d4db834caa0688bcefc0092b7978402b783eaf3.tar.gz
freeipa-3d4db834caa0688bcefc0092b7978402b783eaf3.tar.xz
freeipa-3d4db834caa0688bcefc0092b7978402b783eaf3.zip
Add 'ca' plugin
This commit adds the 'ca' plugin for creating and managing lightweight CAs. The initial implementation supports a single level of sub-CAs underneath the IPA CA. This commit also: - adds the container for FreeIPA CA objects - adds schema for the FreeIPA CA objects - updates ipa-pki-proxy.conf to allow access to the Dogtag lightweight CAs REST API. Part of: https://fedorahosted.org/freeipa/ticket/4559 Reviewed-By: Jan Cholasta <jcholast@redhat.com> Reviewed-By: Martin Babinsky <mbabinsk@redhat.com>
Diffstat (limited to 'ipaserver')
-rw-r--r--ipaserver/install/cainstance.py7
-rw-r--r--ipaserver/install/server/upgrade.py16
-rw-r--r--ipaserver/plugins/ca.py217
-rw-r--r--ipaserver/plugins/dogtag.py54
4 files changed, 292 insertions, 2 deletions
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index becb0b172..3e2576d05 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -1629,6 +1629,13 @@ def ensure_ldap_profiles_container():
ou=['certificateProfiles'],
)
+def ensure_lightweight_cas_container():
+ ensure_entry(
+ DN(('ou', 'authorities'), ('ou', 'ca'), ('o', 'ipaca')),
+ objectclass=['top', 'organizationalUnit'],
+ ou=['authorities'],
+ )
+
def ensure_entry(dn, **attrs):
server_id = installutils.realm_to_serverid(api.env.realm)
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index cd2ad2e11..81a49e8af 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -345,6 +345,16 @@ def ca_import_included_profiles(ca):
return cainstance.import_included_profiles()
+def ca_ensure_lightweight_cas_container(ca):
+ root_logger.info('[Ensuring Lightweight CAs container exists in Dogtag database]')
+
+ if not ca.is_configured():
+ root_logger.info('CA is not configured')
+ return False
+
+ return cainstance.ensure_lightweight_cas_container()
+
+
def upgrade_ca_audit_cert_validity(ca):
"""
Update the Dogtag audit signing certificate.
@@ -1438,7 +1448,10 @@ def ca_upgrade_schema(ca):
root_logger.info('CA is not configured')
return False
- schema_files=['/usr/share/pki/server/conf/schema-certProfile.ldif']
+ schema_files=[
+ '/usr/share/pki/server/conf/schema-certProfile.ldif',
+ '/usr/share/pki/server/conf/schema-authority.ldif',
+ ]
try:
modified = schemaupdate.update_schema(schema_files, ldapi=True)
except Exception as e:
@@ -1698,6 +1711,7 @@ def upgrade_configuration():
except ipautil.CalledProcessError as e:
root_logger.error("Failed to restart %s: %s", ca.service_name, e)
+ ca_ensure_lightweight_cas_container(ca)
ca_enable_ldap_profile_subsystem(ca)
# This step MUST be done after ca_enable_ldap_profile_subsystem and
diff --git a/ipaserver/plugins/ca.py b/ipaserver/plugins/ca.py
new file mode 100644
index 000000000..ee98f0a2a
--- /dev/null
+++ b/ipaserver/plugins/ca.py
@@ -0,0 +1,217 @@
+#
+# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
+#
+
+from ipalib import api, errors, DNParam, Str
+from ipalib.constants import IPA_CA_CN
+from ipalib.plugable import Registry
+from ipaserver.plugins.baseldap import (
+ LDAPObject, LDAPSearch, LDAPCreate, LDAPDelete,
+ LDAPUpdate, LDAPRetrieve)
+from ipaserver.plugins.cert import ca_enabled_check
+from ipalib import _, ngettext
+
+
+__doc__ = _("""
+Manage Certificate Authorities
+
+Subordinate Certificate Authorities (Sub-CAs) can be added for scoped issuance
+of X.509 certificates.
+
+EXAMPLES:
+
+ Create new CA, subordinate to the IPA CA.
+
+ ipa ca-add puppet --desc "Puppet" \\
+ --subject "CN=Puppet CA,O=EXAMPLE.COM"
+
+""")
+
+
+register = Registry()
+
+
+@register()
+class ca(LDAPObject):
+ """
+ Lightweight CA Object
+ """
+ container_dn = api.env.container_ca
+ object_name = _('Certificate Authority')
+ object_name_plural = _('Certificate Authorities')
+ object_class = ['ipaca']
+ permission_filter_objectclasses = ['ipaca']
+ default_attributes = [
+ 'cn', 'description', 'ipacaid', 'ipacaissuerdn', 'ipacasubjectdn',
+ ]
+ rdn_attribute = 'cn'
+ rdn_is_primary_key = True
+ label = _('Certificate Authorities')
+ label_singular = _('Certificate Authority')
+
+ takes_params = (
+ Str('cn',
+ primary_key=True,
+ cli_name='name',
+ label=_('Name'),
+ doc=_('Name for referencing the CA'),
+ ),
+ Str('description?',
+ cli_name='desc',
+ label=_('Description'),
+ doc=_('Description of the purpose of the CA'),
+ ),
+ Str('ipacaid',
+ cli_name='id',
+ label=_('Authority ID'),
+ doc=_('Dogtag Authority ID'),
+ flags=['no_create', 'no_update'],
+ ),
+ DNParam('ipacasubjectdn',
+ cli_name='subject',
+ label=_('Subject DN'),
+ doc=_('Subject Distinguished Name'),
+ flags=['no_update'],
+ ),
+ DNParam('ipacaissuerdn',
+ cli_name='issuer',
+ label=_('Issuer DN'),
+ doc=_('Issuer Distinguished Name'),
+ flags=['no_create', 'no_update'],
+ ),
+ )
+
+ permission_filter_objectclasses = ['ipaca']
+ managed_permissions = {
+ 'System: Read CAs': {
+ 'replaces_global_anonymous_aci': True,
+ 'ipapermbindruletype': 'all',
+ 'ipapermright': {'read', 'search', 'compare'},
+ 'ipapermdefaultattr': {
+ 'cn',
+ 'description',
+ 'ipacaid',
+ 'ipacaissuerdn',
+ 'ipacasubjectdn',
+ 'objectclass',
+ },
+ },
+ 'System: Add CA': {
+ 'ipapermright': {'add'},
+ 'replaces': [
+ '(target = "ldap:///cn=*,cn=cas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Add CA";allow (add) groupdn = "ldap:///cn=Add CA,cn=permissions,cn=pbac,$SUFFIX";)',
+ ],
+ 'default_privileges': {'CA Administrator'},
+ },
+ 'System: Delete CA': {
+ 'ipapermright': {'delete'},
+ 'replaces': [
+ '(target = "ldap:///cn=*,cn=cas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete CA";allow (delete) groupdn = "ldap:///cn=Delete CA,cn=permissions,cn=pbac,$SUFFIX";)',
+ ],
+ 'default_privileges': {'CA Administrator'},
+ },
+ 'System: Modify CA': {
+ 'ipapermright': {'write'},
+ 'ipapermdefaultattr': {
+ 'cn',
+ 'description',
+ },
+ 'replaces': [
+ '(targetattr = "cn || description")(target = "ldap:///cn=*,cn=cas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify CA";allow (write) groupdn = "ldap:///cn=Modify CA,cn=permissions,cn=pbac,$SUFFIX";)',
+ ],
+ 'default_privileges': {'CA Administrator'},
+ },
+ }
+
+
+@register()
+class ca_find(LDAPSearch):
+ __doc__ = _("Search for CAs.")
+ msg_summary = ngettext(
+ '%(count)d CA matched', '%(count)d CAs matched', 0
+ )
+
+ def execute(self, *keys, **options):
+ ca_enabled_check()
+ return super(ca_find, self).execute(*keys, **options)
+
+
+@register()
+class ca_show(LDAPRetrieve):
+ __doc__ = _("Display the properties of a CA.")
+
+ def execute(self, *args, **kwargs):
+ ca_enabled_check()
+ return super(ca_show, self).execute(*args, **kwargs)
+
+
+@register()
+class ca_add(LDAPCreate):
+ __doc__ = _("Create a CA.")
+ msg_summary = _('Created CA "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options):
+ ca_enabled_check()
+ if not ldap.can_add(dn[1:]):
+ raise errors.ACIError(
+ info=_("Insufficient 'add' privilege for entry '%s'.") % dn)
+
+ # check for name collision before creating CA in Dogtag
+ try:
+ api.Object.ca.get_dn_if_exists(keys[-1])
+ self.obj.handle_duplicate_entry(*keys)
+ except errors.NotFound:
+ pass
+
+ # Create the CA in Dogtag.
+ with self.api.Backend.ra_lightweight_ca as ca_api:
+ resp = ca_api.create_ca(options['ipacasubjectdn'])
+ entry['ipacaid'] = [resp['id']]
+ entry['ipacaissuerdn'] = [resp['issuerDN']]
+
+ # In the event that the issued certificate's subject DN
+ # differs from what was requested, record the actual DN.
+ #
+ entry['ipacasubjectdn'] = [resp['dn']]
+ return dn
+
+
+@register()
+class ca_del(LDAPDelete):
+ __doc__ = _('Delete a CA.')
+
+ msg_summary = _('Deleted CA "%(value)s"')
+
+ def pre_callback(self, ldap, dn, *keys, **options):
+ ca_enabled_check()
+
+ if keys[0] == IPA_CA_CN:
+ raise errors.ProtectedEntryError(
+ label=_("CA"),
+ key=keys[0],
+ reason=_("IPA CA cannot be deleted"))
+
+ ca_id = self.api.Command.ca_show(keys[0])['result']['ipacaid'][0]
+ with self.api.Backend.ra_lightweight_ca as ca_api:
+ ca_api.disable_ca(ca_id)
+ ca_api.delete_ca(ca_id)
+
+ return dn
+
+
+@register()
+class ca_mod(LDAPUpdate):
+ __doc__ = _("Modify CA configuration.")
+ msg_summary = _('Modified CA "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ ca_enabled_check()
+
+ if 'rename' in options or 'cn' in entry_attrs:
+ if keys[0] == IPA_CA_CN:
+ raise errors.ProtectedEntryError(
+ label=_("CA"),
+ key=keys[0],
+ reason=u'IPA CA cannot be renamed')
+
+ return dn
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index 197814c4d..20349b05f 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -2073,7 +2073,10 @@ class RestClient(Backend):
headers = headers or {}
headers['Cookie'] = self.cookie
- resource = os.path.join('/ca/rest', self.path, path)
+ if path is not None:
+ resource = os.path.join('/ca/rest', self.path, path)
+ else:
+ resource = os.path.join('/ca/rest', self.path)
# perform main request
status, resp_headers, resp_body = dogtag.https_request(
@@ -2147,3 +2150,52 @@ class ra_certprofile(RestClient):
Delete the profile from Dogtag
"""
self._ssldo('DELETE', profile_id, headers={'Accept': 'application/json'})
+
+
+@register()
+class ra_lightweight_ca(RestClient):
+ """
+ Lightweight CA management backend plugin.
+ """
+ path = 'authorities'
+
+ def create_ca(self, dn):
+ """Create CA with the given DN.
+
+ New CA is issued by IPA CA. Nested sub-CAs and unrelated
+ root CAs are not yet supported.
+
+ Return the (parsed) JSON response from server.
+
+ """
+
+ assert isinstance(dn, DN)
+ status, resp_headers, resp_body = self._ssldo(
+ 'POST', None,
+ headers={
+ 'Content-type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ body=json.dumps({"parentID": "host-authority", "dn": unicode(dn)}),
+ )
+ try:
+ return json.loads(resp_body)
+ except:
+ raise errors.RemoteRetrieveError(reason=_("Response from CA was not valid JSON"))
+
+ def read_ca(self, ca_id):
+ status, resp_headers, resp_body = self._ssldo(
+ 'GET', ca_id, headers={'Accept': 'application/json'})
+ try:
+ return json.loads(resp_body)
+ except:
+ raise errors.RemoteRetrieveError(reason=_("Response from CA was not valid JSON"))
+
+ def disable_ca(self, ca_id):
+ self._ssldo(
+ 'POST', ca_id + '/disable',
+ headers={'Accept': 'application/json'},
+ )
+
+ def delete_ca(self, ca_id):
+ self._ssldo('DELETE', ca_id)