diff options
author | Fraser Tweedale <ftweedal@redhat.com> | 2015-05-14 01:46:06 -0400 |
---|---|---|
committer | Fraser Tweedale <ftweedal@redhat.com> | 2015-07-06 11:25:01 -0400 |
commit | f5fa6e1a0d0d5dd66ba23937d16665b5793e20d3 (patch) | |
tree | 0ff160a47f52efe0af4d4e4536b1b122a78d09d6 | |
parent | 2a976334c2160c91a61fb0c477777e7adbbd3150 (diff) | |
download | freeipa-feature/subca.zip freeipa-feature/subca.tar.gz freeipa-feature/subca.tar.xz |
Add subca pluginfeature/subca
Part of: https://fedorahosted.org/freeipa/ticket/4559
-rw-r--r-- | ACI.txt | 8 | ||||
-rw-r--r-- | API.txt | 52 | ||||
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | VERSION | 4 | ||||
-rw-r--r-- | install/conf/ipa-pki-proxy.conf | 4 | ||||
-rw-r--r-- | install/share/60certificate-profiles.ldif | 1 | ||||
-rw-r--r-- | install/share/bootstrap-template.ldif | 6 | ||||
-rw-r--r-- | install/updates/41-subca.update | 4 | ||||
-rw-r--r-- | install/updates/Makefile.am | 1 | ||||
-rw-r--r-- | ipalib/constants.py | 1 | ||||
-rw-r--r-- | ipalib/plugins/subca.py | 184 | ||||
-rw-r--r-- | ipaserver/install/cainstance.py | 8 | ||||
-rw-r--r-- | ipaserver/install/server/upgrade.py | 16 | ||||
-rw-r--r-- | ipaserver/plugins/dogtag.py | 23 |
14 files changed, 309 insertions, 8 deletions
@@ -270,6 +270,14 @@ dn: cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example aci: (targetattr = "krblastpwdchange || krbpasswordexpiration || krbprincipalkey || userpassword")(target = "ldap:///uid=*,cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Reset Preserved User password";allow (read,search,write) groupdn = "ldap:///cn=System: Reset Preserved User password,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: dc=ipa,dc=example aci: (target_to = "ldap:///cn=users,cn=accounts,dc=ipa,dc=example")(target_from = "ldap:///cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=nsContainer)")(version 3.0;acl "permission:System: Undelete User";allow (moddn) groupdn = "ldap:///cn=System: Undelete User,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=subcas,cn=ca,dc=ipa,dc=example +aci: (targetfilter = "(objectclass=ipasubca)")(version 3.0;acl "permission:System: Add Sub-CA";allow (add) groupdn = "ldap:///cn=System: Add Sub-CA,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=subcas,cn=ca,dc=ipa,dc=example +aci: (targetfilter = "(objectclass=ipasubca)")(version 3.0;acl "permission:System: Delete Sub-CA";allow (delete) groupdn = "ldap:///cn=System: Delete Sub-CA,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=subcas,cn=ca,dc=ipa,dc=example +aci: (targetattr = "cn || description")(targetfilter = "(objectclass=ipasubca)")(version 3.0;acl "permission:System: Modify Sub-CA";allow (write) groupdn = "ldap:///cn=System: Modify Sub-CA,cn=permissions,cn=pbac,dc=ipa,dc=example";) +dn: cn=subcas,cn=ca,dc=ipa,dc=example +aci: (targetattr = "cn || createtimestamp || description || entryusn || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipasubca)")(version 3.0;acl "permission:System: Read Sub-CAs";allow (compare,read,search) userdn = "ldap:///all";) dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example aci: (targetfilter = "(objectclass=ipasudocmd)")(version 3.0;acl "permission:System: Add Sudo Command";allow (add) groupdn = "ldap:///cn=System: Add Sudo Command,cn=permissions,cn=pbac,dc=ipa,dc=example";) dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example @@ -4393,6 +4393,58 @@ 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: subca_add +args: 1,6,3 +arg: Str('cn', attribute=True, cli_name='handle', multivalue=False, primary_key=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('description', attribute=True, cli_name='desc', multivalue=False, required=True) +option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +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: subca_find +args: 1,8,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='handle', 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: 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: subca_mod +args: 1,9,3 +arg: Str('cn', attribute=True, cli_name='handle', 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: 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: subca_show +args: 1,4,3 +arg: Str('cn', attribute=True, cli_name='handle', 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: sudocmd_add args: 1,7,3 arg: Str('sudocmd', attribute=True, cli_name='command', multivalue=False, primary_key=True, required=True) @@ -0,0 +1,5 @@ +SubCAs TODO: + +- bump pki dep to version with sub-cas +- key replication via custodia +- see other TODOs in code @@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=137 -# Last change: mbabinsk: Commands to manage user/host/service certificates +IPA_API_VERSION_MINOR=138 +# Last change: ftweedal - add subca plugin diff --git a/install/conf/ipa-pki-proxy.conf b/install/conf/ipa-pki-proxy.conf index 4b5b6f7..4e31793 100644 --- a/install/conf/ipa-pki-proxy.conf +++ b/install/conf/ipa-pki-proxy.conf @@ -1,4 +1,4 @@ -# VERSION 8 - DO NOT REMOVE THIS LINE +# VERSION 9 - DO NOT REMOVE THIS LINE ProxyRequests Off @@ -27,7 +27,7 @@ ProxyRequests Off </LocationMatch> # matches for CA REST API -<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/installer/installToken|^/ca/rest/securityDomain/domainInfo|^/ca/rest/securityDomain/installToken|^/ca/rest/profiles|^/ca/rest/admin/kraconnector/remove"> +<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/installer/installToken|^/ca/rest/securityDomain/domainInfo|^/ca/rest/securityDomain/installToken|^/ca/rest/profiles|^/ca/rest/subca|^/ca/rest/admin/kraconnector/remove"> NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate NSSVerifyClient optional ProxyPassMatch ajp://localhost:$DOGTAG_PORT diff --git a/install/share/60certificate-profiles.ldif b/install/share/60certificate-profiles.ldif index 798c3a3..5235cd8 100644 --- a/install/share/60certificate-profiles.ldif +++ b/install/share/60certificate-profiles.ldif @@ -6,3 +6,4 @@ attributeTypes: (2.16.840.1.113730.3.8.21.1.4 NAME 'ipaCaCategory' DESC 'Additio attributeTypes: (2.16.840.1.113730.3.8.21.1.5 NAME 'ipaCertProfileCategory' DESC 'Additional classification for certificate profiles' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.2' ) objectClasses: (2.16.840.1.113730.3.8.21.2.1 NAME 'ipaCertProfile' SUP top STRUCTURAL MUST ( cn $ description $ ipaCertProfileStoreIssued ) X-ORIGIN 'IPA v4.2' ) objectClasses: (2.16.840.1.113730.3.8.21.2.2 NAME 'ipaCaAcl' SUP ipaAssociation STRUCTURAL MUST cn MAY ( ipaCaCategory $ ipaCertProfileCategory $ userCategory $ hostCategory $ serviceCategory $ ipaMemberCa $ ipaMemberCertProfile $ memberService ) X-ORIGIN 'IPA v4.2' ) +objectClasses: (2.16.840.1.113730.3.8.21.2.3 NAME 'ipaSubCa' SUP top STRUCTURAL MUST cn MAY description X-ORIGIN 'IPA v4.2' ) diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif index 2387f22..710c134 100644 --- a/install/share/bootstrap-template.ldif +++ b/install/share/bootstrap-template.ldif @@ -447,3 +447,9 @@ changetype: add objectClass: nsContainer objectClass: top cn: caacls + +dn: cn=subcas,cn=ca,$SUFFIX +changetype: add +objectClass: nsContainer +objectClass: top +cn: subcas diff --git a/install/updates/41-subca.update b/install/updates/41-subca.update new file mode 100644 index 0000000..dd094ff --- /dev/null +++ b/install/updates/41-subca.update @@ -0,0 +1,4 @@ +dn: cn=subcas,cn=ca,$SUFFIX +default: objectClass: nsContainer +default: objectClass: top +default: cn: subcas diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am index 2693e4f..3010a19 100644 --- a/install/updates/Makefile.am +++ b/install/updates/Makefile.am @@ -35,6 +35,7 @@ app_DATA = \ 40-certprofile.update \ 40-otp.update \ 41-caacl.update \ + 41-subca.update \ 45-roles.update \ 50-7_bit_check.update \ 50-dogtag10-migration.update \ diff --git a/ipalib/constants.py b/ipalib/constants.py index a062505..48ffc90 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -121,6 +121,7 @@ DEFAULT_CONFIG = ( ('container_certprofile', DN(('cn', 'certprofiles'), ('cn', 'ca'))), ('container_topology', DN(('cn', 'topology'), ('cn', 'ipa'), ('cn', 'etc'))), ('container_caacl', DN(('cn', 'caacls'), ('cn', 'ca'))), + ('container_subca', DN(('cn', 'subcas'), ('cn', 'ca'))), # Ports, hosts, and URIs: ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'), diff --git a/ipalib/plugins/subca.py b/ipalib/plugins/subca.py new file mode 100644 index 0000000..0d0532a --- /dev/null +++ b/ipalib/plugins/subca.py @@ -0,0 +1,184 @@ +# +# Copyright (C) 2015 FreeIPA Contributors see COPYING for license +# + +import re + +from ipalib import api, Str +from ipalib import output +from ipalib.plugable import Registry +from ipalib.plugins.baseldap import ( + LDAPObject, LDAPSearch, LDAPCreate, + LDAPUpdate, LDAPRetrieve) +from ipalib.plugins.cert import ca_enabled_check +from ipalib import ngettext +from ipalib.text import _ + + + +__doc__ = _(""" +Manage Subordiate Certificate Authorities + +Subordinate Certificate Authorities (Sub-CAs) can be added for scoped issuance +of X.509 certificates. + +CA HANDLE SYNTAX: + +A CA handle is a string without spaces or punctuation starting with a letter +and followed by a sequence of letters, digits or underscore ("_"). + +EXAMPLES: + + Create a new Sub-CA + ipa subca-add puppet --purpose "Puppet Certificates" + + Delete a Sub-CA: + ipa subca-del puppet + +""") + + +register = Registry() + + +subca_handle_pattern = re.compile('^[a-zA-Z]\w*$') + + +def validate_subca_handle(ugettext, value): + """Ensure Sub-CA handle matches form required by CA.""" + if subca_handle_pattern.match(value) is None: + return _('invalid Sub-CA handle') + else: + return None + + +@register() +class subca(LDAPObject): + """ + Sub-CA object. + """ + container_dn = api.env.container_subca + object_name = _('Subordinate Certificate Authority') + object_name_plural = _('Subordinate Certificate Authorities') + object_class = ['ipasubca'] + default_attributes = ['cn', 'description'] + search_attributes = ['cn', 'description'] + rdn_is_primary_key = True + label = _('Subordinate Certificate Authorities') + label_singular = _('Subordinate Certificate Authority') + + takes_params = ( + Str('cn', validate_subca_handle, + primary_key=True, + cli_name='handle', + label=_('Sub-CA handle'), + doc=_('Handle for referencing this Sub-CA'), + ), + Str('description', + required=True, + cli_name='desc', + label=_('Sub-CA description'), + doc=_('Brief description of this Sub-CA'), + ), + ) + + permission_filter_objectclasses = ['ipasubca'] + managed_permissions = { + 'System: Read Sub-CAs': { + 'replaces_global_anonymous_aci': True, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'cn', + 'description', + 'objectclass', + }, + }, + 'System: Add Sub-CA': { + 'ipapermright': {'add'}, + 'replaces': [ + '(target = "ldap:///cn=*,cn=subcas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Add Sub-CA";allow (add) groupdn = "ldap:///cn=Add Sub-CA,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'CA Administrator'}, + }, + 'System: Delete Sub-CA': { + 'ipapermright': {'delete'}, + 'replaces': [ + '(target = "ldap:///cn=*,cn=subcas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete Sub-CA";allow (delete) groupdn = "ldap:///cn=Delete Sub-CA,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'CA Administrator'}, + }, + 'System: Modify Sub-CA': { + 'ipapermright': {'write'}, + 'ipapermdefaultattr': { + 'cn', + 'description', + }, + 'replaces': [ + '(targetattr = "cn || description")(target = "ldap:///cn=*,cn=subcas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify Sub-CA";allow (write) groupdn = "ldap:///cn=Modify Sub-CA,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'CA Administrator'}, + }, + } + + +@register() +class subca_find(LDAPSearch): + __doc__ = _("Search for Sub-CAs.") + msg_summary = ngettext( + '%(count)d Sub-CA matched', '%(count)d sub-CAs matched', 0 + ) + + def execute(self, *keys, **options): + ca_enabled_check() + return super(subca_find, self).execute(*keys, **options) + + +@register() +class subca_show(LDAPRetrieve): + __doc__ = _("Display the properties of a Sub-CA.") + + def execute(self, *args, **kwargs): + ca_enabled_check() + return super(subca_show, self).execute(*args, **kwargs) + + +@register() +class subca_add(LDAPCreate): + __doc__ = _("Create a Sub-CA.") + msg_summary = _('Created Sub-CA "%(value)s"') + + takes_options = ( + Str('issuer_dn', # TODO DNParam ? + label=_('Issuer DN'), + ), + ) + + def pre_callback(self, ldap, dn, entry, entry_attrs, *keys, **options): + ca_enabled_check() + return dn + + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + """Create the sub-CA in Dogtag. + + If the operation fails, remove the LDAP entry. + """ + try: + with self.api.Backend.ra_subca as subca_api: + subca_api.create_subca(keys[0], options['issuer_dn']) + except: + ldap.delete_entry(dn) + raise + + return dn + + +@register() +class subca_mod(LDAPUpdate): + __doc__ = _("Modify Sub-CA configuration.") + msg_summary = _('Modified Sub-CA "%(value)s"') + + def execute(self, *args, **kwargs): + ca_enabled_check() + # TODO prevent rename (cn maps to Dogtag subca) + return super(subca_mod, self).execute(*args, **kwargs) diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 5fd3017..4cf5a26 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -1640,6 +1640,10 @@ def update_people_entry(dercert): return True def ensure_ldap_profiles_container(): + ensure_container(('ou', 'certificateProfiles')) + + +def ensure_container(rdn): server_id = installutils.realm_to_serverid(api.env.realm) dogtag_uri = 'ldapi://%%2fvar%%2frun%%2fslapd-%s.socket' % server_id @@ -1647,7 +1651,7 @@ def ensure_ldap_profiles_container(): if not conn.isconnected(): conn.connect(autobind=True) - dn = DN(('ou', 'certificateProfiles'), ('ou', 'ca'), ('o', 'ipaca')) + dn = DN(rdn, ('ou', 'ca'), ('o', 'ipaca')) try: conn.get_entry(dn) except errors.NotFound: @@ -1655,7 +1659,7 @@ def ensure_ldap_profiles_container(): entry = conn.make_entry( dn, objectclass=['top', 'organizationalUnit'], - ou=['certificateProfiles'], + **{rdn[0]: [rdn[1]]} ) conn.add_entry(entry) diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 740f046..a62cc53 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -349,6 +349,16 @@ def ca_import_included_profiles(ca): return cainstance.import_included_profiles() +def ca_ensure_subcas_container(ca): + root_logger.info('[Ensuring sub-CAs container exists in Dogtag database]') + + if not ca.is_configured(): + root_logger.info('CA is not configured') + return False + + return cainstance.ensure_container(('ou', 'subCAs')) + + def upgrade_ca_audit_cert_validity(ca): """ Update the Dogtag audit signing certificate. @@ -1261,7 +1271,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-subCA.ldif', + ] try: modified = schemaupdate.update_schema(schema_files, ldapi=True) except Exception as e: @@ -1488,6 +1501,7 @@ def upgrade_configuration(): except ipautil.CalledProcessError as e: root_logger.error("Failed to restart %s: %s", ca.service_name, e) + ca_ensure_subcas_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/dogtag.py b/ipaserver/plugins/dogtag.py index 3dc8f5c..f03f791 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -2045,7 +2045,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, status_text, resp_headers, resp_body = dogtag.https_request( @@ -2100,3 +2103,21 @@ class ra_certprofile(RestClient): self._ssldo('DELETE', profile_id, headers={'Accept': 'application/json'}) api.register(ra_certprofile) + + +class ra_subca(RestClient): + """ + Sub-CA management backend plugin. + """ + path = 'subca' + + def create_subca(self, ca_ref, dn): + self._ssldo('POST', None, + headers={ + 'Content-type': 'application/json', + 'Accept': 'application/json', + }, + body=json.dumps({"caRef": ca_ref, "dn": dn}), + ) + +api.register(ra_subca) |